added sanity checks
.NET Test / .NET tests (push) Successful in 3m55s

This commit is contained in:
2026-09-18 14:10:24 +04:00
parent b5a8bd2580
commit 5797bf4884
44 changed files with 419 additions and 146 deletions
+1 -10
View File
@@ -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);
}
/// <inheritdoc cref="DoubleDouble.Cbrt"/>
@@ -103,24 +103,28 @@ public readonly partial struct DoubleDouble :
}
/// <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)
{
return PreciseMathHelper.AddScalar(left._high, left._low, right);
}
/// <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)
{
return right + left;
}
/// <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)
{
return PreciseMathHelper.AddScalar(left._high, left._low, -right);
}
/// <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)
{
// Negate the components, not the result: exact cancellation must yield +0.
@@ -153,7 +157,8 @@ public readonly partial struct DoubleDouble :
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)
{
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;
}
/// <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)
{
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);
}
}
+27 -6
View File
@@ -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,