diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 88b744885..a82d37cbd 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -313,13 +313,16 @@ powers of `x` found it.
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.
+unevaluated too. An argument real near the destination is read as before. The value at the destination is not read
+off an expression holding such a step either, which is how `x / floor(arcsin(x))` was answered.
| 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 |
+| `limitright(x / floor(arcsin(x)), x, 1)` | `1`, where it is `(1 + i)/2` | unevaluated |
+| `limitleft(x / floor(arcsin(x)), x, -1)` | `1/2`, where it is `(2 - i)/5` | unevaluated |
### A step function that has a limit from one side is that limit on that side
diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs
index dbce3eb2c..ad93979a9 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs
@@ -548,14 +548,11 @@ bool IsRealJustOff()
ApproachFrom.Right => new[] { 1 },
_ => new[] { -1, 1 },
};
+ if (dist == Real.PositiveInfinity || dist == Real.NegativeInfinity)
+ return AtALargeDecimal(dist == Real.PositiveInfinity ? argument : argument.Substitute(x, -x), x) is Real;
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)
+ if (argument.Substitute(x, dist + Rational.Create(offset, 1000000)).Evaled is not Real)
return false;
- }
return true;
}
diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs
index d31fc920d..e06b2af8b 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Solvers.cs
@@ -50,6 +50,13 @@ internal static class LimitSolvers
// https://github.com/asc-community/AngouriMath/issues/2266
if (res.Nodes.Any(SitsOnAJump))
return null;
+ // Nor where a step's argument is real at the destination and complex just short of it, which the node
+ // declines too (LimitOfAStepFunction): x / floor(arcsin(x)) at 1 from the right is x / floor(arcsin(1 + 1/t))
+ // here, whose arcsin(1) = pi/2 has the floor 1, where the floor all the way in is 1 - i, and the quotient was
+ // answered 1 where it is (1 + i)/2.
+ if (expr.Nodes.Any(node => node is Floorf or Ceilf or Roundf && node.ContainsNode(x)
+ && IsComplexJustShortOfInfinity(node.DirectChildren[0], x)))
+ return null;
if (res.Evaled is Complex limit)
{
MultithreadingFunctional.ExitIfCancelled();
@@ -77,6 +84,11 @@ internal static class LimitSolvers
return null;
}
+ /// Whether is real at +oo and not at a large finite point short of it.
+ private static bool IsComplexJustShortOfInfinity(Entity argument, Variable x)
+ => argument.Substitute(x, Infinity).Evaled is Real { IsNaN: false }
+ && LimitFunctional.AtALargeDecimal(argument, x) is Complex and not Real;
+
///
/// Whether is a step function whose argument evaluates to one of its jumps: an integer for
/// floor and ceil, a half-integer for round, 0 for sgn. Taken on the real part, and on the
diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
index b74fc768a..e2d124163 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Transformations.cs
@@ -1112,6 +1112,15 @@ internal static bool IsDeterminate(Entity combined, Variable x)
return null;
}
+ ///
+ /// at the decimal 10^6 with the downcasting off, so that a factorial or a power of the
+ /// variable is taken in decimals: at the integer 10^6, 1/x! built a number of five and a half million digits, and
+ /// asking whether floor(1/x!) was real short of +oo took four seconds.
+ ///
+ internal static Entity AtALargeDecimal(Entity expr, Variable x)
+ => MathS.Settings.DowncastingEnabled.As(false,
+ () => expr.Substitute(x, Real.Create(PeterO.Numbers.EDecimal.FromString("1E6"))).Evaled);
+
///
/// The sign an expression keeps throughout a punctured one-sided neighbourhood of the
/// destination, or where it cannot be read off.
diff --git a/Sources/Tests/UnitTests/Calculus/ASubstitutionDeclinesAStepItsNodeDeclinesTest.cs b/Sources/Tests/UnitTests/Calculus/ASubstitutionDeclinesAStepItsNodeDeclinesTest.cs
new file mode 100644
index 000000000..165a7ffca
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ASubstitutionDeclinesAStepItsNodeDeclinesTest.cs
@@ -0,0 +1,50 @@
+//
+// 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 System;
+using System.Threading.Tasks;
+using AngouriMath.Core;
+using Xunit;
+using static AngouriMath.Entity;
+
+namespace AngouriMath.Tests.Calculus
+{
+ ///
+ /// A floor or a ceil whose argument is real at the destination and complex just off it declines its own limit since
+ /// #2355, and the substitution that reads an expression's value at the destination answered the expression it is in
+ /// anyway: x / floor(arcsin(x)) at 1 from the right was 1, the value at 1, where the floor all the way in
+ /// is 1 - i and the quotient (1 + i)/2. And #2355 asked whether such an argument was real at the integer
+ /// 10^6 on the way to an infinity, which for 1/x! built a number of five and a half million digits. Found by
+ /// limitcheck at DEPTH=3, SEED=19; no issue was filed.
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ASubstitutionDeclinesAStepItsNodeDeclinesTest
+ {
+ [Theory]
+ [InlineData("x / floor(arcsin(x))", "1", ApproachFrom.Right)]
+ [InlineData("x / floor(arcsin(x))", "-1", ApproachFrom.Left)]
+ public void IsNotTheValueAtTheDestination(string input, string destination, ApproachFrom side)
+ => Assert.IsType(MathS.FromString(input).Limit("x", destination, side));
+
+ [Theory]
+ [InlineData("x / floor(x + 1/2)", "2", ApproachFrom.Left, "1")]
+ [InlineData("floor(arctan(x))", "+oo", ApproachFrom.BothSides, "1")]
+ [InlineData("floor(arctan(x))", "-oo", ApproachFrom.BothSides, "-2")]
+ [InlineData("floor(x^x)", "2", ApproachFrom.Left, "3")]
+ public void ARealStepIsReadAsBefore(string input, string destination, ApproachFrom side, string expected)
+ => Assert.Equal(MathS.FromString(expected), MathS.FromString(input).Limit("x", destination, side));
+
+ // The cap is far from both: the limit takes about 0.2 s, and took 3.9 s building 10^6!.
+ [Fact]
+ public void AFactorialOnTheWayToInfinityIsNotBuilt()
+ {
+ var task = Task.Factory.StartNew(() => MathS.FromString("floor(1/x!)").Limit("x", "+oo", ApproachFrom.BothSides),
+ TaskCreationOptions.LongRunning);
+ Assert.True(task.Wait(TimeSpan.FromSeconds(2)), "the limit of floor(1/x!) at +oo did not finish in two seconds");
+ }
+ }
+}