Skip to content

Answer the limit of a signum instead of overflowing the stack (#704) - #705

Merged
Rafael-SOWNet merged 1 commit into
ASC-Community:masterfrom
Rafael-SOWNet:fix/signum-limit
Aug 4, 2026
Merged

Rafael-SOWNet merged 1 commit into
ASC-Community:masterfrom
Rafael-SOWNet:fix/signum-limit

Conversation

@Rafael-SOWNet

Copy link
Copy Markdown
Member

lim x->2 signum(x) kills the process. Not an exception — a stack overflow, so a caller cannot guard against it.

Signumf was the one node whose limit override handed back an unevaluated Limitf of the very expression it was asked about:

// TODO:
internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side)
    => new Limitf(this, x, dist, side);

That is not merely a failure to answer. The two-sided finite path compares its two one-sided results with ExpressionNumerical.AreEqual, which evaluates them; evaluating a Limitf computes the limit; and computing it arrives back at the same override. Some four thousand frames later the stack runs out. abs(x) is fine, because Absf maps the limit through its argument instead.

What it answers now

The sign is constant on either side of zero, so wherever the argument tends to anything but zero the limit is the sign of that — including at the infinities, where signum is 1 and -1.

before after
lim x->2 signum(x) crash 1
lim x->-2 signum(x) crash -1
lim x->2 signum(x - 5) crash -1
lim x->+oo signum(x) crash 1
lim x->-oo signum(x) crash -1
lim x->2 signum(x) * x crash 2
lim x->0 signum(x) crash unevaluated

At zero there is nothing to say — the sign is 1 on one side and -1 on the other, and which one a one-sided limit takes depends on the direction the argument approaches from rather than only on what it tends to. So that one stays unevaluated, which is the honest answer and, more to the point, terminates.

The one-line reason it stops recursing

null instead of a Limitf of itself. It means the same thing to the caller — no reading — and the public API still hands back an unevaluated limit, so nothing about the answer changes for the unsettleable case. But null takes the branch that falls through to l'Hopital's rule and returns, rather than the branch that evaluates the two results and re-enters. Worth stating because it is the whole fix.

Tests

13 new. Three of them assert termination rather than a value — they would time out rather than fail without the change, which is the right shape for a former crash: a regression fails the run instead of taking the runner down with it.

Suite: Failed: 0, Passed: 4474, Skipped: 14, Total: 4488.

Independent of #703; git merge-tree reports no conflict between them, though both touch Limit.Classes.cs in different places. Found while writing #703, where a new node had the same shape and the same crash — I gave that one a real limit case rather than copying this pattern.

lim x->2 signum(x) killed the process. Signumf was the one node whose limit
override handed back an unevaluated limit of the very expression it was asked
about, and that is not merely a failure to answer: the two-sided path compares
its two one-sided results by evaluating them, evaluating a limit computes it,
and computing it arrives back at the same override. Some four thousand frames
later the stack runs out, which kills the process rather than raising anything
a caller could catch.

The sign is constant on either side of zero, so wherever the argument tends to
anything but zero the limit is the sign of that -- including at the infinities,
where it is 1 and -1. At zero there is nothing to say, since the sign is 1 on
one side and -1 on the other and which one a one-sided limit takes depends on
the direction the argument approaches from rather than only on what it tends to.

Null is returned there rather than a limit of this expression. It means the same
thing to the caller, which hands back an unevaluated limit of its own, and it
takes the branch that falls through to l'Hopital's rule and returns instead of
the branch that evaluates and re-enters.

13 new tests, three of which time out rather than fail without the fix, since
what they are really pinning is termination. Suite 4474 passed, 0 failed.
@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 71.42857% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.59%. Comparing base (90c00a8) to head (3b97876).
⚠️ Report is 79 commits behind head on master.

Files with missing lines Patch % Lines
...nctions/Continuous/Limits/Solvers/Limit.Classes.cs 71.42% 1 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##           master     #705      +/-   ##
==========================================
+ Coverage   80.99%   81.59%   +0.59%     
==========================================
  Files         155      159       +4     
  Lines       13687    13793     +106     
  Branches     1957     2331     +374     
==========================================
+ Hits        11086    11254     +168     
+ Misses       1990     1885     -105     
- Partials      611      654      +43     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@Rafael-SOWNet
Rafael-SOWNet merged commit 0bde637 into ASC-Community:master Aug 4, 2026
25 of 26 checks passed
@Rafael-SOWNet
Rafael-SOWNet deleted the fix/signum-limit branch August 4, 2026 23:27
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.

1 participant