From 5797bf488438170e5fcbfa5acfb7cd84a81887a6 Mon Sep 17 00:00:00 2001 From: just Date: Fri, 18 Sep 2026 14:10:24 +0400 Subject: [PATCH] added sanity checks --- 0-source/Just.PreciseMath/DDMath.cs | 11 +- .../DoubleDouble.Arithmetic.cs | 39 ++--- .../Just.PreciseMath/PreciseMathHelper.cs | 33 ++++- 1-tests/Directory.Build.props | 5 + .../ArithmeticRangeTests.cs | 3 - .../DoubleDoubleArithmeticTests.cs | 135 +++++++++++++++++- .../DoubleDoubleBoundaryTests.cs | 4 - .../DoubleDoubleCbrtTests.cs | 4 - .../DoubleDoubleComparisonTests.cs | 3 - .../DoubleDoubleConversionTests.cs | 4 - .../DoubleDoubleExponentialFunctionsTests.cs | 3 - .../DoubleDoubleFormattingTests.cs | 2 - .../DoubleDoubleHypotTests.cs | 4 - .../DoubleDoubleNumberStylesTests.cs | 3 - .../DoubleDoubleParsingTests.cs | 3 - .../DoubleDoubleRepresentationTests.cs | 3 - .../DoubleDoubleRootFunctionsTests.cs | 3 - .../DoubleDoubleRootNTests.cs | 3 - .../DoubleDoubleSignedNumberTests.cs | 4 - .../DoubleDoubleSpanFormattingTests.cs | 2 - .../DoubleDoubleSpecialValueTests.cs | 3 - .../DoubleDoubleTests.cs | 4 - .../GenericConversionTests.cs | 3 - .../PreciseMathExpTests.cs | 3 - .../PreciseMathInvSqrtTests.cs | 4 - .../PreciseMathLogTests.cs | 3 - .../PreciseMathPowTests.cs | 4 - .../PreciseMathRealPowSpecialTests.cs | 3 - .../PreciseMathRealPowTests.cs | 3 - .../PreciseMathReciprocalTests.cs | 4 - .../PreciseMathSqrtTests.cs | 4 - .../PreciseMathTests.cs | 3 - .../ReferenceData/ExpReferenceData.cs | 2 - .../ExponentialFunctionsReferenceData.cs | 2 - .../ReferenceData/LogReferenceData.cs | 2 - .../ReferenceData/RealPowReferenceData.cs | 2 - .../ReferenceData/RootNReferenceData.cs | 2 - .../DoubleDoubleAdditionChecks.cs | 27 ++++ .../DoubleDoubleArithmeticEqualityChecks.cs | 108 ++++++++++++++ .../DoubleDoubleDivisionChecks.cs | 29 ++++ .../DoubleDoubleMultiplicationChecks.cs | 29 ++++ .../DoubleDoubleSubtractionChecks.cs | 30 ++++ AGENTS.md | 6 - README.md | 14 +- 44 files changed, 419 insertions(+), 146 deletions(-) create mode 100644 1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleAdditionChecks.cs create mode 100644 1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleArithmeticEqualityChecks.cs create mode 100644 1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleDivisionChecks.cs create mode 100644 1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleMultiplicationChecks.cs create mode 100644 1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleSubtractionChecks.cs diff --git a/0-source/Just.PreciseMath/DDMath.cs b/0-source/Just.PreciseMath/DDMath.cs index 8b0abc5..810c69d 100644 --- a/0-source/Just.PreciseMath/DDMath.cs +++ b/0-source/Just.PreciseMath/DDMath.cs @@ -36,16 +36,7 @@ public static partial class DDMath return PreciseMathHelper.DivideBoundary(one, value); } - double quotient = one / value._high; - double remainder = Math.FusedMultiplyAdd(-quotient, value._high, one); - remainder = Math.FusedMultiplyAdd(-quotient, value._low, remainder); - double correction = remainder / value._high; - // For normalized input, using the high denominator in the correction - // adds only O(u^2) error, with u=2^-53. The denominator exponent guard - // keeps the quotient normal and finite; it dominates the correction, - // so QuickTwoSum is ordered and its sum remains finite. - (double high, double low) = PreciseMathHelper.TwoQuickAdd(quotient, correction); - return new DoubleDouble(high, low == 0.0 ? 0.0 : low); + return PreciseMathHelper.DivideScalarFinite(one, value); } /// diff --git a/0-source/Just.PreciseMath/DoubleDouble.Arithmetic.cs b/0-source/Just.PreciseMath/DoubleDouble.Arithmetic.cs index ca86a5f..37085ed 100644 --- a/0-source/Just.PreciseMath/DoubleDouble.Arithmetic.cs +++ b/0-source/Just.PreciseMath/DoubleDouble.Arithmetic.cs @@ -103,24 +103,28 @@ public readonly partial struct DoubleDouble : } /// Applies the expansion operation without discarding the low component. + /// Preserves the component bits of addition with the equivalent zero-low scalar expansion. public static DoubleDouble operator +(DoubleDouble left, double right) { return PreciseMathHelper.AddScalar(left._high, left._low, right); } /// Applies the expansion operation without discarding the low component. + /// Preserves the component bits of addition with the equivalent zero-low scalar expansion. public static DoubleDouble operator +(double left, DoubleDouble right) { return right + left; } /// Applies the expansion operation without discarding the low component. + /// Preserves the component bits of subtraction with the equivalent zero-low scalar expansion. public static DoubleDouble operator -(DoubleDouble left, double right) { return PreciseMathHelper.AddScalar(left._high, left._low, -right); } /// Applies the expansion operation without discarding the low component. + /// Preserves the component bits of subtraction with the equivalent zero-low scalar expansion. public static DoubleDouble operator -(double left, DoubleDouble right) { // Negate the components, not the result: exact cancellation must yield +0. @@ -153,7 +157,8 @@ public readonly partial struct DoubleDouble : return right * left; } - /// Applies the expansion operation without discarding the low component. + /// Divides by a scalar using a quotient estimate and two residual corrections. + /// Preserves the component bits of division by the equivalent zero-low expansion. public static DoubleDouble operator /(DoubleDouble left, double right) { if (!IsFinite(left) || !double.IsFinite(right) || left._high == 0.0 || right == 0.0) @@ -166,15 +171,24 @@ public readonly partial struct DoubleDouble : } double quotient = left._high / right; - double remainder = Math.FusedMultiplyAdd(-quotient, right, left._high); - double correction = (remainder + left._low) / right; - // The exponent guard keeps the quotient normal. The correction is - // O(u * quotient), so QuickTwoSum is ordered; one correction gives O(u^2) error. - (double high, double low) = PreciseMathHelper.TwoQuickAdd(quotient, correction); - return new DoubleDouble(high, low == 0.0 ? 0.0 : low); + // The initial product is near left.High, so the exponent guard makes + // TwoMultiply exact. Keep the complete remainder, including its low. + (double product, double productError) = PreciseMathHelper.TwoMultiply(right, quotient); + DoubleDouble remainder = PreciseMathHelper.AddFinite(left._high, left._low, -product, -productError); + double correction = remainder._high / right; + (double correctionProduct, double correctionError) = PreciseMathHelper.TwoMultiply(right, correction); + // This product can be subnormal. FMA rounds its residual just as the + // exact boundary product does; normalize and canonicalize its zero low. + DoubleDouble correctedProduct = PreciseMathHelper.NormalizeFinite(correctionProduct, correctionError); + double finalRemainder = PreciseMathHelper.SubtractDivisionCorrectionHigh(remainder, correctedProduct); + double finalCorrection = finalRemainder / right; + // The same finite bounds as DD/DD apply. Normalize the first correction + // before adding the last, preserving the DD/DD evaluation order. + return PreciseMathHelper.NormalizeFinite(quotient, correction) + finalCorrection; } - /// Applies the expansion operation without discarding the low component. + /// Divides a scalar using a quotient estimate and two residual corrections. + /// Preserves the component bits of division with the equivalent zero-low numerator. public static DoubleDouble operator /(double left, DoubleDouble right) { if (!double.IsFinite(left) || !IsFinite(right) || left == 0.0 || right._high == 0.0) @@ -186,13 +200,6 @@ public readonly partial struct DoubleDouble : return PreciseMathHelper.DivideBoundary(left, right); } - double quotient = left / right._high; - double remainder = Math.FusedMultiplyAdd(-quotient, right._high, left); - remainder = Math.FusedMultiplyAdd(-quotient, right._low, remainder); - double correction = remainder / right._high; - // Using the high denominator in the correction adds only O(u^2) error. - // As above, the guarded quotient dominates its correction in magnitude. - (double high, double low) = PreciseMathHelper.TwoQuickAdd(quotient, correction); - return new DoubleDouble(high, low == 0.0 ? 0.0 : low); + return PreciseMathHelper.DivideScalarFinite(left, right); } } diff --git a/0-source/Just.PreciseMath/PreciseMathHelper.cs b/0-source/Just.PreciseMath/PreciseMathHelper.cs index 28b4092..b31b79e 100644 --- a/0-source/Just.PreciseMath/PreciseMathHelper.cs +++ b/0-source/Just.PreciseMath/PreciseMathHelper.cs @@ -104,7 +104,25 @@ internal static class PreciseMathHelper return NormalizeFinite(sum, sumError + (middleError + lowError)); } - // Only for DD division's second remainder: normalized finite inputs with highs + // Finite, nonzero numerator and normalized denominator, both with high + // exponents in [-450, 450]. Shared with Reciprocal after its constant-numerator + // checks. Do not promote left or dispatch to the DD/DD division operator. + internal static DoubleDouble DivideScalarFinite(double left, DoubleDouble right) + { + double quotient = left / right._high; + DoubleDouble product = right * quotient; + // Preserve DD/DD's complete first remainder. A chained scalar FMA drops + // residual bits needed by the second correction; retain the expansion + // subtraction before computing the next quotient correction. + DoubleDouble remainder = AddFinite(left, 0.0, -product._high, -product._low); + double correction = remainder._high / right._high; + double finalRemainder = SubtractDivisionCorrectionHigh(remainder, right * correction); + double finalCorrection = finalRemainder / right._high; + // DD/DD's guard-derived finite bounds and normalization order apply. + return NormalizeFinite(quotient, correction) + finalCorrection; + } + + // Only for division's second remainder: normalized finite inputs with highs // bounded by 2^462, including the correction product's exact fallback results. // Same sign and normal binade imply Sterbenz-exact high subtraction. Its error // is +0; with canonical input lows, TwoAdd(+0, lowSum) is (lowSum, +0). @@ -164,11 +182,14 @@ internal static class PreciseMathHelper } (double sum, double error) = TwoAdd(high, value); - // Near high-component cancellation, Sterbenz makes the first sum exact, - // so error is zero and this retains low exactly. Otherwise its rounding - // contributes only O(u^2) relative error. The final TwoSum normalizes. - (double result, double residual) = TwoAdd(sum, error + low); - return new DoubleDouble(result, residual == 0.0 ? 0.0 : residual); + // Specialize AddFinite for a zero-low scalar: adding that zero to low + // has no residual. Retain the rounding error when combining the high + // sum's error with low, then fold it into the final normalization. + // TwoSum also absorbs a negated zero low from scalar-left subtraction. + (double middle, double middleError) = TwoAdd(error, low); + (double result, double residual) = TwoAdd(sum, middle); + // The unchanged addition guard gives AddFinite's finite-sum bounds. + return NormalizeFinite(result, residual + middleError); } // Keep the complete BigInteger expressions out of ordinary arithmetic bodies, diff --git a/1-tests/Directory.Build.props b/1-tests/Directory.Build.props index 41c5032..a9288b0 100644 --- a/1-tests/Directory.Build.props +++ b/1-tests/Directory.Build.props @@ -18,4 +18,9 @@ + + + + + diff --git a/1-tests/Just.PreciseMath.Tests/ArithmeticRangeTests.cs b/1-tests/Just.PreciseMath.Tests/ArithmeticRangeTests.cs index 59fe0a2..c500019 100644 --- a/1-tests/Just.PreciseMath.Tests/ArithmeticRangeTests.cs +++ b/1-tests/Just.PreciseMath.Tests/ArithmeticRangeTests.cs @@ -1,6 +1,3 @@ -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class ArithmeticRangeTests diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleArithmeticTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleArithmeticTests.cs index d93e405..ce09c43 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleArithmeticTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleArithmeticTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleArithmeticTests @@ -332,6 +328,53 @@ public class DoubleDoubleArithmeticTests Check(6.0 / new DoubleDouble(2.0), 3.0, 0.0); } + [Theory] + [InlineData(2.0)] + [InlineData(3.0)] + public void ScalarAdditionAndSubtractionRetainTheMiddleSumResidual(double magnitude) + { + // Exact Fraction arithmetic on the stored E components confirms that + // all signed E +/- 2 and E +/- 3 results fit exactly in two components. + // Compare exact dyadic sums, not another overload or a rounded tolerance. + foreach (double valueSign in new[] { -1.0, 1.0 }) + { + foreach (double scalarSign in new[] { -1.0, 1.0 }) + { + DoubleDouble value = valueSign * DoubleDouble.E; + double scalar = scalarSign * magnitude; + BigInteger expectedSum = Units(value) + Units(scalar); + BigInteger expectedDifference = Units(value) - Units(scalar); + Units(value + scalar).ShouldBe(expectedSum); + Units(scalar + value).ShouldBe(expectedSum); + Units(value - scalar).ShouldBe(expectedDifference); + Units(scalar - value).ShouldBe(-expectedDifference); + } + } + } + + [Theory] + [InlineData("+")] + [InlineData("-")] + public void ScalarSumsAndDifferencesMatchExpansionBitsAcrossRanges(string operation) + { + // Bitwise compatibility; independent exact-rational accuracy checks are separate. + (double[] scalars, DoubleDouble[] values) = ScalarArithmeticCompatibilityCases(); + foreach (DoubleDouble value in values) + { + foreach (double scalar in scalars) + { + DoubleDouble expanded = new(scalar); + DoubleDouble expected = operation == "+" ? value + expanded : value - expanded; + DoubleDouble actual = operation == "+" ? value + scalar : value - scalar; + CheckBoundary(actual, expected.High, expected.Low); + + DoubleDouble expectedReverse = operation == "+" ? expanded + value : expanded - value; + DoubleDouble actualReverse = operation == "+" ? scalar + value : scalar - value; + CheckBoundary(actualReverse, expectedReverse.High, expectedReverse.Low); + } + } + } + [Fact] public void ExpansionCancellationRetainsExactComponentsAcrossTheAdditionGuard() { @@ -579,6 +622,25 @@ public class DoubleDoubleArithmeticTests } } + [Fact] + public void DivisionOverloadsPreserveComponentBitsAcrossRangesAndSpecialValues() + { + // Compatibility, not an accuracy oracle. GeneralArithmeticMeetsExactRationalBound + // separately checks complete results against exact rational bounds. + (double[] scalars, DoubleDouble[] values) = ScalarArithmeticCompatibilityCases(); + foreach (DoubleDouble value in values) + { + foreach (double scalar in scalars) + { + DoubleDouble expectedQuotient = value / new DoubleDouble(scalar); + CheckBoundary(value / scalar, expectedQuotient.High, expectedQuotient.Low); + + DoubleDouble expectedReverseQuotient = new DoubleDouble(scalar) / value; + CheckBoundary(scalar / value, expectedReverseQuotient.High, expectedReverseQuotient.Low); + } + } + } + [Fact] public void DivisionRetainsSubnormalCorrectionsWithOrdinaryHighComponents() { @@ -597,6 +659,38 @@ public class DoubleDoubleArithmeticTests } } + [Theory] + [InlineData(-1000)] + [InlineData(-1021)] + [InlineData(-1022)] + [InlineData(-1023)] + public void ScalarDivisionPreservesSparseCorrectionProductsNearUnderflow(int lowExponent) + { + // At -1000, the second product is normal but its exact residual is + // (962132647665515 / 1073741824) * epsilon, which rounds to 896056 * epsilon. + // Derived from exact binary64 rational products; the other rows bracket + // the correction product's normal/subnormal transition. + foreach (double numeratorSign in new[] { -1.0, 1.0 }) + { + foreach (double denominatorSign in new[] { -1.0, 1.0 }) + { + foreach (double lowSign in new[] { -1.0, 1.0 }) + { + double low = Math.ScaleB(lowSign, lowExponent); + double denominator = denominatorSign * 1.1; + DoubleDouble numerator = DoubleDouble.FromComponents(numeratorSign * 1.1, low); + DoubleDouble actual = numerator / denominator; + DoubleDouble expanded = numerator / new DoubleDouble(denominator); + CheckBoundary(actual, expanded.High, expanded.Low); + // Exact quotient = +/-1 + low/denominator. Its high cannot + // change here; binary64 division independently rounds the low. + CheckBoundary(actual, numeratorSign / denominatorSign, low / denominator); + AssertRelative(actual, Units(numerator) << 1074, Units(denominator)); + } + } + } + } + [Fact] public void SubnormalProductsWithLargeNormalsRetainExactResultsInBothOrders() { @@ -884,6 +978,39 @@ public class DoubleDoubleArithmeticTests } } + private static (double[] Scalars, DoubleDouble[] Values) ScalarArithmeticCompatibilityCases() + { + List scalars = [0.0, -0.0, 1.0, -1.0, 2.0, -2.0, 3.0, -3.0, 100.0, -100.0, + double.MaxValue, double.MinValue, double.PositiveInfinity, + double.NegativeInfinity, double.NaN]; + List values = [DoubleDouble.E, -DoubleDouble.E]; + // Include addition/division dispatch edges, exponent extremes, dense/sparse + // lows of either sign, and significands on either side of a binade. + int[] exponents = [-1074, -1022, -451, -450, -1, 0, 1, 450, 451, 1020, 1021, 1023]; + double[] significands = [1.0, Math.BitIncrement(1.0), 1.5, Math.BitDecrement(2.0)]; + foreach (int exponent in exponents) + { + foreach (double significand in significands) + { + foreach (double sign in new[] { -1.0, 1.0 }) + { + double high = Math.ScaleB(sign * significand, exponent); + scalars.Add(high); + double denseLow = Math.ScaleB(1.0, exponent - 54); + foreach (double low in new[] { 0.0, denseLow, -denseLow, double.Epsilon, -double.Epsilon }) + { + values.Add(DoubleDouble.FromComponents(high, low)); + } + } + } + } + foreach (double scalar in scalars) + { + values.Add(new DoubleDouble(scalar)); + } + return (scalars.ToArray(), values.ToArray()); + } + private static void AssertDivisionMatchesPrevious(DoubleDouble left, DoubleDouble right) { // Freeze the pre-specialization expression. The unchanged public operators diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleBoundaryTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleBoundaryTests.cs index 0f7e90d..fadfc95 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleBoundaryTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleBoundaryTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleBoundaryTests diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleCbrtTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleCbrtTests.cs index 7e3cd8e..f8d999f 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleCbrtTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleCbrtTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleCbrtTests diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleComparisonTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleComparisonTests.cs index 022696a..11a6b5a 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleComparisonTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleComparisonTests.cs @@ -1,6 +1,3 @@ -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleComparisonTests diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleConversionTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleConversionTests.cs index 3867bcc..e7d3d9e 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleConversionTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleConversionTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleConversionTests diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleExponentialFunctionsTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleExponentialFunctionsTests.cs index fe45528..7e90f0e 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleExponentialFunctionsTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleExponentialFunctionsTests.cs @@ -1,8 +1,5 @@ using System.Globalization; -using System.Numerics; using Just.PreciseMath.Tests.ReferenceData; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleFormattingTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleFormattingTests.cs index 0efb243..fab29ed 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleFormattingTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleFormattingTests.cs @@ -1,6 +1,4 @@ using System.Globalization; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleHypotTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleHypotTests.cs index e455895..d228c6b 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleHypotTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleHypotTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleHypotTests diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleNumberStylesTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleNumberStylesTests.cs index c8a10bd..59e8a9e 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleNumberStylesTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleNumberStylesTests.cs @@ -1,7 +1,4 @@ using System.Globalization; -using System.Numerics; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleParsingTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleParsingTests.cs index 0140ec2..20e2dee 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleParsingTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleParsingTests.cs @@ -1,7 +1,4 @@ using System.Globalization; -using System.Numerics; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleRepresentationTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleRepresentationTests.cs index 695dc78..d4385f2 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleRepresentationTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleRepresentationTests.cs @@ -1,6 +1,3 @@ -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleRepresentationTests diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleRootFunctionsTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleRootFunctionsTests.cs index d2929f0..54bbaee 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleRootFunctionsTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleRootFunctionsTests.cs @@ -1,6 +1,3 @@ -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleRootFunctionsTests diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleRootNTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleRootNTests.cs index c7f996e..1a4d8ed 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleRootNTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleRootNTests.cs @@ -1,8 +1,5 @@ using System.Globalization; -using System.Numerics; using Just.PreciseMath.Tests.ReferenceData; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleSignedNumberTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleSignedNumberTests.cs index f3895f8..a0fef1d 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleSignedNumberTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleSignedNumberTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleSignedNumberTests diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleSpanFormattingTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleSpanFormattingTests.cs index 4d6ecc0..27aa5ae 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleSpanFormattingTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleSpanFormattingTests.cs @@ -1,6 +1,4 @@ using System.Globalization; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleSpecialValueTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleSpecialValueTests.cs index 7134170..8a1ba53 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleSpecialValueTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleSpecialValueTests.cs @@ -1,7 +1,4 @@ using System.Globalization; -using System.Numerics; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/DoubleDoubleTests.cs b/1-tests/Just.PreciseMath.Tests/DoubleDoubleTests.cs index 790f0e7..7be04c4 100644 --- a/1-tests/Just.PreciseMath.Tests/DoubleDoubleTests.cs +++ b/1-tests/Just.PreciseMath.Tests/DoubleDoubleTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class DoubleDoubleTests diff --git a/1-tests/Just.PreciseMath.Tests/GenericConversionTests.cs b/1-tests/Just.PreciseMath.Tests/GenericConversionTests.cs index 5c9d7b6..189306a 100644 --- a/1-tests/Just.PreciseMath.Tests/GenericConversionTests.cs +++ b/1-tests/Just.PreciseMath.Tests/GenericConversionTests.cs @@ -1,7 +1,4 @@ using System.Diagnostics.CodeAnalysis; -using System.Numerics; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/PreciseMathExpTests.cs b/1-tests/Just.PreciseMath.Tests/PreciseMathExpTests.cs index 4e7d4fb..9b9a3dd 100644 --- a/1-tests/Just.PreciseMath.Tests/PreciseMathExpTests.cs +++ b/1-tests/Just.PreciseMath.Tests/PreciseMathExpTests.cs @@ -1,8 +1,5 @@ using System.Globalization; -using System.Numerics; using Just.PreciseMath.Tests.ReferenceData; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/PreciseMathInvSqrtTests.cs b/1-tests/Just.PreciseMath.Tests/PreciseMathInvSqrtTests.cs index c029044..0f26469 100644 --- a/1-tests/Just.PreciseMath.Tests/PreciseMathInvSqrtTests.cs +++ b/1-tests/Just.PreciseMath.Tests/PreciseMathInvSqrtTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class PreciseMathInvSqrtTests diff --git a/1-tests/Just.PreciseMath.Tests/PreciseMathLogTests.cs b/1-tests/Just.PreciseMath.Tests/PreciseMathLogTests.cs index 788b113..94c3308 100644 --- a/1-tests/Just.PreciseMath.Tests/PreciseMathLogTests.cs +++ b/1-tests/Just.PreciseMath.Tests/PreciseMathLogTests.cs @@ -1,8 +1,5 @@ using System.Globalization; -using System.Numerics; using Just.PreciseMath.Tests.ReferenceData; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/PreciseMathPowTests.cs b/1-tests/Just.PreciseMath.Tests/PreciseMathPowTests.cs index 3670a2f..9f76dc6 100644 --- a/1-tests/Just.PreciseMath.Tests/PreciseMathPowTests.cs +++ b/1-tests/Just.PreciseMath.Tests/PreciseMathPowTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class PreciseMathPowTests diff --git a/1-tests/Just.PreciseMath.Tests/PreciseMathRealPowSpecialTests.cs b/1-tests/Just.PreciseMath.Tests/PreciseMathRealPowSpecialTests.cs index 5a2afac..08af8a8 100644 --- a/1-tests/Just.PreciseMath.Tests/PreciseMathRealPowSpecialTests.cs +++ b/1-tests/Just.PreciseMath.Tests/PreciseMathRealPowSpecialTests.cs @@ -1,6 +1,3 @@ -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class PreciseMathRealPowSpecialTests diff --git a/1-tests/Just.PreciseMath.Tests/PreciseMathRealPowTests.cs b/1-tests/Just.PreciseMath.Tests/PreciseMathRealPowTests.cs index d67dd17..607c5a4 100644 --- a/1-tests/Just.PreciseMath.Tests/PreciseMathRealPowTests.cs +++ b/1-tests/Just.PreciseMath.Tests/PreciseMathRealPowTests.cs @@ -1,8 +1,5 @@ using System.Globalization; -using System.Numerics; using Just.PreciseMath.Tests.ReferenceData; -using Shouldly; -using Xunit; namespace Just.PreciseMath.Tests; diff --git a/1-tests/Just.PreciseMath.Tests/PreciseMathReciprocalTests.cs b/1-tests/Just.PreciseMath.Tests/PreciseMathReciprocalTests.cs index ab63d4a..ded089b 100644 --- a/1-tests/Just.PreciseMath.Tests/PreciseMathReciprocalTests.cs +++ b/1-tests/Just.PreciseMath.Tests/PreciseMathReciprocalTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class PreciseMathReciprocalTests diff --git a/1-tests/Just.PreciseMath.Tests/PreciseMathSqrtTests.cs b/1-tests/Just.PreciseMath.Tests/PreciseMathSqrtTests.cs index f48c1f2..94fbcb8 100644 --- a/1-tests/Just.PreciseMath.Tests/PreciseMathSqrtTests.cs +++ b/1-tests/Just.PreciseMath.Tests/PreciseMathSqrtTests.cs @@ -1,7 +1,3 @@ -using System.Numerics; -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class PreciseMathSqrtTests diff --git a/1-tests/Just.PreciseMath.Tests/PreciseMathTests.cs b/1-tests/Just.PreciseMath.Tests/PreciseMathTests.cs index 66ca109..ba030f8 100644 --- a/1-tests/Just.PreciseMath.Tests/PreciseMathTests.cs +++ b/1-tests/Just.PreciseMath.Tests/PreciseMathTests.cs @@ -1,6 +1,3 @@ -using Shouldly; -using Xunit; - namespace Just.PreciseMath.Tests; public class PreciseMathTests diff --git a/1-tests/Just.PreciseMath.Tests/ReferenceData/ExpReferenceData.cs b/1-tests/Just.PreciseMath.Tests/ReferenceData/ExpReferenceData.cs index 14e5238..132e5cc 100644 --- a/1-tests/Just.PreciseMath.Tests/ReferenceData/ExpReferenceData.cs +++ b/1-tests/Just.PreciseMath.Tests/ReferenceData/ExpReferenceData.cs @@ -1,5 +1,3 @@ -using Xunit; - namespace Just.PreciseMath.Tests.ReferenceData; // Generated by generate_exp.py; do not derive expected values from DD arithmetic. diff --git a/1-tests/Just.PreciseMath.Tests/ReferenceData/ExponentialFunctionsReferenceData.cs b/1-tests/Just.PreciseMath.Tests/ReferenceData/ExponentialFunctionsReferenceData.cs index 1de9ca4..ed22dd5 100644 --- a/1-tests/Just.PreciseMath.Tests/ReferenceData/ExponentialFunctionsReferenceData.cs +++ b/1-tests/Just.PreciseMath.Tests/ReferenceData/ExponentialFunctionsReferenceData.cs @@ -9,8 +9,6 @@ // absolute; reference rounding uncertainty is bounded by 2^-350 relative. // Tiny corrections to 1/-1 below 120 digits are not component-retention oracles. // Signed zeros, infinities, NaNs, and enormous arguments are tested separately. -using Xunit; - namespace Just.PreciseMath.Tests.ReferenceData; internal static class ExponentialFunctionsReferenceData diff --git a/1-tests/Just.PreciseMath.Tests/ReferenceData/LogReferenceData.cs b/1-tests/Just.PreciseMath.Tests/ReferenceData/LogReferenceData.cs index e120849..c4002e3 100644 --- a/1-tests/Just.PreciseMath.Tests/ReferenceData/LogReferenceData.cs +++ b/1-tests/Just.PreciseMath.Tests/ReferenceData/LogReferenceData.cs @@ -1,7 +1,5 @@ // Generated by generate_log.py; do not hand-edit reference literals. // Exact binary64 sums; Decimal.ln at 450/650 digits, rounded to 120 digits. -using Xunit; - namespace Just.PreciseMath.Tests.ReferenceData; internal static class LogReferenceData diff --git a/1-tests/Just.PreciseMath.Tests/ReferenceData/RealPowReferenceData.cs b/1-tests/Just.PreciseMath.Tests/ReferenceData/RealPowReferenceData.cs index 5719924..ce1c580 100644 --- a/1-tests/Just.PreciseMath.Tests/ReferenceData/RealPowReferenceData.cs +++ b/1-tests/Just.PreciseMath.Tests/ReferenceData/RealPowReferenceData.cs @@ -1,8 +1,6 @@ // Generated by generate_real_pow.py; do not hand-edit reference literals. // Exact binary64 sums; Decimal ln/exp at 450/650 digits, rounded to 120 digits. // Reference uncertainty is explicitly allowed as 2^-350 relative in the tests. -using Xunit; - namespace Just.PreciseMath.Tests.ReferenceData; internal static class RealPowReferenceData diff --git a/1-tests/Just.PreciseMath.Tests/ReferenceData/RootNReferenceData.cs b/1-tests/Just.PreciseMath.Tests/ReferenceData/RootNReferenceData.cs index 913bc5a..c868645 100644 --- a/1-tests/Just.PreciseMath.Tests/ReferenceData/RootNReferenceData.cs +++ b/1-tests/Just.PreciseMath.Tests/ReferenceData/RootNReferenceData.cs @@ -1,7 +1,5 @@ // Generated by generate_rootn.py; do not hand-edit reference literals. // Exact component sums; Decimal ln/exp at 450/650 digits, rounded to 120 digits. -using Xunit; - namespace Just.PreciseMath.Tests.ReferenceData; internal static class RootNReferenceData diff --git a/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleAdditionChecks.cs b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleAdditionChecks.cs new file mode 100644 index 0000000..9b993ea --- /dev/null +++ b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleAdditionChecks.cs @@ -0,0 +1,27 @@ +namespace Just.PreciseMath.Tests.SanityChecks; + +public class DoubleDoubleAdditionChecks +{ + public static IEnumerable> ReferenceCases => + [ + (new DoubleDouble(1.0), new DoubleDouble(1.0), new DoubleDouble(2.0)), + (new DoubleDouble(1.0), new DoubleDouble(2.0), new DoubleDouble(3.0)), + (new DoubleDouble(1.25), new DoubleDouble(2.5), new DoubleDouble(3.75)), + (new DoubleDouble(65535.0), new DoubleDouble(1.0), new DoubleDouble(65536.0)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), DoubleDouble.Parse("-812563124576179134504", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(42.0)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(127.0), DoubleDouble.Parse("812563124576179134673", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(27.0), DoubleDouble.Parse("812563124576179134573", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(2.70), DoubleDouble.Parse("812563124576179134548.7", System.Globalization.CultureInfo.InvariantCulture)), + ]; + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void AdditionShouldProduceExpectedResults(DoubleDouble left, DoubleDouble right, DoubleDouble expected) + { + DoubleDouble result = left + right; + + result.High.ShouldBe(expected.High); + result.Low.ShouldBe(expected.Low); + result.ShouldBe(expected); + } +} diff --git a/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleArithmeticEqualityChecks.cs b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleArithmeticEqualityChecks.cs new file mode 100644 index 0000000..c71e6f2 --- /dev/null +++ b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleArithmeticEqualityChecks.cs @@ -0,0 +1,108 @@ +namespace Just.PreciseMath.Tests.SanityChecks; + +public class DoubleDoubleArithmeticEqualityChecks +{ + public static IEnumerable> ReferenceCases => [1.0, 2.0, 3.0, 100.0, 0.1, 0.5, 0.333333333333333333, -1.0, 1553.0, 1.0e+10]; + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void AdditionShouldProduceIdenticalResults(double reference) + { + DoubleDouble testValue = DoubleDouble.E; // any value with full precision range used + + DoubleDouble ddxdd_Result = testValue + (new DoubleDouble(reference)); + DoubleDouble ddxd_Result = testValue + reference; + DoubleDouble dxdd_Result = reference + testValue; + + ddxdd_Result.High.ShouldBe(ddxd_Result.High); + ddxdd_Result.Low.ShouldBe(ddxd_Result.Low); + ddxdd_Result.ShouldBe(ddxd_Result); + + ddxdd_Result.High.ShouldBe(dxdd_Result.High); + ddxdd_Result.Low.ShouldBe(dxdd_Result.Low); + ddxdd_Result.ShouldBe(dxdd_Result); + + ddxd_Result.High.ShouldBe(dxdd_Result.High); + ddxd_Result.Low.ShouldBe(dxdd_Result.Low); + ddxd_Result.ShouldBe(dxdd_Result); + } + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void SubtractionFromShouldProduceIdenticalResults(double reference) + { + DoubleDouble testValue = DoubleDouble.E; // any value with full precision range used + + DoubleDouble ddxdd_Result = testValue - (new DoubleDouble(reference)); + DoubleDouble ddxd_Result = testValue - reference; + + ddxdd_Result.High.ShouldBe(ddxd_Result.High); + ddxdd_Result.Low.ShouldBe(ddxd_Result.Low); + ddxdd_Result.ShouldBe(ddxd_Result); + } + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void SubtractionShouldProduceIdenticalResults(double reference) + { + DoubleDouble testValue = DoubleDouble.E; // any value with full precision range used + + DoubleDouble ddxdd_Result = (new DoubleDouble(reference)) - testValue; + DoubleDouble dxdd_Result = reference - testValue; + + ddxdd_Result.High.ShouldBe(dxdd_Result.High); + ddxdd_Result.Low.ShouldBe(dxdd_Result.Low); + ddxdd_Result.ShouldBe(dxdd_Result); + } + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void MultiplicationShouldProduceIdenticalResults(double reference) + { + DoubleDouble testValue = DoubleDouble.E; // any value with full precision range used + + DoubleDouble ddxdd_Result = testValue * (new DoubleDouble(reference)); + DoubleDouble ddxd_Result = testValue * reference; + DoubleDouble dxdd_Result = reference * testValue; + + ddxdd_Result.High.ShouldBe(ddxd_Result.High); + ddxdd_Result.Low.ShouldBe(ddxd_Result.Low); + ddxdd_Result.ShouldBe(ddxd_Result); + + ddxdd_Result.High.ShouldBe(dxdd_Result.High); + ddxdd_Result.Low.ShouldBe(dxdd_Result.Low); + ddxdd_Result.ShouldBe(dxdd_Result); + + ddxd_Result.High.ShouldBe(dxdd_Result.High); + ddxd_Result.Low.ShouldBe(dxdd_Result.Low); + ddxd_Result.ShouldBe(dxdd_Result); + } + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void DivisionShouldProduceIdenticalResults(double reference) + { + DoubleDouble testValue = DoubleDouble.E; // any value with full precision range used + + DoubleDouble ddxdd_Result = testValue / (new DoubleDouble(reference)); + DoubleDouble ddxd_Result = testValue / reference; + + ddxdd_Result.High.ShouldBe(ddxd_Result.High); + ddxdd_Result.Low.ShouldBe(ddxd_Result.Low); + ddxdd_Result.ShouldBe(ddxd_Result); + } + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void DivisionByShouldProduceIdenticalResults(double reference) + { + DoubleDouble testValue = DoubleDouble.E; // any value with full precision range used + + DoubleDouble ddxdd_Result = (new DoubleDouble(reference)) / testValue; + DoubleDouble dxdd_Result = reference / testValue; + + ddxdd_Result.High.ShouldBe(dxdd_Result.High); + ddxdd_Result.Low.ShouldBe(dxdd_Result.Low); + ddxdd_Result.ShouldBe(dxdd_Result); + } +} diff --git a/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleDivisionChecks.cs b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleDivisionChecks.cs new file mode 100644 index 0000000..7891033 --- /dev/null +++ b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleDivisionChecks.cs @@ -0,0 +1,29 @@ +namespace Just.PreciseMath.Tests.SanityChecks; + +public class DoubleDoubleDivisionChecks +{ + public static IEnumerable> ReferenceCases => + [ + (new DoubleDouble(6.0), new DoubleDouble(2.0), new DoubleDouble(3.0)), + (new DoubleDouble(6.0), new DoubleDouble(3.0), new DoubleDouble(2.0)), + (new DoubleDouble(5.0), new DoubleDouble(2.0), new DoubleDouble(2.5)), + (new DoubleDouble(1.0), new DoubleDouble(4.0), new DoubleDouble(0.25)), + (new DoubleDouble(3.75), new DoubleDouble(1.25), new DoubleDouble(3.0)), + (new DoubleDouble(65536.0), new DoubleDouble(1024.0), new DoubleDouble(64.0)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(2.0), DoubleDouble.Parse("406281562288089567273", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(4.0), DoubleDouble.Parse("203140781144044783636.5", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(8.0), DoubleDouble.Parse("101570390572022391818.25", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(1.0)), + ]; + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void DivisionShouldProduceExpectedResults(DoubleDouble left, DoubleDouble right, DoubleDouble expected) + { + DoubleDouble result = left / right; + + result.High.ShouldBe(expected.High); + result.Low.ShouldBe(expected.Low); + result.ShouldBe(expected); + } +} diff --git a/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleMultiplicationChecks.cs b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleMultiplicationChecks.cs new file mode 100644 index 0000000..2f8892b --- /dev/null +++ b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleMultiplicationChecks.cs @@ -0,0 +1,29 @@ +namespace Just.PreciseMath.Tests.SanityChecks; + +public class DoubleDoubleMultiplicationChecks +{ + public static IEnumerable> ReferenceCases => + [ + (new DoubleDouble(1.0), new DoubleDouble(1.0), new DoubleDouble(1.0)), + (new DoubleDouble(2.0), new DoubleDouble(3.0), new DoubleDouble(6.0)), + (new DoubleDouble(1.25), new DoubleDouble(2.5), new DoubleDouble(3.125)), + (new DoubleDouble(-1.25), new DoubleDouble(2.5), new DoubleDouble(-3.125)), + (new DoubleDouble(65535.0), new DoubleDouble(65536.0), new DoubleDouble(4294901760.0)), + (new DoubleDouble(65536.0), new DoubleDouble(65536.0), new DoubleDouble(4294967296.0)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(2.0), DoubleDouble.Parse("1625126249152358269092", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(0.5), DoubleDouble.Parse("406281562288089567273", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(27.0), DoubleDouble.Parse("21939204363556836632742", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(127.0), DoubleDouble.Parse("103195516821174750087342", System.Globalization.CultureInfo.InvariantCulture)), + ]; + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void MultiplicationShouldProduceExpectedResults(DoubleDouble left, DoubleDouble right, DoubleDouble expected) + { + DoubleDouble result = left * right; + + result.High.ShouldBe(expected.High); + result.Low.ShouldBe(expected.Low); + result.ShouldBe(expected); + } +} diff --git a/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleSubtractionChecks.cs b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleSubtractionChecks.cs new file mode 100644 index 0000000..ca8943b --- /dev/null +++ b/1-tests/Just.PreciseMath.Tests/SanityChecks/DoubleDoubleSubtractionChecks.cs @@ -0,0 +1,30 @@ +namespace Just.PreciseMath.Tests.SanityChecks; + +public class DoubleDoubleSubtractionChecks +{ + public static IEnumerable> ReferenceCases => + [ + (new DoubleDouble(2.0), new DoubleDouble(1.0), new DoubleDouble(1.0)), + (new DoubleDouble(1.0), new DoubleDouble(2.0), new DoubleDouble(-1.0)), + (new DoubleDouble(3.75), new DoubleDouble(2.5), new DoubleDouble(1.25)), + (new DoubleDouble(2.5), new DoubleDouble(3.75), new DoubleDouble(-1.25)), + (new DoubleDouble(65536.0), new DoubleDouble(1.0), new DoubleDouble(65535.0)), + (new DoubleDouble(65536.0), new DoubleDouble(65535.0), new DoubleDouble(1.0)), + (DoubleDouble.Parse("812563124576179134673", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(127.0), DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(127.0), DoubleDouble.Parse("812563124576179134419", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), DoubleDouble.Parse("812563124576179134504", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(42.0)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(0.5), DoubleDouble.Parse("812563124576179134545.5", System.Globalization.CultureInfo.InvariantCulture)), + (DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), DoubleDouble.Parse("812563124576179134546", System.Globalization.CultureInfo.InvariantCulture), new DoubleDouble(0.0)), + ]; + + [Theory] + [MemberData(nameof(ReferenceCases))] + public void SubtractionShouldProduceExpectedResults(DoubleDouble left, DoubleDouble right, DoubleDouble expected) + { + DoubleDouble result = left - right; + + result.High.ShouldBe(expected.High); + result.Low.ShouldBe(expected.Low); + result.ShouldBe(expected); + } +} diff --git a/AGENTS.md b/AGENTS.md index 87a3362..d0fef2c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -16,12 +16,6 @@ Do not claim API completeness or accuracy beyond tested contracts. comparisons; use the Dry job for smoke validation, not performance conclusions. - Root `Directory.Build.props` holds shared settings. Each numbered directory's props explicitly imports it; preserve this import chain. -- `review-legacy/`: optional local reference material, excluded by `.gitignore`. - Start with `review-legacy/Review-Revisited-DoubleDouble.md` when present. The code - and proposed fixes contain known defects: reproduce findings against current - code rather than treating them as correctness oracles. Do not compile, copy - wholesale, or force-add these files. If absent, proceed without them; do not - invent their contents or make validation depend on them. - Use the ignored, repository-local `.hermes/` directory for plans, checklists, investigation notes, and handoff context. Create or update notes as useful; keep them concise and revalidate them against current files. Optional diff --git a/README.md b/README.md index 2e34be6..036e13e 100644 --- a/README.md +++ b/README.md @@ -24,11 +24,15 @@ not perform double-double arithmetic or allocate on the heap. a `double`. Addition retains residuals under cancellation; multiplication uses fused multiply-add; division uses residual corrections. Mixed `double` operators use specialized scalar paths rather than promoting the scalar to `DoubleDouble`. - Their finite fast paths normalize once with a final sum transform; scalar - division uses one compensated quotient correction within the error contract below. + Division retains the complete first remainder and applies two quotient corrections, + normalizing before the final correction. Mixed addition and subtraction also retain + intermediate sum residuals through final normalization. Mixed addition, subtraction, + and division produce the same component bits as their `DoubleDouble` operations + with the scalar represented as a zero-low pair. - Exponent boundaries: bounded `BigInteger` calculations avoid intermediate overflow and underflow on the exceptional finite path. Ordinary arithmetic uses - floating-point transforms without allocations. The stored value remains two + floating-point transforms without allocations, although sparse division correction + products can also reach a boundary path. The stored value remains two doubles; this is not an arbitrary-precision API. - Comparisons use both components. `Equals` treats NaNs as equal and signed zeros as equal for collections. `CompareTo` orders NaN before other values. Numerical @@ -107,8 +111,8 @@ The `DDMath` static class provides: It shares the existing `DoubleDouble.Abs` implementation. - `Reciprocal(DoubleDouble)`: returns exactly the same high and low component bits as `1.0 / value`. It specializes scalar/DD division for a numerator of one, - omitting only redundant numerator checks while preserving both divisions, - the FMA sequence, and normalization. Signed zeros map to signed infinities, + omitting redundant numerator checks and sharing the finite scalar-numerator kernel, + including both residual corrections and normalization. Signed zeros map to signed infinities, signed infinities to signed zeros, and NaN to canonical NaN. The allocating exact boundary path handles extreme exponents; finite overflow produces signed infinity. No speedup over scalar/DD division has been measured.