diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 9bab3fdbe..a603de820 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -240,6 +240,24 @@ compiled `0^(-1/2)` was 0; since that is `NaN`, the root was missed.
| `"sqrt(-4 - 4x) + sqrt(2 + x) = -3 - 4x".ToEntity().Solve("x")` | `{ -1.1951718122545800, -1 }` | `{ -1, … }`, the second root in radicals |
| `"sqrt(x + 5) + sqrt(x) = x".ToEntity().Solve("x")` | `{ 5.6349218075063181 }` | the same root in radicals |
+### A limit at a point of continuity is read before it is simplified
+
+**A limit at a finite point takes its value at the point before simplifying the expression, which was the
+expensive half: `x / (tanh(1/2) - tan(x))` at 1 took 10 s, and takes 0.1 s.** The value is the same, and its form
+can differ: a tangent comes back as the quotient of its sine and cosine. A factorial now declines at its poles, which
+reading the value first would otherwise have written down. Some answers move, in both directions, for an expression
+whose values are not real on a side. A NaN there follows the convention `x^x` at 0 is pinned to, a side with no real
+values making no two-sided limit, where the limit was unevaluated.
+
+| Input | Was (2.6.0) | Now |
+|---|---|---|
+| `"x / (tanh(1/2) - tan(x))".ToEntity().Limit("x", 1)` | `1 / ((e - 1) / (e + 1) - tan(1))`, in 4.5 s | `1 / ((e - 1) / (e + 1) - sin(1) / cos(1))` |
+| `"abs(x + 1 - x^x)".ToEntity().Limit("x", 0, ApproachFrom.Right)` | `NaN` | `limitright(abs(x + 1 - x ^ x), x, 0)` |
+| `"abs(x + 1 - x^x)".ToEntity().Limit("x", 0, ApproachFrom.Left)` | `NaN` | `limitleft(abs(x + 1 - x ^ x), x, 0)` |
+| `"sin(x)^(log(x, pi)^arcsin(x))".ToEntity().Limit("x", 1)` | `limit(sin(x) ^ log(x, pi) ^ arcsin(x), x, 1)` | `NaN` |
+| `"(2*(x - pi))^ln(-x)".ToEntity().Limit("x", 0)` | `limit((2 * (x - pi)) ^ ln(-x), x, 0)` | `NaN` |
+| `"arctan((-1)^x)^(pi/x/cos(2))".ToEntity().Limit("x", 1/2)` | `limit(arctan((-1) ^ x) ^ (pi / x / cos(2)), x, 1/2)` | `NaN` |
+
### A radical times a power of its variable is solved exactly
**Silent: an equation in `sqrt(x)` or a cube root of `x` beside powers of `x` is answered in radicals and with all its
diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs
index 50931b529..3f5fe7713 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs
@@ -419,11 +419,18 @@ partial record Arccotanf
partial record Factorialf
{
- internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side) =>
- ComputeLimitImpl(this, x, dist, side) is { } lim ? lim
- : ComputeLimitImpl(New(
- Argument.ComputeLimitDivideEtImpera(x, dist, side) is { IsFinite: true } lim1 ? lim1 : Argument),
- x, dist, side);
+ // Not at a pole: the factorial of a negative integer has no value, and the factorial tends
+ // to an infinity there whose sign depends on the side, so putting the argument's limit in
+ // would only write the pole down. x! at -1 came back (-1)! once that limit arrived as -1.
+ internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side)
+ {
+ if (ComputeLimitImpl(this, x, dist, side) is { } lim)
+ return lim;
+ var argumentLimit = Argument.ComputeLimitDivideEtImpera(x, dist, side);
+ if (argumentLimit?.Evaled is Integer { EInteger.Sign: < 0 })
+ return null;
+ return ComputeLimitImpl(New(argumentLimit is { IsFinite: true } lim1 ? lim1 : Argument), x, dist, side);
+ }
}
// The upper incomplete gamma function, continuous in z away from 0, and at 0 for Re a > 0, where it is Gamma(a): the
diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs
index 33cc7480e..3e2ec8778 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs
@@ -57,7 +57,18 @@ private static Entity AsAPowerOfTheVariable(Entity node, Variable x)
private static Entity? SimplifyAndComputeLimitToInfinityCore(Entity expr, Variable x)
{
- expr = expr.Replace(node => AsAPowerOfTheVariable(node, x)).Simplify();
+ expr = expr.Replace(node => AsAPowerOfTheVariable(node, x));
+ // The value at the destination, read before the simplification below as well as after
+ // it: the substitution is what answers a continuous point in any case, under the same
+ // guards for a jump, a cut and an indeterminate power, and simplifying first is the
+ // expensive half. x / (tanh(1/2) - tan(x)) at 1, moved out to infinity as
+ // x = 1 + 1/x, took 10 s to simplify for a value the substitution reads in
+ // milliseconds. A real number only: a complex one can be the value on a cut, and the
+ // guards for those after this were written against the simplified form.
+ if (LimitSolvers.SolveBySubstitution(expr, x) is { } beforeSimplifying
+ && beforeSimplifying.Evaled is Real { IsFinite: true })
+ return beforeSimplifying.InnerSimplified;
+ expr = expr.Simplify();
if (expr is Providedf(var expression, _)) expr = expression; // limits operate assuming a continuous expression even though some points may be undefined.
// The second remarkable limit reads a 1^oo, and it ran once, at the top of
diff --git a/Sources/Tests/UnitTests/Calculus/AContinuousPointIsReadBeforeSimplifyingTest.cs b/Sources/Tests/UnitTests/Calculus/AContinuousPointIsReadBeforeSimplifyingTest.cs
new file mode 100644
index 000000000..99f486480
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/AContinuousPointIsReadBeforeSimplifyingTest.cs
@@ -0,0 +1,48 @@
+//
+// 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;
+
+namespace AngouriMath.Tests.Calculus
+{
+ ///
+ /// A limit at a finite point is taken by moving the variable out to infinity, and the expression was simplified there
+ /// before its value was read: x / (tanh(1/2) - tan(x)) at 1, a point where it is continuous, took 10 s, and
+ /// tanh(x) / (tanh(1/2) - tan(x)) 18 s, for a value the substitution reads in milliseconds. Found by
+ /// limitcheck at DEPTH=3, SEED=28, as twelve limits that did not finish in 5 s; no issue was filed.
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class AContinuousPointIsReadBeforeSimplifyingTest
+ {
+ // The cap is far from both: each takes about 0.1 s, and took 10 to 18 s.
+ [Theory]
+ [InlineData("x / (tanh(1/2) - tan(x))", "1", ApproachFrom.BothSides)]
+ [InlineData("tanh(x) / (tanh(1/2) - tan(x))", "1", ApproachFrom.BothSides)]
+ [InlineData("tanh(x) / (tanh(1/2) - tan(x))", "-1", ApproachFrom.Left)]
+ [InlineData("tanh(x) / (tanh(1/2) - tan(x))", "2", ApproachFrom.Right)]
+ public void IsTheValueThereAndPrompt(string input, string destination, ApproachFrom side)
+ {
+ var expression = MathS.FromString(input);
+ var task = Task.Factory.StartNew(() => expression.Limit("x", destination, side), TaskCreationOptions.LongRunning);
+ Assert.True(task.Wait(TimeSpan.FromSeconds(5)), $"lim {side} x->{destination} {input} did not finish in five seconds");
+ var difference = (task.Result - expression.Substitute("x", destination)).EvalNumerical();
+ Assert.True(difference is Entity.Number.Complex value && Entity.Number.Complex.Abs(value) is Entity.Number.Real abs && abs < 1e-10,
+ $"lim {side} x->{destination} {input} = {task.Result}");
+ }
+
+ // A pole of the factorial is not a value to read: x! at -1 has an infinity of each sign on its two sides.
+ [Fact]
+ public void TheFactorialAtAPoleIsNotItsValue()
+ {
+ Assert.IsType(MathS.FromString("x!").Limit("x", -1, ApproachFrom.Left));
+ Assert.IsType(MathS.FromString("x!").Limit("x", -1, ApproachFrom.Right));
+ }
+ }
+}