98 lines
3.3 KiB
C#
98 lines
3.3 KiB
C#
namespace Just.PreciseMath;
|
|
|
|
/// <summary>
|
|
/// Represents a normalized, fixed-size sum of two binary64 values.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Finite components are nonoverlapping; the high component is the rounded sum and
|
|
/// the low component retains its residual. NaN and infinities have a positive-zero
|
|
/// low component. A zero low input preserves the high zero's sign; exact nonzero
|
|
/// cancellation produces positive zero. Exponent range is limited by binary64,
|
|
/// and precision decreases near underflow. Arithmetic is not guaranteed correctly
|
|
/// rounded. Equals treats NaNs as equal for collections, while operators do not.
|
|
/// </remarks>
|
|
public readonly partial struct DoubleDouble :
|
|
ISignedNumber<DoubleDouble>
|
|
{
|
|
internal readonly double _high;
|
|
internal readonly double _low;
|
|
|
|
|
|
/// <summary>
|
|
/// Stores trusted components without normalization or validation.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Callers must supply a normalized finite pair, or a canonical NaN/infinity
|
|
/// with a positive-zero low component. Zero residuals must be positive zero.
|
|
/// Use FromComponents for arbitrary pairs or rounded arithmetic intermediates.
|
|
/// </remarks>
|
|
internal DoubleDouble(double high, double low)
|
|
{
|
|
_high = high;
|
|
_low = low;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates the normalized sum of two arbitrary components. Nonfinite sums have
|
|
/// a positive-zero low component; NaNs are canonicalized to double.NaN.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// Uses round-to-nearest, ties-to-even binary64 arithmetic. A zero low input
|
|
/// preserves the high zero's sign; exact nonzero cancellation yields positive
|
|
/// zero. Overflow produces infinity and precision decreases near underflow.
|
|
/// </remarks>
|
|
public static DoubleDouble FromComponents(double high, double low)
|
|
{
|
|
if (low == 0.0)
|
|
{
|
|
return new DoubleDouble(high);
|
|
}
|
|
|
|
double sum = high + low;
|
|
if (!double.IsFinite(sum))
|
|
{
|
|
return new DoubleDouble(sum);
|
|
}
|
|
|
|
// Magnitude-ordered QuickTwoSum, reusing the checked sum. Subtracting
|
|
// the larger input avoids TwoSum's possible intermediate overflow when
|
|
// a small opposite-sign input precedes a value near the binary64 limit.
|
|
double error = Math.Abs(high) >= Math.Abs(low)
|
|
? low - (sum - high)
|
|
: high - (sum - low);
|
|
return new DoubleDouble(sum, error == 0.0 ? 0.0 : error);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Constructs new DoubleDouble from a given double.
|
|
/// </summary>
|
|
/// <param name="high">Initial high component</param>
|
|
public DoubleDouble(double high) : this(double.IsNaN(high) ? double.NaN : high, 0.0)
|
|
{
|
|
}
|
|
|
|
/// <summary>
|
|
/// High part of DoubleDouble
|
|
/// </summary>
|
|
public readonly double High => _high;
|
|
/// <summary>
|
|
/// Low part of DoubleDouble
|
|
/// </summary>
|
|
public readonly double Low => _low;
|
|
|
|
|
|
/// <inheritdoc/>
|
|
[Pure]
|
|
public override bool Equals(object? obj) => obj is DoubleDouble other && Equals(other);
|
|
/// <inheritdoc/>
|
|
[Pure]
|
|
public override int GetHashCode() => HashCode.Combine(_high, _low);
|
|
|
|
/// <summary>Returns the normalized components.</summary>
|
|
public void Decompose(out double high, out double low)
|
|
{
|
|
high = _high;
|
|
low = _low;
|
|
}
|
|
}
|