diff --git a/BREAKING-CHANGES.md b/BREAKING-CHANGES.md
index 7d8b0e0ac..07216a887 100644
--- a/BREAKING-CHANGES.md
+++ b/BREAKING-CHANGES.md
@@ -4292,6 +4292,21 @@ Both columns measured on a build, `v2.5.0` against this change.
| `"x^2/x^2"` | `1 provided not x ^ 2 = 0` | `1 provided not x = 0` |
| `"(x^2 - 1)/(x^2 + 2x + 1) + 1/(x + 1)"` | `x / (x + 1) provided not x ^ 2 + 2 * x + 1 = 0` | `x / (x + 1)` |
+### A coefficient past the doubles is not taken for zero
+
+**Answers where there were none.** `(b x^(-4) + b x^(-6) + b x^(-8))/sqrt(1/c^2 + x^2)` was declined. Since the interval
+evaluation of [#1019](https://github.com/asc-community/AngouriMath/issues/1019) it was answered on the unreleased
+master with two of its three terms missing, wrong at every point. Its coefficients run to `c^294`, past `1.3e154` at
+the symbols a coefficient is pinned to when it is read in lowest terms. The check that two values agree took their
+sizes squared, which is infinite there, and so was the tolerance relative to them. Every two values that large agreed,
+and each of those coefficients was taken for zero. The sizes are taken without squaring now, and a size past the
+doubles is left to the decimals.
+
+| Input | Was (2.5.0) | Now |
+|---|---|---|
+| `"(b*x^(-4) + b*x^(-6) + b*x^(-8))*(1/c^2 + x^2)^(-1/2)".ToEntity().Integrate("x")` | `integral(...)` | 1,445 characters |
+| `"(b*x^12 + b*x^10 + b*x^8)*(1/c^2 + x^2)^(-1/2)/x^16".ToEntity().Integrate("x")` | `integral(...)` | 1,254 characters |
+
### A partial-fraction coefficient with symbols in it is in lowest terms, its rational content included
**Improvement, not silent.** The decomposition over written factors with symbols among their
diff --git a/Sources/AngouriMath/Numerics/IntervalEvaluation.cs b/Sources/AngouriMath/Numerics/IntervalEvaluation.cs
index 50e74cf58..a406deb6a 100644
--- a/Sources/AngouriMath/Numerics/IntervalEvaluation.cs
+++ b/Sources/AngouriMath/Numerics/IntervalEvaluation.cs
@@ -168,7 +168,21 @@ internal ComplexInterval(Interval re, Interval im)
internal bool IsFinite => Re.IsFinite && Im.IsFinite;
internal bool IsReal => Im.Low == 0 && Im.High == 0;
- internal double Magnitude => Math.Sqrt(Re.Magnitude * Re.Magnitude + Im.Magnitude * Im.Magnitude);
+ internal double Magnitude => Hypotenuse(Re.Magnitude, Im.Magnitude);
+
+ ///
+ /// sqrt(a^2 + b^2) for and at least zero,
+ /// scaled by the larger so that it does not overflow: squared, anything past about
+ /// 1.3e154 is infinite.
+ ///
+ internal static double Hypotenuse(double a, double b)
+ {
+ var (large, small) = a >= b ? (a, b) : (b, a);
+ if (large == 0 || !Interval.IsFiniteDouble(large))
+ return large;
+ var ratio = small / large;
+ return large * Math.Sqrt(1 + ratio * ratio);
+ }
public static ComplexInterval operator +(ComplexInterval a, ComplexInterval b) => new(a.Re + b.Re, a.Im + b.Im);
public static ComplexInterval operator -(ComplexInterval a, ComplexInterval b) => new(a.Re - b.Re, a.Im - b.Im);
@@ -308,15 +322,26 @@ internal static class IntervalEvaluation
/// of one, where both are smaller): true where the whole of their difference is within
/// it, false where the whole of it is beyond, null where the intervals cannot tell.
///
+ ///
+ /// The sizes are taken without squaring. Squared, a size past about 1.3e154 was
+ /// infinite, and so was the tolerance relative to it, and any two values that large agreed:
+ /// a coefficient of c^294 read at c = 3.71 was taken for zero, which dropped
+ /// two of the three terms of (b x^(-4) + b x^(-6) + b x^(-8))/sqrt(1/c^2 + x^2)'s
+ /// antiderivative, and an answer that large at a sampled point was passed whatever it was.
+ /// A size past the doubles is left to the decimals.
+ /// https://github.com/asc-community/AngouriMath/issues/1019
+ ///
internal static bool? Agree(ComplexInterval left, ComplexInterval right, double relativeTolerance)
{
var difference = left - right;
var scale = Math.Max(1.0, Math.Max(left.Magnitude, right.Magnitude));
+ if (!Interval.IsFiniteDouble(scale))
+ return null;
var allowed = relativeTolerance * scale;
- var largest = Math.Sqrt(difference.Re.Magnitude * difference.Re.Magnitude + difference.Im.Magnitude * difference.Im.Magnitude);
+ var largest = Interval.Wider(ComplexInterval.Hypotenuse(difference.Re.Magnitude, difference.Im.Magnitude), 1);
if (largest <= allowed)
return true;
- var smallest = Math.Sqrt(difference.Re.Mignitude * difference.Re.Mignitude + difference.Im.Mignitude * difference.Im.Mignitude);
+ var smallest = Interval.Wider(ComplexInterval.Hypotenuse(difference.Re.Mignitude, difference.Im.Mignitude), -1);
if (smallest > allowed)
return false;
return null;
diff --git a/Sources/Tests/UnitTests/Calculus/ACoefficientPastTheDoublesIsNotTakenForZeroIntegralTest.cs b/Sources/Tests/UnitTests/Calculus/ACoefficientPastTheDoublesIsNotTakenForZeroIntegralTest.cs
new file mode 100644
index 000000000..db61ffb5a
--- /dev/null
+++ b/Sources/Tests/UnitTests/Calculus/ACoefficientPastTheDoublesIsNotTakenForZeroIntegralTest.cs
@@ -0,0 +1,45 @@
+//
+// 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 AngouriMath.Extensions;
+using Xunit;
+
+namespace AngouriMath.Tests.Calculus
+{
+ ///
+ /// The coefficients of these antiderivatives run to c^294, past 1.3e154 at the
+ /// symbols the lowest-terms reading pins, where the interval check took a size squared to an
+ /// infinity and agreed that each was zero: two of the three terms were dropped, and the answer
+ /// differed from the integral at every point.
+ /// #1019
+ ///
+ [Trait("Area", "Calculus")]
+ public sealed class ACoefficientPastTheDoublesIsNotTakenForZeroIntegralTest
+ {
+ [Theory]
+ [InlineData("(b*x^(-4) + b*x^(-6) + b*x^(-8))*(1/c^2 + x^2)^(-1/2)")]
+ [InlineData("(b*x^12 + b*x^10 + b*x^8)*(1/c^2 + x^2)^(-1/2)/x^16")]
+ public void KeepsEveryTerm(string integrand)
+ {
+ var integral = integrand.ToEntity().Integrate("x");
+ if (integral.Stringize().Contains("integral("))
+ return;
+ Entity Pinned(Entity e) => e.Substitute("b", 0.7).Substitute("c", 1.3);
+ var derivative = Pinned(integral.Substitute("C", 0)).Differentiate("x");
+ var original = Pinned(integrand.ToEntity());
+ foreach (var at in new[] { -2.1, -1.3, -0.6, 0.5, 1.2, 2.3 })
+ {
+ var want = original.Substitute("x", at).EvalNumerical();
+ var got = derivative.Substitute("x", at).EvalNumerical();
+ Assert.True(Math.Abs((double)(got - want).RealPart) + Math.Abs((double)(got - want).ImaginaryPart)
+ < 1e-9 * Math.Max(1, Math.Abs((double)want.RealPart)),
+ $"d/dx of the antiderivative of {integrand} is {got} at x = {at}, where the integrand is {want}");
+ }
+ }
+ }
+}
diff --git a/Sources/Tests/UnitTests/Core/IntervalEvaluationTest.cs b/Sources/Tests/UnitTests/Core/IntervalEvaluationTest.cs
index 53c814bfe..e7c27a0be 100644
--- a/Sources/Tests/UnitTests/Core/IntervalEvaluationTest.cs
+++ b/Sources/Tests/UnitTests/Core/IntervalEvaluationTest.cs
@@ -160,6 +160,22 @@ public void MoreDigitsSettleWhatDoublesCannot()
Assert.False(PreciseEvaluation.Agree(PreciseEvaluation.Of(nearby, 40)!.Value, PreciseEvaluation.Of(sine, 40)!.Value, 1e-9));
}
+ ///
+ /// A size past about 1.3e154 squared to an infinity, and so did the tolerance relative
+ /// to it: two values that large agreed whatever they were, and a coefficient that large was
+ /// taken for zero. Neither is decided now, and an exact difference between them is seen.
+ ///
+ [Theory]
+ [InlineData("2 * 10^167", "0")]
+ [InlineData("2 * 10^167", "3 * 10^167")]
+ [InlineData("2 * 10^167 * i", "0")]
+ public void ASizePastTheDoublesDoesNotAgree(string left, string right)
+ => Assert.NotEqual(true, IntervalEvaluation.Agree(IntervalEvaluation.Of(left.ToEntity())!.Value, IntervalEvaluation.Of(right.ToEntity())!.Value, 1e-30));
+
+ [Fact]
+ public void TheSizeOfALargeComplexValueIsFinite()
+ => Assert.InRange(IntervalEvaluation.Of("3 * 10^160 + 4 * 10^160 * i".ToEntity())!.Value.Magnitude, 4.99e160, 5.01e160);
+
[Theory]
[InlineData("sqrt(2)")]
[InlineData("e^2 - pi")]