243 lines
12 KiB
C#
243 lines
12 KiB
C#
namespace Just.PreciseMath;
|
|
|
|
public readonly partial struct DoubleDouble :
|
|
IAdditionOperators<DoubleDouble, double, DoubleDouble>,
|
|
ISubtractionOperators<DoubleDouble, double, DoubleDouble>,
|
|
IMultiplyOperators<DoubleDouble, double, DoubleDouble>,
|
|
IDivisionOperators<DoubleDouble, double, DoubleDouble>
|
|
{
|
|
/// <summary>Returns the square using a specialized double-double product.</summary>
|
|
/// <remarks>
|
|
/// Combines the equal cross terms before FMA accumulation, retaining low squared.
|
|
/// Results are approximate and may differ in low bits from value * value;
|
|
/// finite results are tested against 2^-100 relative error plus one minimum
|
|
/// binary64 subnormal, not a guarantee of correctly rounded components.
|
|
/// Either zero maps to positive zero, either infinity to positive infinity,
|
|
/// and NaN to canonical NaN. Uses the allocating multiplication fallback at
|
|
/// exponent boundaries. No performance improvement is guaranteed.
|
|
/// </remarks>
|
|
[Pure]
|
|
public static DoubleDouble Square(DoubleDouble value)
|
|
{
|
|
if (!IsFinite(value) || value._high == 0.0)
|
|
{
|
|
return new DoubleDouble(value._high * value._high);
|
|
}
|
|
if (!PreciseMathHelper.IsMultiplicationWithinFastRange(value._high, value._high))
|
|
{
|
|
return PreciseMathHelper.MultiplyBoundary(value, value);
|
|
}
|
|
|
|
(double product, double error) = PreciseMathHelper.TwoSquare(value._high);
|
|
// The guard bounds the high exponent to [-450, 450], so doubling high
|
|
// is exact and finite. FMA avoids rounding the cross product separately.
|
|
error = Math.FusedMultiplyAdd(value._high + value._high, value._low, error);
|
|
// As for multiplication, normalized input makes |error| < 4u*product;
|
|
// the positive high product is normal and the corrected sum is finite.
|
|
(double high, double low) = PreciseMathHelper.TwoQuickAdd(product, error);
|
|
// Incorporate low squared AFTER the cross term has been absorbed into
|
|
// high. Otherwise an exactly representable tail can round away before
|
|
// cancellation, e.g. (1 - 2^-54)^2 loses its 2^-108 residual.
|
|
low = Math.FusedMultiplyAdd(value._low, value._low, low);
|
|
(high, low) = PreciseMathHelper.TwoQuickAdd(high, low);
|
|
return new DoubleDouble(high, low == 0.0 ? 0.0 : low);
|
|
}
|
|
|
|
/// <summary>Returns the operand unchanged.</summary>
|
|
public static DoubleDouble operator +(DoubleDouble value)
|
|
{
|
|
return value;
|
|
}
|
|
|
|
/// <summary>Negates the value, including the high zero's sign, retaining canonical NaN and zero residuals.</summary>
|
|
public static DoubleDouble operator -(DoubleDouble value)
|
|
{
|
|
// Negation preserves normalization; only NaN and zero residuals need canonicalization.
|
|
return new DoubleDouble(double.IsNaN(value._high) ? double.NaN : -value._high,
|
|
value._low == 0.0 ? 0.0 : -value._low);
|
|
}
|
|
|
|
/// <summary>Adds normalized expansions, retaining low-sum residuals under cancellation.</summary>
|
|
public static DoubleDouble operator +(DoubleDouble left, DoubleDouble right)
|
|
{
|
|
if (!IsFinite(left) || !IsFinite(right) || (left._high == 0.0 && right._high == 0.0))
|
|
{
|
|
return new DoubleDouble(left._high + right._high);
|
|
}
|
|
if (!PreciseMathHelper.IsAdditionWithinFastRange(left._high) || !PreciseMathHelper.IsAdditionWithinFastRange(right._high))
|
|
{
|
|
return PreciseMathHelper.AddBoundary(left, right);
|
|
}
|
|
|
|
return PreciseMathHelper.AddFinite(left._high, left._low, right._high, right._low);
|
|
}
|
|
|
|
/// <summary>Subtracts normalized expansions.</summary>
|
|
public static DoubleDouble operator -(DoubleDouble left, DoubleDouble right)
|
|
{
|
|
if (!IsFinite(left) || !IsFinite(right) || (left._high == 0.0 && right._high == 0.0))
|
|
{
|
|
return new DoubleDouble(left._high - right._high);
|
|
}
|
|
if (!PreciseMathHelper.IsAdditionWithinFastRange(left._high) || !PreciseMathHelper.IsAdditionWithinFastRange(right._high))
|
|
{
|
|
return PreciseMathHelper.AddBoundary(left, -right);
|
|
}
|
|
|
|
// With a canonical left low (never -0), the low TwoSum absorbs the
|
|
// negated right zero low without changing either output component's bits.
|
|
// Avoid unary negation's intermediate canonicalization on this finite path.
|
|
return PreciseMathHelper.AddFinite(left._high, left._low, -right._high, -right._low);
|
|
}
|
|
|
|
/// <summary>Multiplies expansions using an FMA product residual and cross terms.</summary>
|
|
/// <remarks>Results are approximate double-double values, not universally correctly rounded.</remarks>
|
|
public static DoubleDouble operator *(DoubleDouble left, DoubleDouble right)
|
|
{
|
|
if (!IsFinite(left) || !IsFinite(right) || left._high == 0.0 || right._high == 0.0)
|
|
{
|
|
return new DoubleDouble(left._high * right._high);
|
|
}
|
|
if (!PreciseMathHelper.IsMultiplicationWithinFastRange(left._high, right._high))
|
|
{
|
|
return PreciseMathHelper.MultiplyBoundary(left, right);
|
|
}
|
|
|
|
(double product, double error) = PreciseMathHelper.TwoMultiply(left._high, right._high);
|
|
error = Math.FusedMultiplyAdd(left._high, right._low, error);
|
|
error = Math.FusedMultiplyAdd(left._low, right._high, error);
|
|
error = Math.FusedMultiplyAdd(left._low, right._low, error);
|
|
// With u = 2^-53, normalized inputs and the exponent-sum guard give
|
|
// |error| < 4u*|product|, including rounding at the subnormal floor.
|
|
// The product is normal and nonzero; its corrected sum remains finite.
|
|
(double high, double low) = PreciseMathHelper.TwoQuickAdd(product, error);
|
|
return new DoubleDouble(high, low == 0.0 ? 0.0 : low);
|
|
}
|
|
|
|
/// <summary>Divides expansions using a quotient estimate and two residual corrections.</summary>
|
|
/// <remarks>Zero and nonfinite operands follow binary64 rules; precision decreases near underflow.</remarks>
|
|
public static DoubleDouble operator /(DoubleDouble left, DoubleDouble right)
|
|
{
|
|
if (!IsFinite(left) || !IsFinite(right) || left._high == 0.0 || right._high == 0.0)
|
|
{
|
|
return new DoubleDouble(left._high / right._high);
|
|
}
|
|
if (!PreciseMathHelper.IsDivisionWithinFastRange(left._high) || !PreciseMathHelper.IsDivisionWithinFastRange(right._high))
|
|
{
|
|
return PreciseMathHelper.DivideBoundary(left, right);
|
|
}
|
|
|
|
double quotient = left._high / right._high;
|
|
DoubleDouble remainder = left - (right * quotient);
|
|
double correction = remainder._high / right._high;
|
|
double finalRemainder = PreciseMathHelper.SubtractDivisionCorrectionHigh(remainder, right * correction);
|
|
double finalCorrection = finalRemainder / right._high;
|
|
// The entry guard gives |quotient| <= 2^901. Conservatively bounding
|
|
// 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;
|
|
}
|
|
|
|
/// <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.
|
|
return PreciseMathHelper.AddScalar(-right._high, -right._low, left);
|
|
}
|
|
|
|
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
|
public static DoubleDouble operator *(DoubleDouble left, double right)
|
|
{
|
|
if (!IsFinite(left) || !double.IsFinite(right) || left._high == 0.0 || right == 0.0)
|
|
{
|
|
return new DoubleDouble(left._high * right);
|
|
}
|
|
if (!PreciseMathHelper.IsMultiplicationWithinFastRange(left._high, right))
|
|
{
|
|
return PreciseMathHelper.MultiplyBoundary(left, right);
|
|
}
|
|
|
|
(double product, double error) = PreciseMathHelper.TwoMultiply(left._high, right);
|
|
error = Math.FusedMultiplyAdd(left._low, right, error);
|
|
// Normalized input bounds the correction by O(u * product). The exponent
|
|
// guard keeps the high product normal and its product residual representable.
|
|
(double high, double low) = PreciseMathHelper.TwoQuickAdd(product, error);
|
|
return new DoubleDouble(high, low == 0.0 ? 0.0 : low);
|
|
}
|
|
|
|
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
|
public static DoubleDouble operator *(double left, DoubleDouble right)
|
|
{
|
|
return right * left;
|
|
}
|
|
|
|
/// <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)
|
|
{
|
|
return new DoubleDouble(left._high / right);
|
|
}
|
|
if (!PreciseMathHelper.IsDivisionWithinFastRange(left._high) || !PreciseMathHelper.IsDivisionWithinFastRange(right))
|
|
{
|
|
return PreciseMathHelper.DivideBoundary(left, right);
|
|
}
|
|
|
|
double quotient = left._high / right;
|
|
// 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>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)
|
|
{
|
|
return new DoubleDouble(left / right._high);
|
|
}
|
|
if (!PreciseMathHelper.IsDivisionWithinFastRange(left) || !PreciseMathHelper.IsDivisionWithinFastRange(right._high))
|
|
{
|
|
return PreciseMathHelper.DivideBoundary(left, right);
|
|
}
|
|
|
|
return PreciseMathHelper.DivideScalarFinite(left, right);
|
|
}
|
|
}
|