From b57d1d317ce683b32832ece357a078cebcdc6434 Mon Sep 17 00:00:00 2001 From: Hadrian Tang Date: Wed, 14 Jan 2026 02:13:42 +0800 Subject: [PATCH] Fix stack overflow for nonexistent two-sided limits --- .../Continuous/Limits/Limit.Definition.cs | 3 +-- .../Limits/Solvers/Solvers.Definition.cs | 10 ++++++---- .../Continuous/Limits/Transformations.cs | 4 ++-- Sources/Tests/UnitTests/Calculus/LimitTest.cs | 19 +++++++++++++++++++ .../UnitTests/PatternsTest/SimplifyTest.cs | 6 ++++++ 5 files changed, 34 insertions(+), 8 deletions(-) diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Limit.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Limit.Definition.cs index 7fb69fc30..8dc23ea0d 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Limit.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Limit.Definition.cs @@ -60,8 +60,7 @@ partial record Entity public Entity Limit(Variable x, Entity destination, ApproachFrom side) { var res = ComputeLimit(this, x, destination, side); - if (res is null || res == MathS.NaN) - return new Limitf(this, x, destination, side); + if (res is null) return new Limitf(this, x, destination, side); return res.InnerSimplified; } diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs index 7766b4a50..c72860e0f 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs @@ -72,13 +72,15 @@ private static Entity ExpandLogarithm(Entity expr) return fromLeft; if (ExpressionNumerical.AreEqual(fromLeft, fromRight) && (acceptNaN || fromLeft.Evaled != MathS.NaN)) return fromLeft; - expr = ApplylHopitalRule(expr, x, dest); - return ComputeLimit(expr, x, dest, acceptNaN: true); + var lhopital = ApplylHopitalRule(expr, x, dest); + if (lhopital != null) return ComputeLimit(lhopital, x, dest, acceptNaN: true); + else return MathS.NaN; // A two-sided limit cannot exist if the limit from left and right don't match. } else { - expr = ApplylHopitalRule(expr, x, dest); - return ComputeLimit(expr, x, dest); + var lhopital = ApplylHopitalRule(expr, x, dest); + if (lhopital != null) return ComputeLimit(lhopital, x, dest); + else return null; } } throw new AngouriBugException($"Unresolved enum parameter {side}"); diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs index 1210099ea..dc3bca700 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs @@ -55,7 +55,7 @@ private static bool IsInfiniteNode(Entity expr) private static bool IsFiniteNode(Entity expr) => !IsInfiniteNode(expr) && expr != MathS.NaN; - private static Entity ApplylHopitalRule(Entity expr, Variable x, Entity dest) + private static Entity? ApplylHopitalRule(Entity expr, Variable x, Entity dest) { if (expr is Divf(var num, var den)) if (EvalAssumingContinuous(num.Limit(x, dest)) is var numLimit && EvalAssumingContinuous(den.Limit(x, dest)) is var denLimit) @@ -69,7 +69,7 @@ private static Entity ApplylHopitalRule(Entity expr, Variable x, Entity dest) if (ComputeLimit(applied, x, dest) is { } resLim) return resLim; } - return expr; + return null; } private static Entity ApplyTrivialTransformations(Entity expr, Variable x, Entity dest, Func transformation) diff --git a/Sources/Tests/UnitTests/Calculus/LimitTest.cs b/Sources/Tests/UnitTests/Calculus/LimitTest.cs index 0ffdf5036..afaf4860a 100644 --- a/Sources/Tests/UnitTests/Calculus/LimitTest.cs +++ b/Sources/Tests/UnitTests/Calculus/LimitTest.cs @@ -130,5 +130,24 @@ public void TestComplicated() Assert.NotNull(limit); Assert.Equal("sqrt(a / c * 3 / sin(a / c) + sin(d))", limit?.Stringize()); } + [Theory] + [InlineData("limit(1/x,x,0)")] + [InlineData("limit(1/x^3,x,0)")] + [InlineData("limit(x^x,x,0)")] + public void TestNoLimit(string input) // a two-sided limit does not exist + { + var limit = input.ToEntity(); + Assert.Equal(MathS.NaN, limit.InnerSimplified); + Assert.Equal(MathS.NaN, limit.Evaled); + } + [Theory] + [InlineData("limit(apply(f, x),x,-1)")] + [InlineData("limit(x!,x,-1)")] + public void TestCantSolve(string input) // this is different from no limit + { + var limit = input.ToEntity(); + Assert.Equal(input, limit.InnerSimplified); + Assert.Equal(input, limit.Evaled); + } } } \ No newline at end of file diff --git a/Sources/Tests/UnitTests/PatternsTest/SimplifyTest.cs b/Sources/Tests/UnitTests/PatternsTest/SimplifyTest.cs index f448e188a..2c79c0c19 100644 --- a/Sources/Tests/UnitTests/PatternsTest/SimplifyTest.cs +++ b/Sources/Tests/UnitTests/PatternsTest/SimplifyTest.cs @@ -161,6 +161,12 @@ [Fact] public void BigSimple1() => AssertSimplifyToString( [InlineData("ln(a) - ln(b)", "ln(a / b)")] [InlineData("log(2, a) + ln(b)", "log(2, a) + ln(b)")] public void PowerRulesTest(string input, string output) => AssertSimplifyToString(input, output); + + [Theory] + [InlineData("a/a", "1 provided not a = 0")] + [InlineData("(-a)/a", "-1 provided not a = 0")] + [InlineData("a/(-a)", "-1 provided not a = 0")] + public void MakeProvided(string input, string output) => AssertSimplifyToString(input, output, 1); } }