From d690f705ea3d55a45c0fb85adf5b1db7ea4268ca Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Thu, 12 Nov 2026 07:05:30 +0000 Subject: [PATCH] Read a tangent, an error function and a step function as real when a limit's sides are compared A two-sided limit compares its sides where the expression is real on both, and the structural reading of that knew a sine, a cosine and an arctangent but not a tangent, an arccotangent, the error functions or the step functions, all real for a real argument. Where the descent read a side as 0 * oo the limit was NaN, which says it does not exist: (pi^2 + 1/(2x)) erf(x) at 0 is 1/sqrt(pi) from each side. Co-Authored-By: Claude Opus 5.5 --- BREAKING-CHANGES.md | 13 ++++++ .../Limits/Solvers/Solvers.Definition.cs | 11 +++++ .../ARealFunctionAgreesOnBothSidesTest.cs | 41 +++++++++++++++++++ 3 files changed, 65 insertions(+) create mode 100644 Sources/Tests/UnitTests/Calculus/ARealFunctionAgreesOnBothSidesTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 829994cba..05db67a7f 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -164,6 +164,19 @@ Found by limitcheck; no issue was filed. | `"floor(tanh(1 / x))".ToEntity().Limit("x", 0, ApproachFrom.Right)` | unevaluated | `0` | | `"floor(tanh(1 / x))".ToEntity().Limit("x", 0)` | unevaluated | `NaN`: the left side is `-1` | +### A two-sided limit through a real function of a real argument is the sides' common value + +**Silent: a tangent, an arccotangent, an error function and a step function of a real argument are read as real when +the two sides of a two-sided limit are compared**, as a sine, a cosine and an arctangent already were. Their sides were +not compared at all, and where the descent read a side as `0 * oo`, the limit was `NaN`, which says it does not exist. +Since 2.6.0, master answered `NaN` for these. Found by limitcheck; no issue was filed. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"(pi^2 + (1/2) / x) * erf(x)".ToEntity().Limit("x", 0)` | `limit((pi * pi + 1/2 / x) * erf(x), x, 0)` | `1 / sqrt(pi)` | +| `"(1 + 1/x) * erf(x)".ToEntity().Limit("x", 0)` | `limit((1 + 1 / x) * erf(x), x, 0)` | `2 * 1 / sqrt(pi)` | +| `"(abs(x)^(x/pi))^sgn(x)".ToEntity().Limit("x", 0)` | `limit((abs(x) ^ (x / pi)) ^ sgn(x), x, 0)` | `1` | + ### A logarithm of a part tending to 1 is read as the part less 1 in a limit **Silent: a factor `ln(u)` whose antilogarithm tends to 1 is read as `u - 1`, as `sin(f)` is read as `f` where `f` diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs index cd4651035..66e0ec347 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs @@ -246,6 +246,17 @@ bool RealThere(Entity node) Arctanf(var argument) => RealThere(argument), Arcsinf(var argument) => RealThere(argument) && StrictlyInsideTheUnitInterval(argument), Arccosf(var argument) => RealThere(argument) && StrictlyInsideTheUnitInterval(argument), + // So are a tangent and an arccotangent, the error functions, and the step functions, of a real + // argument. (pi^2 + 1/(2x)) erf(x) at 0 was NaN, where each side is 1/sqrt(pi). + Tanf(var argument) => RealThere(argument), + Arccotanf(var argument) => RealThere(argument), + Erff(var argument) => RealThere(argument), + Erfcf(var argument) => RealThere(argument), + Erfif(var argument) => RealThere(argument), + Floorf(var argument) => RealThere(argument), + Ceilf(var argument) => RealThere(argument), + Roundf(var argument) => RealThere(argument), + Signumf(var argument) => RealThere(argument), _ => false }; } diff --git a/Sources/Tests/UnitTests/Calculus/ARealFunctionAgreesOnBothSidesTest.cs b/Sources/Tests/UnitTests/Calculus/ARealFunctionAgreesOnBothSidesTest.cs new file mode 100644 index 000000000..ef7b05cc4 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/ARealFunctionAgreesOnBothSidesTest.cs @@ -0,0 +1,41 @@ +// +// 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 two-sided limit compares its sides only where the expression is known to be real on both, and an error function + /// or a step function of a real argument was not known to be. (pi^2 + 1/(2x)) erf(x) at 0 was NaN, which says there + /// is no limit, where each side is 1/sqrt(pi). Found by limitcheck; no issue was filed. + /// + [Trait("Area", "Calculus")] + public sealed class ARealFunctionAgreesOnBothSidesTest + { + [Theory] + [InlineData("(pi^2 + (1/2) / x) * erf(x)", "1 / sqrt(pi)")] + [InlineData("(1 + 1/x) * erf(x)", "2 / sqrt(pi)")] + [InlineData("(abs(x)^(x/pi))^sgn(x)", "1")] + public void TheSidesAgree(string input, string expected) + { + var limit = input.ToEntity().Limit("x", 0); + Assert.False(limit.ContainsNode("x"), $"the limit of {input} was not taken: {limit}"); + var difference = (limit - expected.ToEntity()).EvalNumerical(); + Assert.True(difference is Entity.Number.Real { IsFinite: true } real && real.Abs() < 1e-20, $"the limit of {input} is {limit}, not {expected}"); + } + + // Where the sides differ there is still no limit. + [Theory] + [InlineData("floor(x)")] + [InlineData("sgn(x)")] + [InlineData("erfc(x) / x")] + public void TheSidesStillDiffer(string input) + => Assert.Equal(MathS.NaN, input.ToEntity().Limit("x", 0)); + } +}