diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index bbc9d7b7f..b421df3d7 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -338,6 +338,21 @@ Two-sided, each side is now taken with its own value, and `floor(x)^x` at 0 is 1
| `"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 function of a quotient tending to 0/0 has the value at the quotient's limit
+
+**Silent: a different answer.** A function of a quotient took the quotient's limit from the descent alone, which reads
+0/0 or oo/oo off the two parts as NaN -- the claim that there is no limit -- where only the descent cannot tell. The
+quotient's whole limit is asked now, once, through the remarkable limits and l'Hopital's rule, and only a value it
+gives is taken. No issue was filed.
+
+| Input | Was (2.6.0) | Now |
+|---|---|---|
+| `limit((sin(x)/x)^2, x, 0)` | `NaN` | `1` |
+| `limit(exp(sin(x)/x), x, 0)` | `NaN` | `e` |
+| `limit(sin(sin(x)/x), x, 0)` | `NaN` | `sin(1)` |
+| `limit(exp((cos(x) - 1)/x^2), x, 0)` | `NaN` | `e ^ (-1/2)` |
+| `limitright(exp(sin(x)/x), x, 0)` | unevaluated | `e` |
+
### A two-sided limit of an expression real on both sides is where its sides agree
**Silent: a two-sided limit that was `NaN` although both sides agree has their value, where the expression is real on
diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs
index ad93979a9..3adb73524 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Limit.Classes.cs
@@ -118,11 +118,55 @@ partial record Divf
// which side the divisor vanishes from, and so comes back NaN. Only where
// nothing above answered, and whatever it did answer is kept if this finds
// nothing better.
- return DivergesAtAVanishingDivisor(Dividend, Divisor, x, dist, side) ?? substituted;
+ if (DivergesAtAVanishingDivisor(Dividend, Divisor, x, dist, side) is { } diverges)
+ return diverges;
+ // An indeterminate form, 0/0 or oo/oo, read off the parts is NaN, which says there
+ // is no limit, where the descent only cannot tell: sin(x)/x at 0 is 1, which the
+ // whole limit reaches through the first remarkable limit, and a function of it --
+ // (sin(x)/x)^2, exp(sin(x)/x), exp(ln(cos(x))/x^2) -- came out NaN, its argument
+ // asked only of the descent. The whole limit of the quotient is asked once, and
+ // only a value is taken, and as the number where the form it came back in holds the
+ // infinity it was taken at: -4/9 * 0 * (1 - (1 + (1/+oo)^2)^(1/3)), handed on as
+ // written, sent a definite integral astray. Folded, it is 0, and ln(2) * 2^(1/+oo) is
+ // ln(2), not its decimal. Where it has no
+ // value, what was answered is kept.
+ if (substituted?.Evaled == MathS.NaN && IsAnIndeterminateQuotient(dividend, divisor)
+ && AsTheWholeLimit(this, x, dist, side) is { } whole && whole.Evaled is Number { IsNaN: false } value)
+ return whole.InnerSimplified is var folded && folded.Nodes.Any(static node => node is Number { IsFinite: false })
+ ? value : folded;
+ return substituted;
}
}
}
+ /// Whether two parts' limits make a quotient 0/0 or oo/oo.
+ private static bool IsAnIndeterminateQuotient(Entity dividend, Entity divisor)
+ => (dividend.Evaled, divisor.Evaled) switch
+ {
+ (Complex { IsZero: true }, Complex { IsZero: true }) => true,
+ (Number { IsFinite: false, IsNaN: false }, Number { IsFinite: false, IsNaN: false }) => true,
+ _ => false,
+ };
+
+ /// The quotient whose whole limit is being asked from inside the descent, so that no other is asked inside it.
+ [System.ThreadStatic] private static HashSet? quotientsAskedWhole;
+
+ private static Entity? AsTheWholeLimit(Entity quotient, Variable x, Entity dist, ApproachFrom side)
+ {
+ // One level: a whole limit asked from here descends again, and a retry inside it asked another, so that a
+ // definite integral's antiderivative at its bounds -- 1/((1 + x^2)^(1/3) (9 + x^2)) across 0 -- went from four
+ // seconds to past two minutes and was left unevaluated.
+ // Nor under a caller's budget of limits, which every limit asked here spends: fifty asked on the side of a
+ // definite integral's bounds left the bounds none, and the integral unevaluated.
+ if (Functions.Algebra.LimitFunctional.UnderALimitBudget)
+ return null;
+ quotientsAskedWhole ??= new();
+ if (quotientsAskedWhole.Count > 0 || !quotientsAskedWhole.Add(quotient))
+ return null;
+ try { return Functions.Algebra.LimitFunctional.ComputeLimit(quotient, x, dist, side); }
+ finally { quotientsAskedWhole.Remove(quotient); }
+ }
+
partial record Modf
{
internal override Entity? ComputeLimitDivideEtImpera(Variable x, Entity dist, ApproachFrom side)
diff --git a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs
index 5b05a2891..7efa0576b 100644
--- a/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs
+++ b/Sources/AngouriMath/Functions/Continuous/Limits/Solvers/Solvers.Definition.cs
@@ -242,6 +242,9 @@ private static Entity ExpandLogarithm(Entity expr)
///
[System.ThreadStatic] private static int? remainingLimits;
+ /// Whether a caller has set a budget of limits, which a limit asked on the side would spend.
+ internal static bool UnderALimitBudget => remainingLimits is not null;
+
///
/// with at most limits taken in all, for a caller that has a
/// use only for a limit it can have cheaply, and leaves the question open otherwise.
diff --git a/Sources/Tests/UnitTests/Calculus/AFunctionOfAnIndeterminateQuotientTest.cs b/Sources/Tests/UnitTests/Calculus/AFunctionOfAnIndeterminateQuotientTest.cs
new file mode 100644
index 000000000..ca1d6d7f8
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/AFunctionOfAnIndeterminateQuotientTest.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.Core;
+using Xunit;
+using static AngouriMath.Entity;
+
+namespace AngouriMath.Tests.Calculus
+{
+ ///
+ /// A function of a quotient that tends to 0/0 took the quotient's limit from the descent alone, which reads 0/0 off
+ /// the two parts as NaN -- the claim that there is no limit -- where only the descent cannot tell: sin(x)/x at 0
+ /// is 1, and (sin(x)/x)^2 and exp(sin(x)/x) were NaN two-sided and unevaluated from either side. The
+ /// quotient's whole limit is asked now, once, and only its value is taken. Found while checking a limit
+ /// limitcheck reported; no issue was filed.
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class AFunctionOfAnIndeterminateQuotientTest
+ {
+ [Theory]
+ [InlineData("(sin(x)/x)^2", ApproachFrom.BothSides, "1")]
+ [InlineData("exp(sin(x)/x)", ApproachFrom.BothSides, "e")]
+ [InlineData("exp(sin(x)/x)", ApproachFrom.Right, "e")]
+ [InlineData("sin(sin(x)/x)", ApproachFrom.BothSides, "sin(1)")]
+ [InlineData("exp((cos(x) - 1)/x^2)", ApproachFrom.BothSides, "e^(-1/2)")]
+ [InlineData("sqrt(sin(x)/x)", ApproachFrom.Right, "1")]
+ // As before.
+ [InlineData("sin(x)/x", ApproachFrom.BothSides, "1")]
+ [InlineData("exp(x/x)", ApproachFrom.BothSides, "e")]
+ public void HasTheValueOfTheFunctionAtTheQuotientsLimit(string input, ApproachFrom side, string expected)
+ {
+ var limit = MathS.FromString(input).Limit("x", 0, side);
+ Assert.IsAssignableFrom(limit.Evaled);
+ var difference = (limit - MathS.FromString(expected)).EvalNumerical();
+ Assert.True(difference.Abs() < (Number.Real)1e-30, $"the limit of {input} at 0 is {limit}, where it is {expected}");
+ }
+ }
+}