This commit is contained in:
@@ -12,31 +12,18 @@ public static partial class DDMath
|
||||
return DoubleDouble.Abs(value);
|
||||
}
|
||||
|
||||
/// <summary>Returns the reciprocal with double-double precision.</summary>
|
||||
/// <remarks>
|
||||
/// Specializes scalar/DD division for a numerator of one, omitting only its
|
||||
/// redundant numerator checks. The arithmetic sequence and boundary handling
|
||||
/// are unchanged, preserving the high and low component bits of <c>1.0 / value</c>.
|
||||
/// Results are approximate, not guaranteed correctly rounded;
|
||||
/// finite results are tested against a relative error bound of 2^-100 plus one
|
||||
/// minimum binary64 subnormal. Precision decreases near underflow.
|
||||
/// Signed zeros map to signed infinities, signed infinities to signed zeros,
|
||||
/// and NaN to canonical NaN. Finite overflow produces signed infinity.
|
||||
/// </remarks>
|
||||
/// <inheritdoc cref="DoubleDouble.Square"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Square(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Square(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Reciprocal"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Reciprocal(DoubleDouble value)
|
||||
{
|
||||
const double one = 1.0;
|
||||
if (!DoubleDouble.IsFinite(value) || value._high == 0.0)
|
||||
{
|
||||
return new DoubleDouble(one / value._high);
|
||||
}
|
||||
if (!PreciseMathHelper.IsDivisionWithinFastRange(value._high))
|
||||
{
|
||||
return PreciseMathHelper.DivideBoundary(one, value);
|
||||
}
|
||||
|
||||
return PreciseMathHelper.DivideScalarFinite(one, value);
|
||||
return DoubleDouble.Reciprocal(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Cbrt"/>
|
||||
|
||||
@@ -6,6 +6,43 @@ public readonly partial struct 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)
|
||||
{
|
||||
|
||||
@@ -22,6 +22,37 @@ public readonly partial struct DoubleDouble : IFormattable
|
||||
return ToString(null, provider);
|
||||
}
|
||||
|
||||
/// <summary>Returns the exact decimal expansion of the stored value using invariant culture.</summary>
|
||||
/// <remarks>
|
||||
/// Uses allocating integer arithmetic, without rounding or scientific notation.
|
||||
/// Omits unnecessary fractional zeros and preserves negative zero; nonfinite
|
||||
/// values use NaN, Infinity, and -Infinity. Sparse pairs can require over a
|
||||
/// thousand characters. Parse with invariant culture to recover the components;
|
||||
/// this is an exact-value format, not a shortest-round-trip format.
|
||||
/// </remarks>
|
||||
public string ToStringExact()
|
||||
{
|
||||
if (!double.IsFinite(_high) || _high == 0.0)
|
||||
{
|
||||
return _high.ToString(CultureInfo.InvariantCulture);
|
||||
}
|
||||
|
||||
(BigInteger numerator, BigInteger denominator) = ConversionFraction();
|
||||
// A sum of binary64 values has a power-of-two denominator. Multiplying
|
||||
// by the matching power of five makes an integer coefficient over 10^n.
|
||||
int decimalPlaces = (int)(denominator.GetBitLength() - 1);
|
||||
BigInteger coefficient = BigInteger.Abs(numerator) * BigInteger.Pow(5, decimalPlaces);
|
||||
string digits = coefficient.ToString(CultureInfo.InvariantCulture);
|
||||
int end = digits.Length;
|
||||
while (decimalPlaces > 0 && digits[end - 1] == '0')
|
||||
{
|
||||
end--;
|
||||
decimalPlaces--;
|
||||
}
|
||||
string sign = numerator.Sign < 0 ? "-" : string.Empty;
|
||||
return sign + FormattingFixed(digits[..end], decimalPlaces, ".");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Formats the exact component sum, rounding to nearest with ties to even.
|
||||
/// Supports G/g (significant digits, default 32; G0 also means 32), E/e
|
||||
|
||||
@@ -5,6 +5,33 @@ public readonly partial struct DoubleDouble
|
||||
/// <summary>Gets the binary radix of the components.</summary>
|
||||
public static int Radix => 2;
|
||||
|
||||
/// <summary>Returns the reciprocal with double-double precision.</summary>
|
||||
/// <remarks>
|
||||
/// Specializes scalar/DD division for a numerator of one, omitting only its
|
||||
/// redundant numerator checks. The arithmetic sequence and boundary handling
|
||||
/// are unchanged, preserving the high and low component bits of <c>1.0 / value</c>.
|
||||
/// Results are approximate, not guaranteed correctly rounded;
|
||||
/// finite results are tested against a relative error bound of 2^-100 plus one
|
||||
/// minimum binary64 subnormal. Precision decreases near underflow.
|
||||
/// Signed zeros map to signed infinities, signed infinities to signed zeros,
|
||||
/// and NaN to canonical NaN. Finite overflow produces signed infinity.
|
||||
/// </remarks>
|
||||
[Pure]
|
||||
public static DoubleDouble Reciprocal(DoubleDouble value)
|
||||
{
|
||||
const double one = 1.0;
|
||||
if (!DoubleDouble.IsFinite(value) || value._high == 0.0)
|
||||
{
|
||||
return new DoubleDouble(one / value._high);
|
||||
}
|
||||
if (!PreciseMathHelper.IsDivisionWithinFastRange(value._high))
|
||||
{
|
||||
return PreciseMathHelper.DivideBoundary(one, value);
|
||||
}
|
||||
|
||||
return PreciseMathHelper.DivideScalarFinite(one, value);
|
||||
}
|
||||
|
||||
/// <summary>Returns the absolute value, preserving both components and canonicalizing NaN.</summary>
|
||||
public static DoubleDouble Abs(DoubleDouble value)
|
||||
{
|
||||
|
||||
@@ -244,7 +244,8 @@ public readonly partial struct DoubleDouble : IRootFunctions<DoubleDouble>
|
||||
|
||||
/// <summary>Returns the nonnegative square root with double-double precision.</summary>
|
||||
/// <remarks>
|
||||
/// Uses power-of-two scaling and an FMA-based Newton correction. Results are
|
||||
/// Uses power-of-two scaling and an FMA-based Newton correction. An exponent-
|
||||
/// tracked correction retains sparse lows endangered by downscaling. Results are
|
||||
/// approximate, not guaranteed correctly rounded; finite positive inputs are
|
||||
/// tested against a relative error bound of 2^-100 across the binary64 range.
|
||||
/// Signed zero and positive infinity are preserved. Negative nonzero values
|
||||
@@ -264,11 +265,19 @@ public readonly partial struct DoubleDouble : IRootFunctions<DoubleDouble>
|
||||
|
||||
// Choose an even exponent, rounding negative odd exponents down too.
|
||||
// For positive finite normalized inputs, exponent is in [-1074, 1022]
|
||||
// and the scaled high is in [1, 4). Scaling the high is exact; any
|
||||
// underflow in a very sparse low is far below the relative error bound.
|
||||
// and the scaled high is in [1, 4). Scaling the high is exact.
|
||||
int exponent = Math.ILogB(value.High) & ~1;
|
||||
double high = Math.ScaleB(value.High, -exponent);
|
||||
double low = Math.ScaleB(value.Low, -exponent);
|
||||
// Dividing the scaled low by 2*estimate (in [2, 4]) can round at the
|
||||
// subnormal floor before output rescaling restores its magnitude. In
|
||||
// that sparse domain, refine high alone and apply the original low with
|
||||
// separate exponents afterward. Never add both the scaled and original low.
|
||||
bool separateLow = exponent > 0 && value.Low != 0.0 && Math.Abs(low) < Math.ScaleB(1.0, -1020);
|
||||
if (separateLow)
|
||||
{
|
||||
low = 0.0;
|
||||
}
|
||||
double estimate = Math.Sqrt(high);
|
||||
|
||||
// Estimate is in [1, 2]. FMA avoids rounding its square before
|
||||
@@ -282,7 +291,10 @@ public readonly partial struct DoubleDouble : IRootFunctions<DoubleDouble>
|
||||
// Normalize again to canonicalize a zero low, including any underflow
|
||||
// of an exceptionally sparse correction during rescaling.
|
||||
int rootExponent = exponent / 2;
|
||||
return DoubleDouble.FromComponents(Math.ScaleB(root.High, rootExponent), Math.ScaleB(root.Low, rootExponent));
|
||||
root = DoubleDouble.FromComponents(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;
|
||||
}
|
||||
|
||||
/// <summary>Returns the reciprocal square root with double-double precision.</summary>
|
||||
|
||||
Reference in New Issue
Block a user