From 414f1fa4e53dc64e8ae65593e6d2009d38ba2e64 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Fri, 30 Oct 2026 20:37:40 +0000 Subject: [PATCH] A limit of a logarithm that is not decided is left unevaluated limit(ln(2 + sin(x)), x, +oo) overflowed the stack. A limit node simplifies its expression evaluated, in which ln(f) is log(2.718..., f). ExpandLogarithm wrote every base but the symbol e as a quotient, ln(f) / ln(2.718...), and the first remarkable limit asks whether its numerator vanishes through a limit node of ln(f), which is evaluated, and so asks the same question again. Where the limit of ln(f) is decided that ends; where it is not, it did not. ExpandLogarithm now reads a base that is e evaluated as e, and writes the logarithm as ln(f) rather than as a quotient. UndecidedLogarithmLimitTest takes five limits of logarithms of oscillating arguments, which must come back unevaluated or NaN, and three decided ones. On master the test host crashes with a stack overflow on the first five. The suite, the trio, F#, AOT and the eight harnesses pass. Fixes #2273. Co-Authored-By: Claude Opus 5.5 (1M context) --- BREAKING-CHANGES.md | 13 ++++++ Sources/.editorconfig | 3 ++ .../Limits/Solvers/Solvers.Definition.cs | 12 ++++++ .../Calculus/UndecidedLogarithmLimitTest.cs | 43 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/UndecidedLogarithmLimitTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 6e558804a..9c90aea54 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -121,6 +121,19 @@ carried it once combined: a second derivative of `1/sqrt(x^2 + 13)` stated `not | `"(a provided b > 0) + (c provided b > 0)".ToEntity().InnerSimplified` | `a + c provided b > 0 and b > 0` | `a + c provided b > 0` | | `MathS.Equations("sqrt(y) - x", "y - a").Solve("x", "y")` | `[[sqrt(a), (sqrt(a)) ^ 2], [-sqrt(a), (-sqrt(a)) ^ 2]]` | `[[sqrt(a) provided C(sqrt(a)), (sqrt(a)) ^ 2 provided C(sqrt(a))], [-sqrt(a) provided C(-sqrt(a)), (-sqrt(a)) ^ 2 provided C(-sqrt(a))]]`, `C(v)` being `v = 0 or -1/2 * pi < arg(v) and arg(v) <= 1/2 * pi` | +### A limit of a logarithm that is not decided is left unevaluated + +**Not silent: what overflowed the stack now returns.** A limit of a logarithm whose argument has no limit, as +`ln(2 + sin(x))` at `+oo`, overflowed the stack and took the process down. A limit node simplifies its expression evaluated, +where `ln(f)` is `log(2.718..., f)`, and that base was not read as `e`: the quotient `ln(f) / ln(2.718...)` it was rewritten +as asked the limit of `ln(f)` again, through a limit node of its own +([#2273](https://github.com/asc-community/AngouriMath/issues/2273)). + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"limit(ln(2 + sin(x)), x, +oo)".ToEntity().Simplify()` | stack overflow | `limit(ln(2 + sin(x)), x, +oo)` | +| `"limitright(ln(sin(1/x) + 2), x, 0)".ToEntity().Simplify()` | stack overflow | `limitright(ln(2 + sin(1 / x)), x, 0)` | + ### Primality is decided by Miller–Rabin and Lucas–Lehmer, and finishes **Silent: a membership in `PP` left open past the machine word is decided where a test proves it, and one that did diff --git a/Sources/.editorconfig b/Sources/.editorconfig index e6a04a3ac..307f85609 100644 --- a/Sources/.editorconfig +++ b/Sources/.editorconfig @@ -114,6 +114,9 @@ file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed [Tests/UnitTests/Calculus/LimitsTheIssueListedTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n +[Tests/UnitTests/Calculus/UndecidedLogarithmLimitTest.cs] +file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n + [Tests/UnitTests/Calculus/PowerSubstitutionIntegralTest.cs] file_header_template=\nCopyright (c) 2019-2026 Angouri.\nAngouriMath is licensed under MIT.\nDetails: https://github.com/asc-community/AngouriMath/blob/master/LICENSE.md.\nWebsite: https://am.angouri.org.\n diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs index 9fba3b24c..2340a1746 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs @@ -156,9 +156,21 @@ internal static partial class LimitFunctional [System.ThreadStatic] private static int bothSidesDepth; + /// + /// A logarithm to a base other than e as a quotient of natural logarithms. + /// + /// + /// A base that is e evaluated is e. The limit of a limit node's evaluation is taken of its + /// expression evaluated, in which ln(f) is log(2.718..., f), and as a quotient that is + /// ln(f) / ln(2.718...), whose first remarkable limit asks the limit of ln(f) through a + /// limit node of its own -- which is evaluated, and so asks the same question again. Where + /// the limit of ln(f) is not decided, that did not end, and the stack overflowed. + /// #2273 + /// private static Entity ExpandLogarithm(Entity expr) => expr switch { + Logf(var @base, var antilog) when @base == MathS.e.Evaled => MathS.Ln(antilog), Logf(var @base, var antilog) when @base != MathS.e => MathS.Ln(antilog) / MathS.Ln(@base), _ => expr }; diff --git a/Sources/Tests/UnitTests/Calculus/UndecidedLogarithmLimitTest.cs b/Sources/Tests/UnitTests/Calculus/UndecidedLogarithmLimitTest.cs new file mode 100644 index 000000000..eac6d99cf --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/UndecidedLogarithmLimitTest.cs @@ -0,0 +1,43 @@ +// +// 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 +{ + /// + /// A limit of a logarithm that is not decided is left unevaluated, where it overflowed the stack. A limit node simplifies + /// its expression evaluated, in which ln(f) is log(2.718..., f), the base was not read as e, and the quotient + /// ln(f) / ln(2.718...) it was rewritten as asked the limit of ln(f) again through a limit node of its own. None of these + /// has a limit, as the argument oscillates, so NaN would be right as well. + /// #2273 + /// + [Trait("Area", "Calculus")] + public sealed class UndecidedLogarithmLimitTest + { + [Theory] + [InlineData("limitright(ln(sin(1/x) + 2), x, 0)")] + [InlineData("limit(ln(sin(1/x) + 2), x, 0)")] + [InlineData("limitleft(ln(2 + cos(1/x)), x, 0)")] + [InlineData("limit(ln(2 + sin(x)), x, +oo)")] + [InlineData("limit(sqrt(x) * ln(2 + sin(1/x)), x, 0)")] + public void ItIsLeftUnevaluated(string limit) + { + var answer = limit.ToEntity().Simplify(); + Assert.True(answer is Entity.Limitf || answer == MathS.NaN, $"{limit} is {answer}"); + } + + /// A logarithm whose limit is decided is answered as before. + [Theory] + [InlineData("limit(ln(x) / ln(2), x, 4)", "2")] + [InlineData("limit(ln(1 + x) / x, x, 0)", "1")] + [InlineData("limitright(ln(x) * x, x, 0)", "0")] + public void ADecidedOneIsAnswered(string limit, string value) + => Assert.Equal(value.ToEntity(), limit.ToEntity().Simplify()); + } +}