diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md index 8c7c08d9f..88b744885 100644 --- a/BREAKING-CHANGES.md +++ b/BREAKING-CHANGES.md @@ -307,6 +307,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 floor or a ceil of a complex argument near a real limit is not the step of the limit + +**Silent: a different answer.** The limit of a floor or a ceil read its argument as real along the path. `4^((-1)^x)` +at 1 from the right tends to 1/4 from below the real line, where every floor is `-i`, and the limit was the floor of 1/4, +`0`. An argument that is complex just off the destination now has its imaginary part's side asked, and where that is +not decided the limit is left unevaluated. Here it is not decided, so the side whose answer was right, the left, is left +unevaluated too. An argument real near the destination is read as before. + +| Input | Was (2.6.0) | Now | +|---|---|---| +| `limitright(floor(4^((-1)^x)), x, 1)` | `0` | unevaluated | +| `limitleft(ceil(4^((-1)^x)), x, 1)` | `1` | unevaluated | +| `limitleft(floor(4^((-1)^x)), x, 1)` | `0` | unevaluated | + ### 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, diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs index a3ea02ba4..dbce3eb2c 100644 --- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs +++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs @@ -498,20 +498,66 @@ partial record Absf // genuinely complex limit has an imaginary part that can arrive at an integer from // one side or the other. // - // Like the Absf and Signumf overrides above, this reads the argument as real-valued - // along the path. An argument that is complex near the destination and real at it - // can still be answered here when it should not be; that is the assumption those - // two already make, and narrowing it wants a way to decide realness that this - // library does not have yet -- https://github.com/asc-community/AngouriMath/issues/721. + // The Absf and Signumf overrides above read the argument as real-valued along the path. + // This one asks: an argument that is complex near the destination and real at it is + // read below by the side its imaginary part arrives from. if (value is not Complex { RealPart: var real, ImaginaryPart: var imaginary }) return null; + // A real limit of an argument that is not real near the destination has an imaginary + // part tending to 0, which is a jump of its own: floor(4^((-1)^x)) at 1 from the right + // tends to 1/4 from below the real line, where every value's floor is -i, and the + // floor of the limit, 0, was the answer. Where the argument is real near the + // destination -- every argument this read as real before, sqrt(x) at 4 among them -- + // nothing is added; where it is not, the side the imaginary part arrives from decides + // the step, and where that is not decided the limit is declined. + Entity imaginaryStep = Integer.Zero; + if (value is Real) + { + if (ImaginaryStep() is not { } step) + return null; + imaginaryStep = step; + } + Entity WithTheImaginaryStep(Entity realAnswer) + => imaginaryStep is Integer { IsZero: true } ? realAnswer : realAnswer + imaginaryStep * MathS.i; if (SitsOnAJump(real)) return value is Integer jump && SignNear(argument - jump, x, dist, side) is { } arrivesFrom - ? atAJump(jump, arrivesFrom) + ? WithTheImaginaryStep(atAJump(jump, arrivesFrom)) : null; if (value is not Real && SitsOnAJump(imaginary)) return null; - return rebuild(limit); + return WithTheImaginaryStep(rebuild(limit)); + + // Real near the destination where it is real at a point just off it on each side + // approached: realness is not decided for every shape -- x^x is outside what + // TreeAnalyzer lists -- and a sample only ever sends the limit to the question of the + // imaginary part's side, or to declining, never to an answer of its own. + Entity? ImaginaryStep() + { + if (Functions.TreeAnalyzer.IsRealValued(argument, x) || IsRealJustOff()) + return Integer.Zero; + return SignNear(MathS.Im(argument), x, dist, side) is { } arrivesFrom && arrivesFrom != 0 + ? atAJump(Integer.Zero, arrivesFrom) + : null; + } + + bool IsRealJustOff() + { + var offsets = side switch + { + ApproachFrom.Left => new[] { -1 }, + ApproachFrom.Right => new[] { 1 }, + _ => new[] { -1, 1 }, + }; + foreach (var offset in offsets) + { + Entity at = dist == Real.PositiveInfinity ? Integer.Create(1000000) + : dist == Real.NegativeInfinity ? Integer.Create(-1000000) + : dist + Rational.Create(offset, 1000000); + if (argument.Substitute(x, at).Evaled is not Real) + return false; + } + return true; + } static bool SitsOnAJump(Real part) => part is Integer || part.EDecimal.CompareTo(part.EDecimal.Floor()) == 0; diff --git a/Sources/Tests/UnitTests/Calculus/AStepOfAComplexArgumentNearARealLimitTest.cs b/Sources/Tests/UnitTests/Calculus/AStepOfAComplexArgumentNearARealLimitTest.cs new file mode 100644 index 000000000..c7a55f748 --- /dev/null +++ b/Sources/Tests/UnitTests/Calculus/AStepOfAComplexArgumentNearARealLimitTest.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.Core; +using Xunit; +using static AngouriMath.Entity; + +namespace AngouriMath.Tests.Calculus +{ + /// + /// The limit of a floor or a ceil read its argument as real along the path. 4^((-1)^x) at 1 from the right + /// tends to 1/4 from below the real line, so that every floor near it is -i, and the limit was the floor of + /// 1/4, 0. An argument that is complex just off the destination now has its imaginary part's side asked, and where + /// that is not decided the limit is declined. An argument real there is read as before. Found by limitcheck at + /// DEPTH=3; no issue was filed. + /// + [Trait("Area", "Calculus")] + public sealed class AStepOfAComplexArgumentNearARealLimitTest + { + [Theory] + [InlineData("floor(4^((-1)^x))", "1", ApproachFrom.Right)] + [InlineData("floor(4^((-1)^x))", "-1", ApproachFrom.Right)] + [InlineData("ceil(4^((-1)^x))", "1", ApproachFrom.Left)] + public void IsNotTheStepOfTheLimit(string input, string destination, ApproachFrom side) + => Assert.IsType(MathS.FromString(input).Limit("x", destination, side)); + + [Theory] + [InlineData("floor(sqrt(x))", "4", ApproachFrom.Left, "1")] + [InlineData("floor(sqrt(x))", "4", ApproachFrom.Right, "2")] + [InlineData("floor(x^x)", "2", ApproachFrom.Left, "3")] + [InlineData("floor(x^x)", "1", ApproachFrom.Left, "0")] + [InlineData("floor(ln(x))", "1", ApproachFrom.Left, "-1")] + [InlineData("ceil(x^(1/3))", "1", ApproachFrom.Right, "2")] + public void ARealArgumentIsReadAsBefore(string input, string destination, ApproachFrom side, string expected) + => Assert.Equal(MathS.FromString(expected), MathS.FromString(input).Limit("x", destination, side)); + } +}