From f4189bc9d6d7cde4fac4d0cb8b921438a86963ec Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 2 Nov 2026 18:58:52 +0000 Subject: [PATCH] Take agreeing sides for the two-sided limit where the expression is real on both Under the complex codomain, the default, two one-sided limits that agree were not taken for the two-sided one: x^x has a limit from the left only through its complex values, and no two-sided limit over the reals. (x^2)^x is real on both sides of 0 and tends to 1 from each, and its two-sided limit was NaN, the claim that there is none. They are now taken for it where the expression is real on both sides, decided structurally: every power to an exponent that is not whole, and every logarithm, has a base positive on both sides by SignNear. x^x is still NaN at 0. | | 2.6.0 and master | now | |---|---|---| | (x^2)^x at 0 | NaN | 1 | | (x*x)^(x - 1) at 0 | NaN | +oo | limitcheck, 600 expressions at each of seeds 1, 7 and 11: the findings are master's but for the (x*x)^(x - 1) row at seed 11, which is gone. Co-Authored-By: Claude Opus 5.5 --- BREAKING-CHANGES.md | 13 ++++++ .../Limits/Solvers/Solvers.Definition.cs | 43 ++++++++++++++++++- .../Calculus/ALimitRealOnBothSidesTest.cs | 37 ++++++++++++++++ 3 files changed, 91 insertions(+), 2 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/ALimitRealOnBothSidesTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index bc87bc009..9f3281629 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -296,6 +296,19 @@ Two-sided, each side is now taken with its own value, and `floor(x)^x` at 0 is 1 | `"floor(x)^tanh(x)".ToEntity().Limit("x", 0, ApproachFrom.Right)` | unevaluated | `0` | | `"floor(x)^x".ToEntity().Limit("x", 0)` | unevaluated | `NaN`, the sides differ | +### A two-sided limit of an expression real on both sides is where its sides agree + +**Silent: a two-sided limit that was `NaN` although both sides agree has their value, where the expression is real on +both sides.** Under the complex codomain, the default, two one-sided limits that agree were not taken to be the +two-sided limit: `x^x` has a limit from the left only through its complex values, and it has no two-sided limit over the +reals. Now they are taken to be where every power to an exponent that is not whole, and every logarithm, has a base +positive on both sides. `x^x` is still `NaN` at 0. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"(x^2)^x".ToEntity().Limit("x", 0)` | `NaN` | `1` | +| `"(x*x)^(x - 1)".ToEntity().Limit("x", 0)` | `NaN` | `+oo` | + ### 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 diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs index 5440235ef..5b05a2891 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs @@ -150,8 +150,8 @@ node is Floorf or Ceilf or Roundf or Signumf /// private static Entity? WhereBothSidesAgree(Entity expr, Variable x, Entity dest) { - if (MathS.Settings.Codomain.Value is not AngouriMath.Core.Domain.Real - || bothSidesDepth >= MaxBothSidesDepth) + if (bothSidesDepth >= MaxBothSidesDepth + || MathS.Settings.Codomain.Value is not AngouriMath.Core.Domain.Real && !IsRealOnBothSides(expr, x, dest)) return null; bothSidesDepth++; try @@ -168,6 +168,45 @@ node is Floorf or Ceilf or Roundf or Signumf finally { bothSidesDepth--; } } + /// + /// Whether is real on both sides of a real , + /// close enough to it, decided structurally: every power to an exponent that is not whole, + /// and every logarithm, has a base and an argument positive on both sides, by + /// . Where it is, its one-sided limits are limits of real values from + /// either side, and their agreeing is the two-sided limit under any codomain. x^x + /// is not, its base being negative on the left, and stays without one; (x^2)^x is, + /// and was NaN at 0 where each side is 1. Anything else unlisted is not real, which costs + /// coverage and never correctness. + /// + private static bool IsRealOnBothSides(Entity expr, Variable x, Entity dest) + { + if (dest.Evaled is not Real { IsFinite: true }) + return false; + return RealThere(expr); + bool RealThere(Entity node) + { + if (!node.ContainsNode(x)) + return node.Evaled is Real { IsFinite: true }; + return node switch + { + Variable variable => variable == x, + Sumf(var augend, var addend) => RealThere(augend) && RealThere(addend), + Minusf(var minuend, var subtrahend) => RealThere(minuend) && RealThere(subtrahend), + Mulf(var multiplier, var multiplicand) => RealThere(multiplier) && RealThere(multiplicand), + Divf(var dividend, var divisor) => RealThere(dividend) && RealThere(divisor), + Powf(var @base, Integer) => RealThere(@base), + Powf(var @base, var exponent) => RealThere(@base) && RealThere(exponent) && PositiveOnBothSides(@base), + Logf(var @base, var antilogarithm) => RealThere(@base) && RealThere(antilogarithm) + && PositiveOnBothSides(@base) && PositiveOnBothSides(antilogarithm), + _ => false + }; + } + bool PositiveOnBothSides(Entity node) + => !node.ContainsNode(x) + ? node.Evaled is Real { IsPositive: true } + : SignNear(node, x, dest, ApproachFrom.Left) is 1 && SignNear(node, x, dest, ApproachFrom.Right) is 1; + } + /// /// How deep may nest. Each one-sided limit it asks /// for may itself reach a two-sided limit of a subexpression, so without a bound the diff --git a/Sources/Tests/UnitTests/Calculus/ALimitRealOnBothSidesTest.cs b/Sources/Tests/UnitTests/Calculus/ALimitRealOnBothSidesTest.cs new file mode 100644 index 000000000..f19bcb6d2 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ALimitRealOnBothSidesTest.cs @@ -0,0 +1,37 @@ +// +// 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; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// Under the complex codomain two agreeing one-sided limits were not taken for the two-sided one, since x^x has a + /// limit from the left only through its complex values. (x^2)^x is real on both sides of 0 and tends to 1 from + /// each, and its two-sided limit was NaN, the claim that there is none. No issue was filed; found by work/limitcheck at + /// seed 11. + /// + [Trait("Area", "Calculus")] + public sealed class ALimitRealOnBothSidesTest + { + [Theory] + [InlineData("(x^2)^x", "1")] + [InlineData("(x*x)^(x - 1)", "+oo")] + [InlineData("(x^4)^(x - 1)", "+oo")] + public void IsWhereItsSidesAgree(string expression, string limit) + => Assert.Equal(limit.ToEntity().Evaled, expression.ToEntity().Limit("x", 0).Evaled); + + // Negative on the left, where the values are complex: still no two-sided limit. + [Theory] + [InlineData("x^x")] + [InlineData("(x/pi)^x")] + public void IsNotWhereOneSideIsComplex(string expression) + => Assert.Equal(MathS.NaN, expression.ToEntity().Limit("x", 0).Evaled); + } +}