small arithmetic optimization
.NET Test / .NET tests (push) Successful in 1m56s

This commit is contained in:
2026-09-20 00:52:24 +04:00
parent 34076c2466
commit 75925ab184
18 changed files with 1366 additions and 21 deletions
@@ -147,7 +147,14 @@ public readonly partial struct DoubleDouble :
// the first product and subtraction gives |remainder.High| <= 2^457
// at the first correction, hence |correction| <= 2^908 and a finite sum
// below 2^909 in magnitude. Retain normalization before the final add.
return PreciseMathHelper.NormalizeFinite(quotient, correction) + finalCorrection;
// The correlated correction product (including its exact fallback) has
// |High| <= 2^460, giving |finalRemainder| <= 2^462 and |finalCorrection|
// <= 2^912. Both final-add highs therefore pass the addition range guard.
// More tightly, the first product differs from left.High by < 4u*|left.High|,
// so |remainder.High| < 16u*|left.High| and |correction/quotient| < 32u
// (u=2^-53, including underflow). The normalized quotient cannot be zero.
DoubleDouble partial = PreciseMathHelper.NormalizeFinite(quotient, correction);
return PreciseMathHelper.AddScalarFinite(partial._high, partial._low, finalCorrection);
}
/// <summary>Applies the expansion operation without discarding the low component.</summary>
@@ -231,8 +238,10 @@ public readonly partial struct DoubleDouble :
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;
// before adding the last, preserving the DD/DD evaluation order. Its high
// is nonzero and < 2^909; |finalCorrection| <= 2^912 fits scalar addition.
DoubleDouble partial = PreciseMathHelper.NormalizeFinite(quotient, correction);
return PreciseMathHelper.AddScalarFinite(partial._high, partial._low, finalCorrection);
}
/// <summary>Divides a scalar using a quotient estimate and two residual corrections.</summary>
@@ -1,10 +1,18 @@
namespace Just.PreciseMath;
public readonly partial struct DoubleDouble : IComparable<DoubleDouble>, IComparable,
public readonly partial struct DoubleDouble :
IEquatable<DoubleDouble>,
IEqualityOperators<DoubleDouble, DoubleDouble, bool>,
IComparable<DoubleDouble>, IComparable,
IComparisonOperators<DoubleDouble, DoubleDouble, bool>
{
/// <inheritdoc/>
[Pure]
public bool Equals(DoubleDouble other)
{
return _high.Equals(other._high) && _low.Equals(other._low);
}
/// <summary>
/// Tests numerical equality; NaN operands are never equal.
/// </summary>
@@ -285,13 +285,18 @@ public readonly partial struct DoubleDouble : IRootFunctions<DoubleDouble>
// where u=2^-53; both components of the input contribute to the residual.
double residual = Math.FusedMultiplyAdd(-estimate, estimate, high) + low;
double correction = residual / (2.0 * estimate);
DoubleDouble root = DoubleDouble.FromComponents(estimate, correction);
// Scaled |low| <= 2^-51 and the rounded sqrt seed's squared error
// is < 2^-49, so |correction| < 2^-48. Both inputs and their sum
// are finite (sum < 4); retain magnitude-aware normalization.
DoubleDouble root = PreciseMathHelper.NormalizeFinite(estimate, correction);
// The root high stays normal, even if rounding reaches the next binade.
// Normalize again to canonicalize a zero low, including any underflow
// of an exceptionally sparse correction during rescaling.
int rootExponent = exponent / 2;
root = DoubleDouble.FromComponents(Math.ScaleB(root.High, rootExponent), Math.ScaleB(root.Low, rootExponent));
// rootExponent is in [-537, 511]. With root.High < 4 and normalized
// |root.Low| <= 2^-52, scaled components and their sum stay below 2^514.
root = PreciseMathHelper.NormalizeFinite(Math.ScaleB(root.High, rootExponent), Math.ScaleB(root.Low, rootExponent));
// In this branch |originalLow/originalHigh| < 2^-1020. The omitted
// quadratic term is negligible even relative to the low correction.
return separateLow ? root + RootLowCorrection(value, root, 2) : root;
@@ -81,12 +81,6 @@ public readonly partial struct DoubleDouble :
public readonly double Low => _low;
/// <inheritdoc/>
[Pure]
public bool Equals(DoubleDouble other)
{
return _high.Equals(other._high) && _low.Equals(other._low);
}
/// <inheritdoc/>
[Pure]
public override bool Equals(object? obj)
+16 -3
View File
@@ -118,8 +118,11 @@ internal static class PreciseMathHelper
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;
// DD/DD's guard-derived bounds apply, including exact correction products:
// the normalized high is nonzero and < 2^909; |finalCorrection| <= 2^912.
// Keep normalization before scalar addition, without repeating its dispatch.
DoubleDouble partial = NormalizeFinite(quotient, correction);
return AddScalarFinite(partial._high, partial._low, finalCorrection);
}
// Only for division's second remainder: normalized finite inputs with highs
@@ -149,7 +152,7 @@ internal static class PreciseMathHelper
// Both components and their rounded sum must be finite. Unlike QuickTwoSum,
// this entry point permits either magnitude order, including cancellation.
// AddFinite and DD division establish these bounds; this helper does not validate
// Arithmetic and Sqrt callers establish these bounds; this helper does not validate
// them or canonicalize NaN/infinity. Use DoubleDouble.FromComponents for arbitrary pairs.
// Retain the high zero's sign when low is zero, as FromComponents does.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
@@ -181,6 +184,16 @@ internal static class PreciseMathHelper
return AddScalarBoundary(high, low, value);
}
return AddScalarFinite(high, low, value);
}
// Normalized finite (high, low), with a negated zero low also allowed.
// high and value must pass the addition range guard and not both be zero.
// Public scalar addition checks these conditions; division establishes them
// before its final correction. Preserve all three transforms and grouping.
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static DoubleDouble AddScalarFinite(double high, double low, double value)
{
(double sum, double error) = TwoAdd(high, value);
// Specialize AddFinite for a zero-low scalar: adding that zero to low
// has no residual. Retain the rounding error when combining the high