Files
Just.PreciseMath/0-source/Just.PreciseMath/DoubleDouble.cs
T
just 720fd1e21f
.NET Test / .NET tests (push) Successful in 2m15s
one-liners use expression body
2026-09-24 00:57:02 +04:00

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;
}
}