Skip to content

Apply the parity identities, so that sin(-x) + sin(x) reaches zero (#929) - #931

Merged
Rafael-SOWNet merged 1 commit into
masterfrom
fix/parity-identities
Aug 14, 2026
Merged

Apply the parity identities, so that sin(-x) + sin(x) reaches zero (#929)#931
Rafael-SOWNet merged 1 commit into
masterfrom
fix/parity-identities

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Collaborator

Closes #929.

cos(-u) = cos(u), sin(-u) = -sin(u) and the rest of the family were absent, so an expression that cancels exactly did not.

"sin(-x) + sin(x)".Simplify()     was  sin(-x) + sin(x)     is  0
"cos(-x) - cos(x)".Simplify()     was  cos(-x) - cos(x)     is  0

Nothing false was being asserted, so this is coverage rather than a wrong answer put right — but an expression that is identically zero was left standing, and anything testing a residual against zero saw a non-zero residual where there was none.

Why only cos(-2 * x) worked before

By accident. The multiple-angle expansion fires for a coefficient of magnitude two or more, and -x is a coefficient of -1, which it skips — so cos(-2 * x) folded while cos(-x) did not, and sin(-2 * x), tan(-2 * x) and abs(-2 * x) did not either, cosine being the only function that expansion happens to handle this way.

A bare negation and a negative numeric coefficient are the same shape — a product with a negative real — and that is what is matched now.

Soundness

Even: cos, sec, abs. Odd: sin, tan, cotan, cosec, sgn.

Each holds on the whole complex plane, and the poles of the odd ones sit symmetrically about zero — tan(-z) is undefined exactly where tan(z) is — so the domain neither widens nor narrows and no condition is owed. That has a test of its own rather than being asserted here.

A cancellation between reciprocal functions keeps the condition it already had: tan(-x) + tan(x) is 0 provided not cos(x) = 0, which is right, since it is undefined at the poles rather than zero there.

The inverse functions are deliberately absent. arcsin and arctan are odd and arccos is not, and this library's arccotan has range (-pi/2, pi/2] rather than the textbook (0, pi), so each wants measuring before anything is written down.

One thing that did not move, and why the tests are shaped as they are

A lone sin(-x) still prints as sin(-x) rather than -sin(x). That is a tie, not a failure: rate:: gives both exactly 14, and a tie goes to whichever candidate was generated first. The identity is applied — it is what makes the cancellation above work — but it does not win a comparison it was never going to win.

So the tests assert the cancellation, which is what the identity is for, rather than pinning the tie-break, which would pin the tie-break.

How it was found

canoncheck, the canonical-form harness added for #746 tier 1 (see #928). abs(-x) vs abs(x), sin(-x) vs -sin(x) and cos(-x) vs cos(x) are three of its listed agreement pairs and all three disagreed. Its Simplify agreement count goes 6 disagreements to 4 — the two remaining being sin(-x) (the tie above) and x ^ 0, which answers 1 provided not x = 0 and is correct.

Measured

Suite 6960 passed / 0 failed (22 new); casbench 116/119 with 0 wrong, 0 error, 0 timeout; propcheck 1340 checks / 0 failures; rootcheck 596/596 clean; simpsweep 10463/10463 agree.

BREAKING-CHANGES.md carries the entry.

🤖 Generated with Claude Code

)

cos(-u) = cos(u), sin(-u) = -sin(u) and the rest of the family were absent, so an expression that
cancels exactly did not:

    "sin(-x) + sin(x)".Simplify()     sin(-x) + sin(x)  ->  0
    "cos(-x) - cos(x)".Simplify()     cos(-x) - cos(x)  ->  0

Nothing false was asserted, so this is coverage rather than a wrong answer put right -- but an
expression that is identically zero was left standing, and anything testing a residual against zero
saw a non-zero residual where there was none.

The one case that already folded, cos(-2 * x), folded by accident. The multiple-angle expansion
fires for a coefficient of magnitude two or more, and `-x` is a coefficient of -1, which it skips --
so cos(-2 * x) worked while cos(-x) did not, and sin(-2 * x), tan(-2 * x) and abs(-2 * x) did not
either, cosine being the only function that expansion happens to handle this way. A bare negation
and a negative numeric coefficient are the same shape, a product with a negative real, and that is
what is matched now.

Even: cos, sec, abs. Odd: sin, tan, cotan, cosec, sgn. Each holds on the whole complex plane, and
the poles of the odd ones sit symmetrically about zero -- tan(-z) is undefined exactly where tan(z)
is -- so the domain neither widens nor narrows and no condition is owed. That has a test of its own
rather than being asserted here. A cancellation between reciprocal functions keeps the condition it
already had: tan(-x) + tan(x) is `0 provided not cos(x) = 0`, which is right, since it is undefined
at the poles rather than zero there.

A lone sin(-x) still prints as sin(-x) rather than -sin(x), and that is a tie rather than a failure:
`rate::` gives both exactly 14, and a tie goes to whichever candidate was generated first. The
identity is applied -- it is what makes the cancellation work -- but it does not win a comparison it
was never going to win. So the tests assert the cancellation, which is what the identity is for,
rather than pinning the tie-break, which would pin the tie-break.

The inverse functions are deliberately absent: arcsin and arctan are odd and arccos is not, and this
library's arccotan has range (-pi/2, pi/2] rather than the textbook (0, pi), so each wants measuring
before it is written down.

Found by `canoncheck`, the canonical-form harness added for #746 tier 1: abs(-x) vs abs(x),
sin(-x) vs -sin(x) and cos(-x) vs cos(x) are three of its listed pairs and all three disagreed. Its
Simplify agreement count goes 6 disagreements to 4, the two remaining being sin(-x) (the tie above)
and x ^ 0, which is `1 provided not x = 0` and correct.

Measured: suite 6960 passed / 0 failed; casbench 116/119 with 0 wrong, 0 error, 0 timeout;
propcheck 1340 checks / 0 failures; rootcheck 596/596 clean; simpsweep 10463/10463 agree.

#929

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
@Rafael-SOWNet
Rafael-SOWNet merged commit abd99ff 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.

Parity identities are missing: sin(-x) + sin(x) does not reduce to 0

1 participant