@@ -36,16 +36,7 @@ public static partial class DDMath
|
|||||||
return PreciseMathHelper.DivideBoundary(one, value);
|
return PreciseMathHelper.DivideBoundary(one, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
double quotient = one / value._high;
|
return PreciseMathHelper.DivideScalarFinite(one, value);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc cref="DoubleDouble.Cbrt"/>
|
/// <inheritdoc cref="DoubleDouble.Cbrt"/>
|
||||||
|
|||||||
@@ -103,24 +103,28 @@ public readonly partial struct DoubleDouble :
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
||||||
|
/// <remarks>Preserves the component bits of addition with the equivalent zero-low scalar expansion.</remarks>
|
||||||
public static DoubleDouble operator +(DoubleDouble left, double right)
|
public static DoubleDouble operator +(DoubleDouble left, double right)
|
||||||
{
|
{
|
||||||
return PreciseMathHelper.AddScalar(left._high, left._low, right);
|
return PreciseMathHelper.AddScalar(left._high, left._low, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
||||||
|
/// <remarks>Preserves the component bits of addition with the equivalent zero-low scalar expansion.</remarks>
|
||||||
public static DoubleDouble operator +(double left, DoubleDouble right)
|
public static DoubleDouble operator +(double left, DoubleDouble right)
|
||||||
{
|
{
|
||||||
return right + left;
|
return right + left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
||||||
|
/// <remarks>Preserves the component bits of subtraction with the equivalent zero-low scalar expansion.</remarks>
|
||||||
public static DoubleDouble operator -(DoubleDouble left, double right)
|
public static DoubleDouble operator -(DoubleDouble left, double right)
|
||||||
{
|
{
|
||||||
return PreciseMathHelper.AddScalar(left._high, left._low, -right);
|
return PreciseMathHelper.AddScalar(left._high, left._low, -right);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
||||||
|
/// <remarks>Preserves the component bits of subtraction with the equivalent zero-low scalar expansion.</remarks>
|
||||||
public static DoubleDouble operator -(double left, DoubleDouble right)
|
public static DoubleDouble operator -(double left, DoubleDouble right)
|
||||||
{
|
{
|
||||||
// Negate the components, not the result: exact cancellation must yield +0.
|
// Negate the components, not the result: exact cancellation must yield +0.
|
||||||
@@ -153,7 +157,8 @@ public readonly partial struct DoubleDouble :
|
|||||||
return right * left;
|
return right * left;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
/// <summary>Divides by a scalar using a quotient estimate and two residual corrections.</summary>
|
||||||
|
/// <remarks>Preserves the component bits of division by the equivalent zero-low expansion.</remarks>
|
||||||
public static DoubleDouble operator /(DoubleDouble left, double right)
|
public static DoubleDouble operator /(DoubleDouble left, double right)
|
||||||
{
|
{
|
||||||
if (!IsFinite(left) || !double.IsFinite(right) || left._high == 0.0 || right == 0.0)
|
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 quotient = left._high / right;
|
||||||
double remainder = Math.FusedMultiplyAdd(-quotient, right, left._high);
|
// The initial product is near left.High, so the exponent guard makes
|
||||||
double correction = (remainder + left._low) / right;
|
// TwoMultiply exact. Keep the complete remainder, including its low.
|
||||||
// The exponent guard keeps the quotient normal. The correction is
|
(double product, double productError) = PreciseMathHelper.TwoMultiply(right, quotient);
|
||||||
// O(u * quotient), so QuickTwoSum is ordered; one correction gives O(u^2) error.
|
DoubleDouble remainder = PreciseMathHelper.AddFinite(left._high, left._low, -product, -productError);
|
||||||
(double high, double low) = PreciseMathHelper.TwoQuickAdd(quotient, correction);
|
double correction = remainder._high / right;
|
||||||
return new DoubleDouble(high, low == 0.0 ? 0.0 : low);
|
(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
/// <summary>Divides a scalar using a quotient estimate and two residual corrections.</summary>
|
||||||
|
/// <remarks>Preserves the component bits of division with the equivalent zero-low numerator.</remarks>
|
||||||
public static DoubleDouble operator /(double left, DoubleDouble right)
|
public static DoubleDouble operator /(double left, DoubleDouble right)
|
||||||
{
|
{
|
||||||
if (!double.IsFinite(left) || !IsFinite(right) || left == 0.0 || right._high == 0.0)
|
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);
|
return PreciseMathHelper.DivideBoundary(left, right);
|
||||||
}
|
}
|
||||||
|
|
||||||
double quotient = left / right._high;
|
return PreciseMathHelper.DivideScalarFinite(left, right);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,7 +104,25 @@ internal static class PreciseMathHelper
|
|||||||
return NormalizeFinite(sum, sumError + (middleError + lowError));
|
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.
|
// bounded by 2^462, including the correction product's exact fallback results.
|
||||||
// Same sign and normal binade imply Sterbenz-exact high subtraction. Its error
|
// Same sign and normal binade imply Sterbenz-exact high subtraction. Its error
|
||||||
// is +0; with canonical input lows, TwoAdd(+0, lowSum) is (lowSum, +0).
|
// 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);
|
(double sum, double error) = TwoAdd(high, value);
|
||||||
// Near high-component cancellation, Sterbenz makes the first sum exact,
|
// Specialize AddFinite for a zero-low scalar: adding that zero to low
|
||||||
// so error is zero and this retains low exactly. Otherwise its rounding
|
// has no residual. Retain the rounding error when combining the high
|
||||||
// contributes only O(u^2) relative error. The final TwoSum normalizes.
|
// sum's error with low, then fold it into the final normalization.
|
||||||
(double result, double residual) = TwoAdd(sum, error + low);
|
// TwoSum also absorbs a negated zero low from scalar-left subtraction.
|
||||||
return new DoubleDouble(result, residual == 0.0 ? 0.0 : residual);
|
(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,
|
// Keep the complete BigInteger expressions out of ordinary arithmetic bodies,
|
||||||
|
|||||||
@@ -18,4 +18,9 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Using Include="System.Numerics"/>
|
||||||
|
<Using Include="Shouldly"/>
|
||||||
|
<Using Include="Xunit"/>
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class ArithmeticRangeTests
|
public class ArithmeticRangeTests
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleArithmeticTests
|
public class DoubleDoubleArithmeticTests
|
||||||
@@ -332,6 +328,53 @@ public class DoubleDoubleArithmeticTests
|
|||||||
Check(6.0 / new DoubleDouble(2.0), 3.0, 0.0);
|
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]
|
[Fact]
|
||||||
public void ExpansionCancellationRetainsExactComponentsAcrossTheAdditionGuard()
|
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]
|
[Fact]
|
||||||
public void DivisionRetainsSubnormalCorrectionsWithOrdinaryHighComponents()
|
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]
|
[Fact]
|
||||||
public void SubnormalProductsWithLargeNormalsRetainExactResultsInBothOrders()
|
public void SubnormalProductsWithLargeNormalsRetainExactResultsInBothOrders()
|
||||||
{
|
{
|
||||||
@@ -884,6 +978,39 @@ public class DoubleDoubleArithmeticTests
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static (double[] Scalars, DoubleDouble[] Values) ScalarArithmeticCompatibilityCases()
|
||||||
|
{
|
||||||
|
List<double> 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<DoubleDouble> 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)
|
private static void AssertDivisionMatchesPrevious(DoubleDouble left, DoubleDouble right)
|
||||||
{
|
{
|
||||||
// Freeze the pre-specialization expression. The unchanged public operators
|
// Freeze the pre-specialization expression. The unchanged public operators
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleBoundaryTests
|
public class DoubleDoubleBoundaryTests
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleCbrtTests
|
public class DoubleDoubleCbrtTests
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleComparisonTests
|
public class DoubleDoubleComparisonTests
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleConversionTests
|
public class DoubleDoubleConversionTests
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
|
||||||
using Just.PreciseMath.Tests.ReferenceData;
|
using Just.PreciseMath.Tests.ReferenceData;
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleHypotTests
|
public class DoubleDoubleHypotTests
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleRepresentationTests
|
public class DoubleDoubleRepresentationTests
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleRootFunctionsTests
|
public class DoubleDoubleRootFunctionsTests
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
|
||||||
using Just.PreciseMath.Tests.ReferenceData;
|
using Just.PreciseMath.Tests.ReferenceData;
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleSignedNumberTests
|
public class DoubleDoubleSignedNumberTests
|
||||||
|
|||||||
@@ -1,6 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class DoubleDoubleTests
|
public class DoubleDoubleTests
|
||||||
|
|||||||
@@ -1,7 +1,4 @@
|
|||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
|
||||||
using Just.PreciseMath.Tests.ReferenceData;
|
using Just.PreciseMath.Tests.ReferenceData;
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class PreciseMathInvSqrtTests
|
public class PreciseMathInvSqrtTests
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
|
||||||
using Just.PreciseMath.Tests.ReferenceData;
|
using Just.PreciseMath.Tests.ReferenceData;
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class PreciseMathPowTests
|
public class PreciseMathPowTests
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class PreciseMathRealPowSpecialTests
|
public class PreciseMathRealPowSpecialTests
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
using System.Globalization;
|
using System.Globalization;
|
||||||
using System.Numerics;
|
|
||||||
using Just.PreciseMath.Tests.ReferenceData;
|
using Just.PreciseMath.Tests.ReferenceData;
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class PreciseMathReciprocalTests
|
public class PreciseMathReciprocalTests
|
||||||
|
|||||||
@@ -1,7 +1,3 @@
|
|||||||
using System.Numerics;
|
|
||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class PreciseMathSqrtTests
|
public class PreciseMathSqrtTests
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
using Shouldly;
|
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests;
|
namespace Just.PreciseMath.Tests;
|
||||||
|
|
||||||
public class PreciseMathTests
|
public class PreciseMathTests
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests.ReferenceData;
|
namespace Just.PreciseMath.Tests.ReferenceData;
|
||||||
|
|
||||||
// Generated by generate_exp.py; do not derive expected values from DD arithmetic.
|
// Generated by generate_exp.py; do not derive expected values from DD arithmetic.
|
||||||
|
|||||||
@@ -9,8 +9,6 @@
|
|||||||
// absolute; reference rounding uncertainty is bounded by 2^-350 relative.
|
// absolute; reference rounding uncertainty is bounded by 2^-350 relative.
|
||||||
// Tiny corrections to 1/-1 below 120 digits are not component-retention oracles.
|
// Tiny corrections to 1/-1 below 120 digits are not component-retention oracles.
|
||||||
// Signed zeros, infinities, NaNs, and enormous arguments are tested separately.
|
// Signed zeros, infinities, NaNs, and enormous arguments are tested separately.
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests.ReferenceData;
|
namespace Just.PreciseMath.Tests.ReferenceData;
|
||||||
|
|
||||||
internal static class ExponentialFunctionsReferenceData
|
internal static class ExponentialFunctionsReferenceData
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// Generated by generate_log.py; do not hand-edit reference literals.
|
// Generated by generate_log.py; do not hand-edit reference literals.
|
||||||
// Exact binary64 sums; Decimal.ln at 450/650 digits, rounded to 120 digits.
|
// Exact binary64 sums; Decimal.ln at 450/650 digits, rounded to 120 digits.
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests.ReferenceData;
|
namespace Just.PreciseMath.Tests.ReferenceData;
|
||||||
|
|
||||||
internal static class LogReferenceData
|
internal static class LogReferenceData
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// Generated by generate_real_pow.py; do not hand-edit reference literals.
|
// 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.
|
// 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.
|
// Reference uncertainty is explicitly allowed as 2^-350 relative in the tests.
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests.ReferenceData;
|
namespace Just.PreciseMath.Tests.ReferenceData;
|
||||||
|
|
||||||
internal static class RealPowReferenceData
|
internal static class RealPowReferenceData
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
// Generated by generate_rootn.py; do not hand-edit reference literals.
|
// 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.
|
// Exact component sums; Decimal ln/exp at 450/650 digits, rounded to 120 digits.
|
||||||
using Xunit;
|
|
||||||
|
|
||||||
namespace Just.PreciseMath.Tests.ReferenceData;
|
namespace Just.PreciseMath.Tests.ReferenceData;
|
||||||
|
|
||||||
internal static class RootNReferenceData
|
internal static class RootNReferenceData
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
namespace Just.PreciseMath.Tests.SanityChecks;
|
||||||
|
|
||||||
|
public class DoubleDoubleAdditionChecks
|
||||||
|
{
|
||||||
|
public static IEnumerable<TheoryDataRow<DoubleDouble, DoubleDouble, DoubleDouble>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
namespace Just.PreciseMath.Tests.SanityChecks;
|
||||||
|
|
||||||
|
public class DoubleDoubleArithmeticEqualityChecks
|
||||||
|
{
|
||||||
|
public static IEnumerable<TheoryDataRow<double>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace Just.PreciseMath.Tests.SanityChecks;
|
||||||
|
|
||||||
|
public class DoubleDoubleDivisionChecks
|
||||||
|
{
|
||||||
|
public static IEnumerable<TheoryDataRow<DoubleDouble, DoubleDouble, DoubleDouble>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
namespace Just.PreciseMath.Tests.SanityChecks;
|
||||||
|
|
||||||
|
public class DoubleDoubleMultiplicationChecks
|
||||||
|
{
|
||||||
|
public static IEnumerable<TheoryDataRow<DoubleDouble, DoubleDouble, DoubleDouble>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
namespace Just.PreciseMath.Tests.SanityChecks;
|
||||||
|
|
||||||
|
public class DoubleDoubleSubtractionChecks
|
||||||
|
{
|
||||||
|
public static IEnumerable<TheoryDataRow<DoubleDouble, DoubleDouble, DoubleDouble>> 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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.
|
comparisons; use the Dry job for smoke validation, not performance conclusions.
|
||||||
- Root `Directory.Build.props` holds shared settings. Each numbered directory's
|
- Root `Directory.Build.props` holds shared settings. Each numbered directory's
|
||||||
props explicitly imports it; preserve this import chain.
|
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,
|
- Use the ignored, repository-local `.hermes/` directory for plans, checklists,
|
||||||
investigation notes, and handoff context. Create or update notes as useful;
|
investigation notes, and handoff context. Create or update notes as useful;
|
||||||
keep them concise and revalidate them against current files. Optional
|
keep them concise and revalidate them against current files. Optional
|
||||||
|
|||||||
@@ -24,11 +24,15 @@ not perform double-double arithmetic or allocate on the heap.
|
|||||||
a `double`. Addition retains residuals under cancellation; multiplication uses
|
a `double`. Addition retains residuals under cancellation; multiplication uses
|
||||||
fused multiply-add; division uses residual corrections. Mixed `double` operators
|
fused multiply-add; division uses residual corrections. Mixed `double` operators
|
||||||
use specialized scalar paths rather than promoting the scalar to `DoubleDouble`.
|
use specialized scalar paths rather than promoting the scalar to `DoubleDouble`.
|
||||||
Their finite fast paths normalize once with a final sum transform; scalar
|
Division retains the complete first remainder and applies two quotient corrections,
|
||||||
division uses one compensated quotient correction within the error contract below.
|
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
|
- Exponent boundaries: bounded `BigInteger` calculations avoid intermediate
|
||||||
overflow and underflow on the exceptional finite path. Ordinary arithmetic uses
|
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.
|
doubles; this is not an arbitrary-precision API.
|
||||||
- Comparisons use both components. `Equals` treats NaNs as equal and signed zeros
|
- Comparisons use both components. `Equals` treats NaNs as equal and signed zeros
|
||||||
as equal for collections. `CompareTo` orders NaN before other values. Numerical
|
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.
|
It shares the existing `DoubleDouble.Abs` implementation.
|
||||||
- `Reciprocal(DoubleDouble)`: returns exactly the same high and low component bits
|
- `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,
|
as `1.0 / value`. It specializes scalar/DD division for a numerator of one,
|
||||||
omitting only redundant numerator checks while preserving both divisions,
|
omitting redundant numerator checks and sharing the finite scalar-numerator kernel,
|
||||||
the FMA sequence, and normalization. Signed zeros map to signed infinities,
|
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
|
signed infinities to signed zeros, and NaN to canonical NaN. The allocating exact boundary
|
||||||
path handles extreme exponents; finite overflow produces signed infinity.
|
path handles extreme exponents; finite overflow produces signed infinity.
|
||||||
No speedup over scalar/DD division has been measured.
|
No speedup over scalar/DD division has been measured.
|
||||||
|
|||||||
Reference in New Issue
Block a user