From 42466125468501acde0d713e6177085b3cc235d3 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Wed, 4 Nov 2026 21:14:49 +0000 Subject: [PATCH] Ask a modulus's argument for its whole limit where its parts say nothing abs(x * ln(x)) at 0 was NaN, the claim that the limit does not exist, and unevaluated from each side, where it is 0 from both: it is real on both sides, and from the left |x| |ln|x| + i pi| tends to 0 too. The modulus took its argument's limit as the descent reads it, and the descent reads x * ln(x) as 0 * -oo, which is NaN. The same on 2.6.0. Where that reading has no value, the modulus now asks its argument's whole limit, one level deep and outside a limit budget as a quotient does, and takes the modulus of a value. x * ln(x) itself keeps the two-sided convention, and so does x^x; abs(x^x), real on both sides since |x^x| is |x|^x to the left of 0, is 1 there. limitcheck at DEPTH=3, 21 seeds 14-51, master against this: 20 more compared over 8 seeds, 10 NaNs where the values settle answered, each taking the modulus of something that holds a power like x^x at 0 -- abs(x^x), abs(sinh(x^x)), abs(x + 1 - x^x) -- and nothing moved to another value. Suite 18994 passed, AOT and the eight harnesses clean. Co-Authored-By: Claude Opus 5.5 --- BREAKING-CHANGES.md | 16 +++++++ .../Limits/Solvers/Limit.Classes.cs | 13 ++++-- .../AModulusAsksItsArgumentsLimitTest.cs | 46 +++++++++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/AModulusAsksItsArgumentsLimitTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 060459006..ff7259513 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -529,6 +529,22 @@ positive on both sides. `x^x` is still `NaN` at 0. | `"(x^2)^x".ToEntity().Limit("x", 0)` | `NaN` | `1` | | `"(x*x)^(x - 1)".ToEntity().Limit("x", 0)` | `NaN` | `+oo` | +### A limit of a modulus asks its argument's whole limit + +**Silent: `abs(x * ln(x))` at 0 is `0`, where it was `NaN`, the claim that the limit does not exist.** It is real on both +sides. From the left, |x| |ln|x| + i pi| tends to 0 too. The modulus took its argument's limit as the descent reads it, +and the descent reads x * ln(x) as 0 * -oo, which is NaN. Where that reading has no value, the modulus now asks its +argument's whole limit and takes the modulus of a value. `x * ln(x)` itself is still `NaN` two-sided, and so is +`x^x`. `abs(x^x)` is real on both sides, since |x^x| is |x|^x to the left of 0, and it is now `1` there. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"abs(x * ln(x))".ToEntity().Limit("x", 0)` | `NaN` | `0` | +| `"abs(x * ln(x))".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `limitright(abs(x * ln(x)), x, 0)` | `0` | +| `"abs(sin(x) * ln(x))".ToEntity().Limit("x", 0)` | `NaN` | `0` | +| `"abs(x^2 * ln(x))".ToEntity().Limit("x", 0)` | `NaN` | `0` | +| `"abs(x^x)".ToEntity().Limit("x", 0)` | `NaN` | `1` | + ### A factor continuous at the point is taken out before l'Hopital's rule **`sin(x)^tanh(x)` at 0 from the right is `1`, where it was left unevaluated.** Read through its exponent, the power is diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs index e0f889f54..d375d50ee 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs @@ -553,9 +553,16 @@ partial record Absf Mulf(var multiplier, var multiplicand) => multiplier.Abs() * multiplicand.Abs(), _ => null }; - return split?.ComputeLimitDivideEtImpera(x, dist, side) is { } bySplitting && bySplitting.Evaled != MathS.NaN - ? bySplitting - : direct; + if (split?.ComputeLimitDivideEtImpera(x, dist, side) is { } bySplitting && bySplitting.Evaled != MathS.NaN) + return bySplitting; + // And the argument's whole limit, where the descent reads a form off its parts: x * ln(x) is + // 0 * -oo there, which is NaN, so abs(x * ln(x)) at 0 had no limit from either side, and the + // two-sided limit was NaN, where it is 0 -- from the left too, |x| |ln|x| + i pi| tending to 0. + // The modulus of a value is taken; x * ln(x) itself keeps the two-sided convention. + if (direct?.Evaled == MathS.NaN + && AsTheWholeLimit(Argument, x, dist, side) is { } whole && whole.Evaled is Number { IsNaN: false }) + return whole.Abs().InnerSimplified; + return direct; } } diff --git a/Sources/Tests/UnitTests/Calculus/AModulusAsksItsArgumentsLimitTest.cs b/Sources/Tests/UnitTests/Calculus/AModulusAsksItsArgumentsLimitTest.cs new file mode 100644 index 000000000..f133d5e8e --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AModulusAsksItsArgumentsLimitTest.cs @@ -0,0 +1,46 @@ +// +// 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.Core; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// abs(x * ln(x)) at 0 was NaN -- the claim that the limit does not exist -- and unevaluated from each side, where + /// it is 0 from both: real on both sides, and from the left |x| |ln|x| + i pi| tends to 0 too. The modulus took its + /// argument's limit as the descent reads it, and the descent reads x * ln(x) as 0 * -oo, which is NaN. The modulus now + /// asks its argument's whole limit where that reading has no value, and takes the modulus of a value. + /// x * ln(x) itself is still NaN two-sided, having no real values to the left of 0. The same on 2.6.0. Found + /// while checking the modulus of a quotient; no issue was filed. + /// + [Trait("Area", "Calculus")] + public sealed class AModulusAsksItsArgumentsLimitTest + { + [Theory] + [InlineData("abs(x * ln(x))")] + [InlineData("abs(sin(x) * ln(x))")] + [InlineData("abs(x^2 * ln(x))")] + public void IsZeroFromEachSideAndBoth(string input) + { + foreach (var side in new[] { ApproachFrom.Left, ApproachFrom.Right, ApproachFrom.BothSides }) + Assert.Equal(MathS.FromString("0"), MathS.FromString(input).Limit("x", 0, side)); + } + + // Real on both sides, since |x^x| is |x|^x to the left of 0; x^x itself is not, and is NaN below. + [Fact] + public void AModulusOfXToTheXIsOne() + => Assert.Equal(MathS.FromString("1"), MathS.FromString("abs(x^x)").Limit("x", 0)); + + [Theory] + [InlineData("x * ln(x)")] + [InlineData("x^x")] + [InlineData("abs(sin(1/x))")] + public void HasNoTwoSidedLimit(string input) + => Assert.Equal(MathS.NaN, MathS.FromString(input).Limit("x", 0)); + } +}