Skip to content

Take the sign back the right way when both operands are negative (#936) - #937

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
fix/numericneat-sign
Aug 14, 2026
Merged

Take the sign back the right way when both operands are negative (#936)#937
Rafael-SOWNet merged 1 commit into
masterfrom
fix/numericneat-sign

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator

Closes #936. A wrong answer through public API.

RewriteRules.NumericNeat.ApplyOnce("-1 + -1")     was  2   is  -(1 + 1)
RewriteRules.NumericNeat.ApplyOnce("-2 + -3")     was  5   is  -(2 + 3)
RewriteRules.NumericNeat.ApplyOnce("-1 - (-1)")   was  2   is   1 - 1

RewriteRules and RewriteRuleSet.ApplyOnce are both public, and Transformation.Rewriting(RewriteRules.NumericNeat) is the documented way to run a single rule set.

The arithmetic

(-a) + (-b) is -(a + b), and the operands are already negative where the rule matches, so what is added is their magnitudes. Written -(left + right) it negated a sum that was negative already. And (-a) - (-b) is b - a, not a + b.

Every neighbouring branch is right — the four mixed-sign sum and difference cases, and the both-negative product and quotient — so this is two lines rather than a design problem. There is a test on the neighbours so a later correction cannot fix one sign by breaking another.

Why nothing caught it, which is the part worth keeping

Simplify and Evaled were correct throughout: the rule only ever fires on numerals, and on numerals evaluation has already produced the answer before this is consulted. Every harness in work/ exercises Simplify, so none of them could see it — the corpora were green on both sides.

A rule that only fires where something else covers for it can be arbitrarily wrong and never show, until a caller applies it alone — which the transformation layer now lets them do. The test therefore asserts the rule set applied on its own, and asserts the value rather than the shape, since writing -1 + -1 as -(1 + 1) is what the rule is for.

How it was found

rulecheck, a new harness for #746 tier 2, which asks for confluence and termination "checked by tooling rather than asserted by authors". Every RewriteRuleSet declares a TransformationRelation and a Soundness and nothing had ever checked either. It applies each set to generated expressions and, for a set declaring Equivalence, compares the value at sample points on and off the real line.

These were two of its findings on the first run. The other eight are non-termination — NumericNeat on --x produces -1 * 1 * 1 * 1 * ... without settling — and want their own issue.

Measured

Suite 7024 passed / 0 failed, 13 new; casbench 116/119 with 0 wrong, 0 error, 0 timeout; simpsweep 10463/10463 agree; propcheck 1340/0.

No BREAKING-CHANGES.md entry for Simplify, whose answers do not move; the rule set's own output does, and the issue records that.

🤖 Generated with Claude Code

    RewriteRules.NumericNeat.ApplyOnce("-1 + -1")     2   ->  -(1 + 1)
    RewriteRules.NumericNeat.ApplyOnce("-2 + -3")     5   ->  -(2 + 3)
    RewriteRules.NumericNeat.ApplyOnce("-1 - (-1)")   2   ->   1 - 1

A wrong answer through public API. `RewriteRules` and `RewriteRuleSet.ApplyOnce` are both public,
and `Transformation.Rewriting(RewriteRules.NumericNeat)` is the documented way to run one rule set.

Two branches, and the arithmetic in each. `(-a) + (-b)` is `-(a + b)`, and the operands are already
negative where the rule matches, so what is added is their magnitudes: written `-(left + right)` it
negated a sum that was negative already. And `(-a) - (-b)` is `b - a`, not `a + b`. Every
neighbouring branch is right -- the four mixed-sign sum and difference cases and the both-negative
product and quotient -- so this is two lines rather than a design problem, and there is a test on the
neighbours so that a later correction cannot fix one sign by breaking another.

**Why nothing caught it, which is the part worth keeping.** `Simplify` and `Evaled` were correct
throughout: the rule only ever fires on numerals, and on numerals evaluation has already produced the
answer before this is consulted. Every harness in `work/` exercises `Simplify`, so none of them could
see it, and the corpora were green on both sides. **A rule that only fires where something else
covers for it can be arbitrarily wrong and never show** -- until a caller applies it alone, which the
transformation layer now lets them do. The test asserts the rule set applied on its own for exactly
that reason, and asserts the value rather than the shape, since writing `-1 + -1` as `-(1 + 1)` is
what the rule is for.

Found by `rulecheck`, a new harness for #746 tier 2, which asks for confluence and termination
"checked by tooling rather than asserted by authors". It applies each rule set to generated
expressions and, for a set declaring Equivalence, compares the value at sample points on and off the
real line. This was two of its findings on the first run; the other eight are non-termination and
want their own issue.

Measured: suite 7024 passed / 0 failed, 13 of them new; casbench 116/119 with 0 wrong, 0 error,
0 timeout; simpsweep 10463/10463 agree; propcheck 1340 checks / 0 failures.

No BREAKING-CHANGES entry for `Simplify`, whose answers do not move. The rule set's own output does,
and that is what the issue and this message record.

#936

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@Rafael-SOWNet
Rafael-SOWNet merged commit 03a0bf6 into master Aug 14, 2026
25 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RewriteRules.NumericNeat answers 2 for -1 + -1: the sign is inverted when both operands are negative

1 participant