From 4c2a22c4c23ecbeae091b7ed44bdc200c5372437 Mon Sep 17 00:00:00 2001 From: Rafael Vuijk Date: Mon, 2 Nov 2026 13:30:32 +0000 Subject: [PATCH] Put a step function at its one-sided limit before a one-sided limit is taken floor, ceil, round and sgn take only whole values, so one that has a limit from a side equals that limit on an interval on that side. Each is now put in as its one-sided limit wherever that limit is a whole number, before a one-sided limit is taken, and in the two-sided path each side gets its own. floor(x) is 0 on (0, 1), so floor(x)^x there is 0 to a positive power. It was read as the form 0^0 through e^(x ln(floor(x))), and answered 1: | | 2.6.0 | master | now | |---|---|---|---| | floor(x)^x at 0+ | 1 | 1 | 0 | | floor(x)^tanh(x) at 0+ | unevaluated | 1 | 0 | | floor(x)^x at 0 | unevaluated | 1 | NaN, 1 from the left and 0 from the right | limitcheck, 600 expressions at each of seeds 1, 7 and 11: the findings are master's at 1 and 7, and at 11 the floor(x)^tanh(x) row is gone. 11 more limits are answered across seeds 1 and 7, each agreeing with the values. Co-Authored-By: Claude Opus 5.5 --- BREAKING-CHANGES.md | 14 +++++++ .../Limits/Solvers/Solvers.Definition.cs | 29 ++++++++++++- .../Calculus/AStepFunctionNearItsLimitTest.cs | 42 +++++++++++++++++++ 3 files changed, 83 insertions(+), 2 deletions(-) create mode 100644 Sources/Tests/UnitTests/Calculus/AStepFunctionNearItsLimitTest.cs diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index ed8e67ccc..3224c830d 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -269,6 +269,20 @@ powers of `x` found it. |---|---|---| | `"(3x - 4) * sqrt(x) + 3 + 3x - 4x^2 = 0".Solve("x")` | four roots, one of them 1.3233…, which is not a root | the other three | +### A step function that has a limit from one side is that limit on that side + +**Silent: a limit of a power whose base is a `floor`, `ceil`, `round` or `sgn` that is 0 on the side approached is 0, +where it was 1.** These functions take only whole values, so where one has a limit from a side it equals that limit on +a whole interval on that side, and is now put in as it before the limit is taken. `floor(x)` is 0 on `(0, 1)`, so +`floor(x)^x` there is 0 to a positive power; it was read as the form `0^0` through `e^(x ln(floor(x)))` and answered 1. +Two-sided, each side is now taken with its own value, and `floor(x)^x` at 0 is 1 from the left and 0 from the right. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `"floor(x)^x".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `1` | `0` | +| `"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 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 2340a1746..5440235ef 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs @@ -101,6 +101,28 @@ internal static partial class LimitFunctional return null; } + /// + /// with each floor, ceil, round and sgn of + /// whose limit from is a whole number put + /// in as that number. Each takes only whole values, so where it has a limit it takes + /// that value on the whole of some interval on that side: the substitution is exact + /// there, and not an approximation. Without it, floor(x) ^ x at 0+ was read as + /// the form 0^0 and answered 1 through e ^ (x ln(floor(x))), where on (0, 1) it + /// is 0 to a positive power, which is 0. + /// + private static Entity WithStepsAtTheirLimits(Entity expr, Variable x, Entity dest, ApproachFrom side) + { + if (!expr.Nodes.Any(node => node is Floorf or Ceilf or Roundf or Signumf)) + return expr; + return expr.Replace(node => + node is Floorf or Ceilf or Roundf or Signumf + && node.ContainsNode(x) + && node.ComputeLimitDivideEtImpera(x, dest, side) is { } limit + && limit.Evaled is Integer whole + ? whole + : node); + } + /// /// The limit where the two one-sided limits agree, asked of each side through the /// whole of , or where either side @@ -278,6 +300,7 @@ internal static T WithinABudget(int limits, System.Func compute) if (side is ApproachFrom.Left or ApproachFrom.Right) { + expr = WithStepsAtTheirLimits(expr, x, dest, side); var oneSided = expr.ComputeLimitDivideEtImpera(x, dest, side); if (oneSided is { } found && (acceptNaN || found.Evaled != MathS.NaN)) return oneSided; @@ -377,8 +400,10 @@ internal static T WithinABudget(int limits, System.Func compute) // https://github.com/asc-community/AngouriMath/issues/1186 return atInfinity; } - else if (expr.ComputeLimitDivideEtImpera(x, dest, ApproachFrom.Left) is { } fromLeft - && expr.ComputeLimitDivideEtImpera(x, dest, ApproachFrom.Right) is { } fromRight) + // Each side with its own steps put in: floor(x) ^ x at 0 is 1 from the left and 0 + // from the right, and read bare both sides were 1. + else if (WithStepsAtTheirLimits(expr, x, dest, ApproachFrom.Left).ComputeLimitDivideEtImpera(x, dest, ApproachFrom.Left) is { } fromLeft + && WithStepsAtTheirLimits(expr, x, dest, ApproachFrom.Right).ComputeLimitDivideEtImpera(x, dest, ApproachFrom.Right) is { } fromRight) { if (fromLeft == fromRight && (acceptNaN || fromLeft.Evaled != MathS.NaN)) return fromLeft; diff --git a/Sources/Tests/UnitTests/Calculus/AStepFunctionNearItsLimitTest.cs b/Sources/Tests/UnitTests/Calculus/AStepFunctionNearItsLimitTest.cs new file mode 100644 index 000000000..f108f7e69 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AStepFunctionNearItsLimitTest.cs @@ -0,0 +1,42 @@ +// +// 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.Core; +using AngouriMath.Extensions; +using Xunit; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// floor, ceil, round and sgn take only whole values, so one that has a limit from a side is + /// equal to it on an interval on that side. floor(x) ^ x at 0+ is 0 to a positive power on (0, 1), and was + /// read as the form 0^0 and answered 1. No issue was filed; found by work/limitcheck at seed 11. + /// + [Trait("Area", "Calculus")] + public sealed class AStepFunctionNearItsLimitTest + { + [Theory] + [InlineData("floor(x)^x", "0")] + [InlineData("floor(x)^tanh(x)", "0")] + [InlineData("floor(x)^sin(x)", "0")] + [InlineData("ceil(x)^x", "1")] + [InlineData("x^sgn(x)", "0")] + public void FromTheRight(string expression, string limit) + => Assert.Equal(limit.ToEntity(), expression.ToEntity().Limit("x", 0, ApproachFrom.Right).Simplify()); + + // floor(x) is -1 on (-1, 0), and (-1)^x tends to 1. + [Fact] + public void FromTheLeft() + => Assert.Equal(1, "floor(x)^x".ToEntity().Limit("x", 0, ApproachFrom.Left).Simplify()); + + // 1 from the left and 0 from the right: no limit. + [Fact] + public void BothSidesDiffer() + => Assert.Equal(MathS.NaN, "floor(x)^x".ToEntity().Limit("x", 0).Simplify()); + } +}