Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions BREAKING-CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,20 @@ rational exponent is still written as its value. Found by limitcheck; no issue w
| `"x^(abs(x)^arctan(2))".ToEntity().Limit("x", 0, ApproachFrom.Left)` | unevaluated | `1` |
| `"x^(abs(x)^arctan(2))".ToEntity().Limit("x", 0, ApproachFrom.Right)` | unevaluated | `1` |

### A two-sided limit whose sides are undecided is left unevaluated

**Silent: a two-sided limit is left unevaluated, rather than `NaN`, where a side's limit stops at an indeterminate form
with an infinity in it**, such as `0 * floor(-oo)`. That form is a side whose parts were never compared, not a side
without a limit, and `NaN` says the limit does not exist. Each limit below exists, and they are 2, 1, 1
and 1 in order. 2.6.0 answered the third with a wrong `0`. Found by limitcheck; no issue was filed.

| Input | Was (2.6.0) | Now |
|---|---|---|
| `"x * floor(2/x)".ToEntity().Limit("x", 0)` | `NaN` | `limit(x * floor(2 / x), x, 0)` |
| `"floor(1/x) * sin(x)".ToEntity().Limit("x", 0)` | `NaN` | `limit(floor(1 / x) * sin(x), x, 0)` |
| `"x^2 * floor(1/x^2)".ToEntity().Limit("x", 0)` | `0` | `limit(x ^ 2 * floor(1 / x ^ 2), x, 0)` |
| `"x * ceil(1/x)".ToEntity().Limit("x", 0)` | `NaN` | `limit(x * ceil(1 / x), x, 0)` |

### A limit at the jump of a step function is not answered by substitution

**Silent: a limit of an expression built on `floor`, `ceil`, `round` or `sgn`, where the step's argument arrives on one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,24 +180,48 @@ node is Floorf or Ceilf or Roundf or Signumf
/// <a href="https://github.com/asc-community/AngouriMath/issues/719">#719</a>,
/// <a href="https://github.com/asc-community/AngouriMath/issues/596">#596</a>
/// </remarks>
private static Entity? WhereBothSidesAgree(Entity expr, Variable x, Entity dest)
private static Entity? WhereBothSidesAgree(Entity expr, Variable x, Entity dest, out bool aSideIsUndecided)
{
// Set where no answer came because a side has none yet, rather than because a side
// does not exist or the two differ. Neither side is then known to be NaN, so neither
// is the limit: x floor(2/x) at 0 is 2 from each side, the one-sided paths do not
// decide it, and the two-sided one answered NaN.
aSideIsUndecided = false;
if (bothSidesDepth >= MaxBothSidesDepth
|| MathS.Settings.Codomain.Value is not AngouriMath.Core.Domain.Real && !IsRealOnBothSides(expr, x, dest))
return null;
bothSidesDepth++;
try
{
if (ComputeLimit(expr, x, dest, ApproachFrom.Left) is not { } fromLeft
|| fromLeft.Evaled == MathS.NaN)
var fromLeft = Decided(ComputeLimit(expr, x, dest, ApproachFrom.Left));
if (fromLeft?.Evaled == MathS.NaN)
return null;
if (ComputeLimit(expr, x, dest, ApproachFrom.Right) is not { } fromRight
|| fromRight.Evaled == MathS.NaN)
// A side that does not exist settles the question whatever the other side does,
// so the right is still asked when the left is undecided.
var fromRight = Decided(ComputeLimit(expr, x, dest, ApproachFrom.Right));
if (fromRight?.Evaled == MathS.NaN)
return null;
if (fromLeft is null || fromRight is null)
{
aSideIsUndecided = true;
return null;
}
return fromLeft == fromRight || ExpressionNumerical.AreEqual(fromLeft, fromRight)
? fromLeft : null;
}
finally { bothSidesDepth--; }

// An indeterminate form a side's own limit could not read is NaN inside the machinery,
// where it stops the search, and withheld at the outermost call, where it is not an
// answer. One with an infinity in it, 0 * floor(-oo) for x floor(2/x), is a product or
// a quotient of parts whose rates were never compared, so that side is undecided rather
// than absent. One of finite parts only stays a side without a limit, as before: 0 / 0
// for (x - x) / (x - x), which has no value anywhere, and ln(-2) / 0 for ln(-2) / ln(x)
// at 1, whose sides are infinities in different directions.
static Entity? Decided(Entity? limit)
=> limit is { } && IsAnIndeterminateForm(limit)
&& limit.Nodes.Any(static node => node is Real { IsFinite: false })
? null : limit;
}

/// <summary>
Expand Down Expand Up @@ -719,9 +743,13 @@ node is Derivativef(var expression, Variable variable, var iterations) && iterat
// again: tan(x/2) at pi, +oo and -oo, asked both sides through the whole of the
// machinery each time, and a definite integral over [0, pi] spent its budget of
// limits on that.
if ((fromLeft.Evaled == MathS.NaN || fromRight.Evaled == MathS.NaN)
&& WhereBothSidesAgree(expr, x, dest) is { } agreed)
return agreed;
if (fromLeft.Evaled == MathS.NaN || fromRight.Evaled == MathS.NaN)
{
if (WhereBothSidesAgree(expr, x, dest, out var aSideIsUndecided) is { } agreed)
return agreed;
if (aSideIsUndecided)
return null;
}
return MathS.NaN; // A two-sided limit cannot exist if the limit from left and right don't match.
}
else
Expand All @@ -734,7 +762,7 @@ node is Derivativef(var expression, Variable variable, var iterations) && iterat
// something that disagrees -- which is most of what the one-sided
// fallbacks are for, so it is where the promotion is worth the most.
// https://github.com/asc-community/AngouriMath/issues/719
return WhereBothSidesAgree(expr, x, dest);
return WhereBothSidesAgree(expr, x, dest, out _);
}
}
throw new AngouriBugException($"Unresolved enum parameter {side}");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//
// Copyright (c) 2019-2026 Angouri.
// AngouriMath is licensed under MIT.
// Details: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.
// Website: https://am.angouri.org.
//

using AngouriMath.Extensions;
using Xunit;

namespace AngouriMath.Tests.Calculus
{
/// <summary>
/// A two-sided limit whose sides stopped at an indeterminate form with an infinity in it, 0 * floor(-oo) for
/// x floor(2/x) at 0, answered NaN, which says there is no limit, where each side is 2. Such a side is undecided,
/// so the limit is left unevaluated. Found by limitcheck; no issue was filed.
/// </summary>
[Trait("Area", "Calculus")]
public sealed class AnUndecidedSideLeavesTheLimitUnevaluatedTest
{
[Theory]
[InlineData("x * floor(2/x)")]
[InlineData("floor(1/x) * sin(x)")]
[InlineData("x^2 * floor(1/x^2)")]
[InlineData("x * ceil(1/x)")]
public void IsNotNaN(string input)
=> Assert.IsType<Entity.Limitf>(input.ToEntity().Limit("x", 0));

// A side that has no limit, or two sides that differ, still mean there is none: 0 / 0 everywhere, infinities
// in different directions, an oscillation, a jump.
[Theory]
[InlineData("(x - x) * sin(x) / ((x - x) * x)", "0")]
[InlineData("ln(-2) / ln(x)", "1")]
[InlineData("sin(1/x)", "0")]
[InlineData("floor(2/x)", "0")]
[InlineData("floor(x)", "0")]
public void IsStillNaN(string input, string destination)
=> Assert.Equal(MathS.NaN, input.ToEntity().Limit("x", destination));
}
}
Loading