Skip to content

Limit over floor or ceil overflows the stack and kills the process #829

Description

@Rafael-SOWNet

Limit over any floor or ceil overflows the stack and terminates the process. It is not an
exception a caller can catch, so a host embedding the library dies with it.

This is #704 reappearing on a new pair of
nodes, introduced by #827.

Measured on master (46140f8)

lim_x->0  (floor(x))    Stack overflow.  (frame repeated 3741 times)
lim_x->2  (floor(x))    Stack overflow.
lim_x->+oo(floor(x))    Stack overflow.
lim_x->0  (ceil(x))     Stack overflow.

For contrast, the neighbours that behave:

lim_x->0  (sgn(x))      limit(sgn(x), x, 0)     <- unevaluated, which is the honest answer
lim_x->0  (abs(x))      0

The repeating frame is

AngouriMath.Entity.ExpandOnTwoAndTArguments<(Entity, ApproachFrom)>(...)
AngouriMath.Functions.Algebra.LimitFunctional.ComputeLimit(...)
AngouriMath.Core.Transformations.Transformation+LimitTransformation.ApplyCore(...)
AngouriMath.Core.Transformations.Transformation.Apply(...)
AngouriMath.Entity.Limit(...)

Cause

Entity.ComputeLimitDivideEtImpera (Limit.Definition.cs:148) has a base implementation that returns
new Limitf(this, x, dist, side) — an unevaluated limit of the very node being asked about. The
two-sided path then compares its two one-sided results by evaluating them, evaluating a limit computes
it, and control arrives back at the same node.

Floorf and Ceilf have no override, so they take that default.

This is written down already. The Signumf override
(Functions/Continuous/Limits/Solvers/Limit.Classes.cs:368-391) carries a comment naming #704 and
saying exactly this:

Nothing is returned there rather than an unevaluated limit of this very expression. That is what
used to be here, and it does not merely fail to answer [...] The recursion ends by overflowing the
stack, which kills the process rather than raising anything a caller could catch.

What the answer should be

floor is constant between consecutive integers, so where the argument tends to a value that is not
on a discontinuity the limit is floor of that value. On a discontinuity the two sides disagree and
there is nothing to say, so the answer is null — which makes the caller hand back an unevaluated
limit(...), the same as sgn does at zero.

Note that the side the argument approaches its own limit from is not the side the variable
approaches dist from, so a one-sided limit is not enough to decide the discontinuity case either.
Signumf is conservative there for the same reason.

Beyond this pair

Nothing in the repository lists the places a new Entity subtype has to be wired, and this is the
cost of that: #827 wired the node, evaluation, ToString, Latexise, ToSympy, differentiation,
InvertNode, Substitute, Sort, Domains, the grammar and tests — and missed one file, and the
result is a process kill.

A test generated by reflection over every Function subtype, asserting that a limit of it terminates,
would have caught this before merge and will catch the next one. round, trunc, min, max and
everything else proposed under #809 hits the same trap.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions