On current master:
"signum(x)".ToEntity().Limit("x", 2) // Stack overflow, process dies
It is not an exception — the process is killed, so a caller cannot guard against it.
Why
Signumf.ComputeLimitDivideEtImpera is the only node whose override hands back an unevaluated Limitf of itself:
partial record Signumf
{
// TODO:
internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side)
=> new Limitf(this, x, dist, side);
}
The two-sided finite path then compares the two one-sided results with ExpressionNumerical.AreEqual, which evaluates them; evaluating a Limitf calls Limit again; and that returns the same node. The cycle is ComputeLimit → Limitf.InnerSimplify → Limit → ComputeLimit, repeated some four thousand times before the stack runs out.
lim x->2 abs(x) is fine, because Absf maps the limit through its argument instead.
The fix
Returning null rather than a Limitf of itself. null means the same thing to the caller — no reading — and takes the branch that falls through to l'Hopital's rule and then returns, instead of the branch that evaluates and re-enters. The user still gets an unevaluated Limitf back from the public API, so nothing about the answer changes; only the recursion goes away.
Better still would be an actual reading: signum is continuous away from 0, so the limit is the value at the point wherever the argument's limit is non-zero.
I hit this writing #703, where a new node had the same shape and the same crash; there I gave it a real limit case. I have not touched Signumf in that PR since it is unrelated to modulus, but I am happy to send a separate one.
Found on 6c1f6b49, .NET 10, Linux.
On current
master:It is not an exception — the process is killed, so a caller cannot guard against it.
Why
Signumf.ComputeLimitDivideEtImperais the only node whose override hands back an unevaluatedLimitfof itself:The two-sided finite path then compares the two one-sided results with
ExpressionNumerical.AreEqual, which evaluates them; evaluating aLimitfcallsLimitagain; and that returns the same node. The cycle isComputeLimit→Limitf.InnerSimplify→Limit→ComputeLimit, repeated some four thousand times before the stack runs out.lim x->2 abs(x)is fine, becauseAbsfmaps the limit through its argument instead.The fix
Returning
nullrather than aLimitfof itself.nullmeans the same thing to the caller — no reading — and takes the branch that falls through to l'Hopital's rule and then returns, instead of the branch that evaluates and re-enters. The user still gets an unevaluatedLimitfback from the public API, so nothing about the answer changes; only the recursion goes away.Better still would be an actual reading:
signumis continuous away from 0, so the limit is the value at the point wherever the argument's limit is non-zero.I hit this writing #703, where a new node had the same shape and the same crash; there I gave it a real limit case. I have not touched
Signumfin that PR since it is unrelated to modulus, but I am happy to send a separate one.Found on
6c1f6b49, .NET 10, Linux.