Repository navigation
Judge a root against the terms it cancels, and judge it either way it is written - #685
Merged
Happypig375 merged 1 commit intoAug 4, 2026
Conversation
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #685 +/- ##
==========================================
- Coverage 80.99% 80.57% -0.43%
==========================================
Files 155 156 +1
Lines 13687 12930 -757
Branches 1957 2125 +168
==========================================
- Hits 11086 10418 -668
+ Misses 1990 1906 -84
+ Partials 611 606 -5 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
… is written
Two faults, both in the check that drops roots which do not satisfy the equation
they came from.
The check reads the residual on its own size, and a root found numerically is
only as accurate as the search that found it: the terms of the equation cancel at
such a root to within that accuracy rather than exactly. The four roots of
1/210 - 17x/210 + 101x^2/210 - 247x^3/210 + x^4 come back as decimals and leave
5.2e-6 against terms of about 1.2e-2, so all four were dropped and master answers
that quartic with the empty set. The residual is now read against the largest of
the terms it is the sum of. The two cases are far apart because a spurious root
is not a near miss -- it comes from a rewrite that widened the domain, so the
residual is a whole quantity in its own right, 2*pi*i for a logarithm sum or the
difference between -3 and 9 for an exponential. Measured over these equations
every genuine root sits at 4.5e-4 or below and most at 1e-15, while every
spurious one sits between 1.5 and 2. The threshold is a hundredth.
The check also ran only when the caller wrote the equation out as expr = 0. The
same equation handed over as the bare expr went through another entry point and
was checked only for being finite, so the two disagreed:
(2^x + 2^(2x) - 6 = 0).Solve(x) gave { 1 } while
"2^x + 2^(2x) - 6".SolveEquation(x) gave { 1, ln((-3) ^ (1 / ln(2))) }. That
second answer is a root of nothing: the exponential solver substitutes t = e^x
and inverts with t = v^(1/p), and composing that with x = ln(t) is only the same
number while the imaginary part stays inside the principal branch. For a negative
v it does not, and 1.58 - 1.75i is a full turn away from the root 1.58 + 4.53i.
Both entry points now check.
Found while testing whether #214 could be closed. 18 tests, of which 5 fail
without the change; 3898 in all, 127 F#, corpus 88/117 with nothing wrong, and
1320 property checks.
Rafael-SOWNet
force-pushed
the
fix/spurious-roots-without-equals
branch
from
August 4, 2026 11:08
a93d959 to
f1460df
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Two faults in the check that drops roots which do not satisfy the equation they came from. Found while testing whether #214 could be closed.
A quartic answered with the empty set
It has four real roots. They come back from the numeric search as decimals, and a root found that way is only as accurate as the search that found it — the terms cancel at it to within that accuracy rather than exactly. These leave a residual of 5.2e-6 against terms of about 1.2e-2. The check read that residual on its own size against a fixed 1e-6 and threw all four away.
The residual is now read against the largest of the terms it is the sum of.
The threshold is measured, not picked. A spurious root is not a near miss — it comes from a rewrite that widened the domain, so its residual is a whole quantity in its own right:
2*pi*ifor a logarithm sum, or the difference between −3 and 9 for an exponential. Across the equations I measured:The threshold is a hundredth — twenty times above the worst genuine root, a hundred and fifty times below the mildest spurious one.
The same equation answered two ways
The check only ran when the caller wrote the equation out. Handed the bare expression it went through a different entry point that checked only for finiteness:
The second answer is a root of nothing. The exponential solver substitutes
t = e^xand inverts witht = v^(1/p); composing that withx = ln(t)is only the same number while the imaginary part stays inside the principal branch. For a negativevit does not —ln((-3)^(1/ln 2))is1.58 - 1.75i, exactly one turn away from the actual rootln(-3)/ln(2) = 1.58 + 4.53i, and satisfies nothing. Both entry points now check.The genuine complex root is still not found; this only stops a non-root being reported in its place. Recovering it needs the exponential solver to invert the composition rather than the two steps separately, which is a separate piece of work.
Verification
18 tests, 5 of which fail without the change. 3898 unit tests and 127 F# tests on both target frameworks, none failing — including
MomoTest, which is the quartic above and which fails without the first half of this. 117-problem self-verifying corpus at 88/117 with nothing wrong, in error or timing out; 1320 property checks over 151 expressions, none failing.One thing I did not pin:
x^5 + 3*x + 1comes back with 28 roots for a quintic, near-duplicates from the numeric search. That is untouched here and worth its own look.