This commit is contained in:
@@ -1,46 +0,0 @@
|
||||
namespace Just.PreciseMath;
|
||||
|
||||
public static partial class DDMath
|
||||
{
|
||||
/// <inheritdoc cref="DoubleDouble.Exp"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Exp2"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp2(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp2(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Exp10"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp10(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp10(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.ExpM1"/>
|
||||
[Pure]
|
||||
public static DoubleDouble ExpM1(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.ExpM1(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Exp2M1"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp2M1(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp2M1(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Exp10M1"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp10M1(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp10M1(value);
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
namespace Just.PreciseMath;
|
||||
|
||||
/// <summary>Provides mathematical functions for normalized double-double values.</summary>
|
||||
/// <remarks>Results use fixed-size double-double precision, not arbitrary precision.</remarks>
|
||||
public static partial class DDMath
|
||||
{
|
||||
/// <summary>Returns the absolute value without discarding the low component.</summary>
|
||||
/// <remarks>Both signs of zero become positive zero; NaN is canonicalized and either infinity becomes positive infinity.</remarks>
|
||||
[Pure]
|
||||
public static DoubleDouble Abs(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Abs(value);
|
||||
}
|
||||
|
||||
/// <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)
|
||||
{
|
||||
return DoubleDouble.Reciprocal(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Cbrt"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Cbrt(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Cbrt(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Hypot(DoubleDouble, DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Hypot(DoubleDouble x, DoubleDouble y)
|
||||
{
|
||||
return DoubleDouble.Hypot(x, y);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Hypot(DoubleDouble, DoubleDouble, DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Hypot(DoubleDouble x, DoubleDouble y, DoubleDouble z)
|
||||
{
|
||||
return DoubleDouble.Hypot(x, y, z);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.RootN"/>
|
||||
[Pure]
|
||||
public static DoubleDouble RootN(DoubleDouble value, int n)
|
||||
{
|
||||
return DoubleDouble.RootN(value, n);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Sqrt"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Sqrt(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Sqrt(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.InvSqrt"/>
|
||||
[Pure]
|
||||
public static DoubleDouble InvSqrt(DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.InvSqrt(value);
|
||||
}
|
||||
}
|
||||
@@ -44,13 +44,13 @@ public readonly partial struct DoubleDouble :
|
||||
}
|
||||
|
||||
/// <summary>Returns the operand unchanged.</summary>
|
||||
public static DoubleDouble operator +(DoubleDouble value)
|
||||
public static DoubleDouble Plus(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)
|
||||
public static DoubleDouble Negate(DoubleDouble value)
|
||||
{
|
||||
// Negation preserves normalization; only NaN and zero residuals need canonicalization.
|
||||
return new DoubleDouble(double.IsNaN(value._high) ? double.NaN : -value._high,
|
||||
@@ -58,7 +58,7 @@ public readonly partial struct DoubleDouble :
|
||||
}
|
||||
|
||||
/// <summary>Adds normalized expansions, retaining low-sum residuals under cancellation.</summary>
|
||||
public static DoubleDouble operator +(DoubleDouble left, DoubleDouble right)
|
||||
public static DoubleDouble Add(DoubleDouble left, DoubleDouble right)
|
||||
{
|
||||
if (!IsFinite(left) || !IsFinite(right) || (left._high == 0.0 && right._high == 0.0))
|
||||
{
|
||||
@@ -73,7 +73,7 @@ public readonly partial struct DoubleDouble :
|
||||
}
|
||||
|
||||
/// <summary>Subtracts normalized expansions.</summary>
|
||||
public static DoubleDouble operator -(DoubleDouble left, DoubleDouble right)
|
||||
public static DoubleDouble Subtract(DoubleDouble left, DoubleDouble right)
|
||||
{
|
||||
if (!IsFinite(left) || !IsFinite(right) || (left._high == 0.0 && right._high == 0.0))
|
||||
{
|
||||
@@ -95,7 +95,7 @@ public readonly partial struct DoubleDouble :
|
||||
/// Swapping operands preserves both result components bit-for-bit.
|
||||
/// Results are approximate double-double values, not universally correctly rounded.
|
||||
/// </remarks>
|
||||
public static DoubleDouble operator *(DoubleDouble left, DoubleDouble right)
|
||||
public static DoubleDouble Multiply(DoubleDouble left, DoubleDouble right)
|
||||
{
|
||||
if (!IsFinite(left) || !IsFinite(right) || left._high == 0.0 || right._high == 0.0)
|
||||
{
|
||||
@@ -127,7 +127,7 @@ public readonly partial struct DoubleDouble :
|
||||
|
||||
/// <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)
|
||||
public static DoubleDouble Divide(DoubleDouble left, DoubleDouble right)
|
||||
{
|
||||
if (!IsFinite(left) || !IsFinite(right) || left._high == 0.0 || right._high == 0.0)
|
||||
{
|
||||
@@ -159,35 +159,35 @@ public readonly partial struct DoubleDouble :
|
||||
|
||||
/// <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)
|
||||
public static DoubleDouble Add(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)
|
||||
public static DoubleDouble Add(double left, DoubleDouble right)
|
||||
{
|
||||
return right + left;
|
||||
return Add(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)
|
||||
public static DoubleDouble Subtract(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)
|
||||
public static DoubleDouble Subtract(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)
|
||||
public static DoubleDouble Multiply(DoubleDouble left, double right)
|
||||
{
|
||||
if (!IsFinite(left) || !double.IsFinite(right) || left._high == 0.0 || right == 0.0)
|
||||
{
|
||||
@@ -207,14 +207,14 @@ public readonly partial struct DoubleDouble :
|
||||
}
|
||||
|
||||
/// <summary>Applies the expansion operation without discarding the low component.</summary>
|
||||
public static DoubleDouble operator *(double left, DoubleDouble right)
|
||||
public static DoubleDouble Multiply(double left, DoubleDouble right)
|
||||
{
|
||||
return right * left;
|
||||
return Multiply(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)
|
||||
public static DoubleDouble Divide(DoubleDouble left, double right)
|
||||
{
|
||||
if (!IsFinite(left) || !double.IsFinite(right) || left._high == 0.0 || right == 0.0)
|
||||
{
|
||||
@@ -246,7 +246,7 @@ public readonly partial struct DoubleDouble :
|
||||
|
||||
/// <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)
|
||||
public static DoubleDouble Divide(double left, DoubleDouble right)
|
||||
{
|
||||
if (!double.IsFinite(left) || !IsFinite(right) || left == 0.0 || right._high == 0.0)
|
||||
{
|
||||
|
||||
+7
-7
@@ -1,6 +1,6 @@
|
||||
namespace Just.PreciseMath;
|
||||
|
||||
public static partial class DDMath
|
||||
public readonly partial struct DoubleDouble
|
||||
{
|
||||
/// <summary>Returns the natural logarithm of a double-double value.</summary>
|
||||
/// <remarks>
|
||||
@@ -17,7 +17,7 @@ public static partial class DDMath
|
||||
{
|
||||
if (double.IsNaN(value.High) || value.High < 0.0)
|
||||
{
|
||||
return DoubleDouble.NaN;
|
||||
return NaN;
|
||||
}
|
||||
if (value.High == 0.0)
|
||||
{
|
||||
@@ -27,9 +27,9 @@ public static partial class DDMath
|
||||
{
|
||||
return value;
|
||||
}
|
||||
if (value == DoubleDouble.One)
|
||||
if (value == One)
|
||||
{
|
||||
return DoubleDouble.Zero;
|
||||
return Zero;
|
||||
}
|
||||
return LogPositiveFinite(value);
|
||||
}
|
||||
@@ -49,9 +49,9 @@ public static partial class DDMath
|
||||
}
|
||||
|
||||
int exponent = Math.ILogB(value.High);
|
||||
DoubleDouble mantissa = DoubleDouble.FromComponents(Math.ScaleB(value.High, -exponent),
|
||||
DoubleDouble mantissa = FromComponents(Math.ScaleB(value.High, -exponent),
|
||||
Math.ScaleB(value.Low, -exponent));
|
||||
if (mantissa.High > DoubleDouble.Sqrt2.High)
|
||||
if (mantissa.High > Sqrt2.High)
|
||||
{
|
||||
mantissa *= 0.5;
|
||||
++exponent;
|
||||
@@ -72,6 +72,6 @@ public static partial class DDMath
|
||||
}
|
||||
// log(m) = 2*atanh(t). The omitted series tail is bounded by
|
||||
// 2*|t|^51/(51*(1-t^2)); rounding, not truncation, dominates.
|
||||
return (sum * 2.0) + (DoubleDouble.Ln2 * exponent);
|
||||
return (sum * 2.0) + (Ln2 * exponent);
|
||||
}
|
||||
}
|
||||
@@ -86,18 +86,6 @@ public readonly partial struct DoubleDouble
|
||||
return IsNaN(y) ? x : MinMagnitude(x, y);
|
||||
}
|
||||
|
||||
/// <summary>Adds one using double-double arithmetic, including its overflow and nonfinite behavior.</summary>
|
||||
public static DoubleDouble operator ++(DoubleDouble value)
|
||||
{
|
||||
return value + 1.0;
|
||||
}
|
||||
|
||||
/// <summary>Subtracts one using double-double arithmetic, including its overflow and nonfinite behavior.</summary>
|
||||
public static DoubleDouble operator --(DoubleDouble value)
|
||||
{
|
||||
return value - 1.0;
|
||||
}
|
||||
|
||||
/// <summary>Tests normalization and canonical NaN and zero-residual bit patterns.</summary>
|
||||
public static bool IsCanonical(DoubleDouble value)
|
||||
{
|
||||
|
||||
@@ -0,0 +1,100 @@
|
||||
namespace Just.PreciseMath;
|
||||
|
||||
public readonly partial struct DoubleDouble
|
||||
{
|
||||
/// <inheritdoc cref="Plus"/>
|
||||
public static DoubleDouble operator +(DoubleDouble value)
|
||||
{
|
||||
return Plus(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Negate"/>
|
||||
public static DoubleDouble operator -(DoubleDouble value)
|
||||
{
|
||||
return Negate(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Add(DoubleDouble, DoubleDouble)"/>
|
||||
public static DoubleDouble operator +(DoubleDouble left, DoubleDouble right)
|
||||
{
|
||||
return Add(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Subtract(DoubleDouble, DoubleDouble)"/>
|
||||
public static DoubleDouble operator -(DoubleDouble left, DoubleDouble right)
|
||||
{
|
||||
return Subtract(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Multiply(DoubleDouble, DoubleDouble)"/>
|
||||
public static DoubleDouble operator *(DoubleDouble left, DoubleDouble right)
|
||||
{
|
||||
return Multiply(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Divide(DoubleDouble, DoubleDouble)"/>
|
||||
public static DoubleDouble operator /(DoubleDouble left, DoubleDouble right)
|
||||
{
|
||||
return Divide(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Add(DoubleDouble, double)"/>
|
||||
public static DoubleDouble operator +(DoubleDouble left, double right)
|
||||
{
|
||||
return Add(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Add(double, DoubleDouble)"/>
|
||||
public static DoubleDouble operator +(double left, DoubleDouble right)
|
||||
{
|
||||
return Add(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Subtract(DoubleDouble, double)"/>
|
||||
public static DoubleDouble operator -(DoubleDouble left, double right)
|
||||
{
|
||||
return Subtract(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Subtract(double, DoubleDouble)"/>
|
||||
public static DoubleDouble operator -(double left, DoubleDouble right)
|
||||
{
|
||||
return Subtract(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Multiply(DoubleDouble, double)"/>
|
||||
public static DoubleDouble operator *(DoubleDouble left, double right)
|
||||
{
|
||||
return Multiply(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Multiply(double, DoubleDouble)"/>
|
||||
public static DoubleDouble operator *(double left, DoubleDouble right)
|
||||
{
|
||||
return Multiply(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Divide(DoubleDouble, double)"/>
|
||||
public static DoubleDouble operator /(DoubleDouble left, double right)
|
||||
{
|
||||
return Divide(left, right);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Divide(double, DoubleDouble)"/>
|
||||
public static DoubleDouble operator /(double left, DoubleDouble right)
|
||||
{
|
||||
return Divide(left, right);
|
||||
}
|
||||
|
||||
/// <summary>Adds one using double-double arithmetic, including its overflow and nonfinite behavior.</summary>
|
||||
public static DoubleDouble operator ++(DoubleDouble value)
|
||||
{
|
||||
return Add(value, 1.0);
|
||||
}
|
||||
|
||||
/// <summary>Subtracts one using double-double arithmetic, including its overflow and nonfinite behavior.</summary>
|
||||
public static DoubleDouble operator --(DoubleDouble value)
|
||||
{
|
||||
return Subtract(value, 1.0);
|
||||
}
|
||||
}
|
||||
+22
-22
@@ -1,6 +1,6 @@
|
||||
namespace Just.PreciseMath;
|
||||
|
||||
public static partial class DDMath
|
||||
public readonly partial struct DoubleDouble
|
||||
{
|
||||
/// <summary>Raises a double-double value to an integer power.</summary>
|
||||
/// <remarks>
|
||||
@@ -21,11 +21,11 @@ public static partial class DDMath
|
||||
{
|
||||
if (exponent == 0)
|
||||
{
|
||||
return DoubleDouble.One;
|
||||
return One;
|
||||
}
|
||||
if (double.IsNaN(value.High))
|
||||
{
|
||||
return DoubleDouble.NaN;
|
||||
return NaN;
|
||||
}
|
||||
if (exponent == 1)
|
||||
{
|
||||
@@ -43,10 +43,10 @@ public static partial class DDMath
|
||||
// Widen BEFORE negation: abs(int.MinValue) is not an int.
|
||||
long remaining = exponent < 0 ? -(long)exponent : exponent;
|
||||
long factorExponent = Math.ILogB(value.High);
|
||||
DoubleDouble factor = DoubleDouble.FromComponents(
|
||||
DoubleDouble factor = FromComponents(
|
||||
Math.ScaleB(Math.Abs(value.High), -(int)factorExponent),
|
||||
Math.ScaleB(value.High < 0.0 ? -value.Low : value.Low, -(int)factorExponent));
|
||||
DoubleDouble result = DoubleDouble.One;
|
||||
DoubleDouble result = One;
|
||||
long resultExponent = 0;
|
||||
while (remaining != 0)
|
||||
{
|
||||
@@ -105,35 +105,35 @@ public static partial class DDMath
|
||||
[Pure]
|
||||
public static DoubleDouble Pow(DoubleDouble value, DoubleDouble exponent)
|
||||
{
|
||||
if (DoubleDouble.IsZero(exponent) || value == DoubleDouble.One)
|
||||
if (IsZero(exponent) || value == One)
|
||||
{
|
||||
return DoubleDouble.One;
|
||||
return One;
|
||||
}
|
||||
if (DoubleDouble.IsNaN(value) || DoubleDouble.IsNaN(exponent))
|
||||
if (IsNaN(value) || IsNaN(exponent))
|
||||
{
|
||||
return DoubleDouble.NaN;
|
||||
return NaN;
|
||||
}
|
||||
|
||||
DoubleDouble magnitude = DoubleDouble.Abs(value);
|
||||
if (DoubleDouble.IsInfinity(exponent))
|
||||
DoubleDouble magnitude = Abs(value);
|
||||
if (IsInfinity(exponent))
|
||||
{
|
||||
if (magnitude == DoubleDouble.One)
|
||||
if (magnitude == One)
|
||||
{
|
||||
return DoubleDouble.One;
|
||||
return One;
|
||||
}
|
||||
bool grows = (magnitude > DoubleDouble.One) == (exponent.High > 0.0);
|
||||
return grows ? new DoubleDouble(double.PositiveInfinity) : DoubleDouble.Zero;
|
||||
bool grows = (magnitude > One) == (exponent.High > 0.0);
|
||||
return grows ? new DoubleDouble(double.PositiveInfinity) : Zero;
|
||||
}
|
||||
|
||||
bool integer = DoubleDouble.IsInteger(exponent);
|
||||
bool integer = IsInteger(exponent);
|
||||
if (integer && exponent.High >= int.MinValue && exponent.High <= int.MaxValue)
|
||||
{
|
||||
// A normalized integer in this range has a zero low component.
|
||||
// Reuse the integer algorithm, including exact identities and ties.
|
||||
return Pow(value, (int)exponent.High);
|
||||
}
|
||||
bool negative = double.IsNegative(value.High) && DoubleDouble.IsOddInteger(exponent);
|
||||
if (value.High == 0.0 || DoubleDouble.IsInfinity(value))
|
||||
bool negative = double.IsNegative(value.High) && IsOddInteger(exponent);
|
||||
if (value.High == 0.0 || IsInfinity(value))
|
||||
{
|
||||
bool infinite = (value.High == 0.0) == (exponent.High < 0.0);
|
||||
double result = infinite ? double.PositiveInfinity : 0.0;
|
||||
@@ -141,11 +141,11 @@ public static partial class DDMath
|
||||
}
|
||||
if (value.High < 0.0 && !integer)
|
||||
{
|
||||
return DoubleDouble.NaN;
|
||||
return NaN;
|
||||
}
|
||||
if (magnitude == DoubleDouble.One)
|
||||
if (magnitude == One)
|
||||
{
|
||||
return negative ? DoubleDouble.NegativeOne : DoubleDouble.One;
|
||||
return negative ? NegativeOne : One;
|
||||
}
|
||||
DoubleDouble resultMagnitude = Exp(exponent * LogPositiveFinite(magnitude));
|
||||
return negative ? -resultMagnitude : resultMagnitude;
|
||||
@@ -158,7 +158,7 @@ public static partial class DDMath
|
||||
// int.MinValue times any finite input's binary64 exponent.
|
||||
int adjustment = Math.ILogB(value.High);
|
||||
exponent += adjustment;
|
||||
return DoubleDouble.FromComponents(Math.ScaleB(value.High, -adjustment),
|
||||
return FromComponents(Math.ScaleB(value.High, -adjustment),
|
||||
Math.ScaleB(value.Low, -adjustment));
|
||||
}
|
||||
}
|
||||
@@ -200,7 +200,7 @@ public readonly partial struct DoubleDouble : IRootFunctions<DoubleDouble>
|
||||
case 1:
|
||||
return value;
|
||||
case -1:
|
||||
return DDMath.Reciprocal(value);
|
||||
return Reciprocal(value);
|
||||
case 2:
|
||||
return Sqrt(value);
|
||||
case -2:
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
namespace Just.PreciseMath;
|
||||
|
||||
/// <summary>Provides instance-style calls to the static DoubleDouble mathematical functions.</summary>
|
||||
/// <remarks>
|
||||
/// Every extension returns a new result without modifying its receiver. Extensions
|
||||
/// forward directly to DoubleDouble, retaining its numerical and special-value contracts.
|
||||
/// </remarks>
|
||||
public static class DoubleDoubleExtensions
|
||||
{
|
||||
/// <inheritdoc cref="DoubleDouble.Abs(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Abs(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Abs(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Square(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Square(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Square(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Reciprocal(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Reciprocal(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Reciprocal(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Sqrt(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Sqrt(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Sqrt(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.InvSqrt(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble InvSqrt(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.InvSqrt(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Cbrt(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Cbrt(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Cbrt(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Exp(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Exp2(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp2(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp2(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Exp10(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp10(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp10(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.ExpM1(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble ExpM1(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.ExpM1(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Exp2M1(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp2M1(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp2M1(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Exp10M1(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Exp10M1(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Exp10M1(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Log(DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Log(this DoubleDouble value)
|
||||
{
|
||||
return DoubleDouble.Log(value);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.RootN(DoubleDouble, int)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble RootN(this DoubleDouble value, int n)
|
||||
{
|
||||
return DoubleDouble.RootN(value, n);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Pow(DoubleDouble, int)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Pow(this DoubleDouble value, int exponent)
|
||||
{
|
||||
return DoubleDouble.Pow(value, exponent);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Pow(DoubleDouble, double)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Pow(this DoubleDouble value, double exponent)
|
||||
{
|
||||
return DoubleDouble.Pow(value, exponent);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="DoubleDouble.Pow(DoubleDouble, DoubleDouble)"/>
|
||||
[Pure]
|
||||
public static DoubleDouble Pow(this DoubleDouble value, DoubleDouble exponent)
|
||||
{
|
||||
return DoubleDouble.Pow(value, exponent);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,55 @@ namespace Just.PreciseMath.Tests;
|
||||
|
||||
public class DoubleDoubleArithmeticTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("+")]
|
||||
[InlineData("-")]
|
||||
[InlineData("*")]
|
||||
[InlineData("/")]
|
||||
public void NamedArithmeticMatchesOperatorComponentBits(string operation)
|
||||
{
|
||||
// Compatibility, not an accuracy oracle. The existing rational tests
|
||||
// independently validate the kernels reached through these operators.
|
||||
DoubleDouble[] values = MultiplicationCommutativityCases();
|
||||
foreach (DoubleDouble left in values)
|
||||
{
|
||||
foreach (DoubleDouble right in values)
|
||||
{
|
||||
(DoubleDouble actual, DoubleDouble expected)[] pairs = operation switch
|
||||
{
|
||||
"+" => [(DoubleDouble.Add(left, right), left + right),
|
||||
(DoubleDouble.Add(left, right.High), left + right.High),
|
||||
(DoubleDouble.Add(left.High, right), left.High + right)],
|
||||
"-" => [(DoubleDouble.Subtract(left, right), left - right),
|
||||
(DoubleDouble.Subtract(left, right.High), left - right.High),
|
||||
(DoubleDouble.Subtract(left.High, right), left.High - right)],
|
||||
"*" => [(DoubleDouble.Multiply(left, right), left * right),
|
||||
(DoubleDouble.Multiply(left, right.High), left * right.High),
|
||||
(DoubleDouble.Multiply(left.High, right), left.High * right)],
|
||||
"/" => [(DoubleDouble.Divide(left, right), left / right),
|
||||
(DoubleDouble.Divide(left, right.High), left / right.High),
|
||||
(DoubleDouble.Divide(left.High, right), left.High / right)],
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(operation))
|
||||
};
|
||||
foreach ((DoubleDouble actual, DoubleDouble expected) in pairs)
|
||||
{
|
||||
CheckBoundary(actual, expected.High, expected.Low);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NamedUnaryArithmeticMatchesOperatorComponentBits()
|
||||
{
|
||||
foreach (DoubleDouble value in MultiplicationCommutativityCases())
|
||||
{
|
||||
CheckBoundary(DoubleDouble.Plus(value), value.High, value.Low);
|
||||
DoubleDouble negated = -value;
|
||||
CheckBoundary(DoubleDouble.Negate(value), negated.High, negated.Low);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(6.0, 2.0, 8.0, 4.0, 12.0, 3.0)]
|
||||
[InlineData(-6.0, 2.0, -4.0, -8.0, -12.0, -3.0)]
|
||||
@@ -1050,7 +1099,7 @@ public class DoubleDoubleArithmeticTests
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
CheckBoundary(DoubleDouble.Square(sign * below), Math.BitDecrement(highSquared), Math.ScaleB(1.0, (2 * exponent) - 108));
|
||||
CheckBoundary(DDMath.Square(sign * above), Math.BitIncrement(highSquared), Math.ScaleB(1.0, (2 * exponent) - 106));
|
||||
CheckBoundary(DoubleDouble.Square(sign * above), Math.BitIncrement(highSquared), Math.ScaleB(1.0, (2 * exponent) - 106));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1060,7 +1109,6 @@ public class DoubleDoubleArithmeticTests
|
||||
foreach (double value in new[] { 0.0, -0.0, double.NaN, double.PositiveInfinity, double.NegativeInfinity })
|
||||
{
|
||||
CheckBits(DoubleDouble.Square(new DoubleDouble(value)), value * value);
|
||||
CheckBits(DDMath.Square(new DoubleDouble(value)), value * value);
|
||||
}
|
||||
for (int exponent = -1074; exponent <= 1023; exponent++)
|
||||
{
|
||||
@@ -1139,7 +1187,6 @@ public class DoubleDoubleArithmeticTests
|
||||
DoubleDouble.IsZero(actual).ShouldBe((numerator << 1) <= denominator);
|
||||
}
|
||||
CheckBoundary(DoubleDouble.Square(-value), actual.High, actual.Low);
|
||||
CheckBoundary(DDMath.Square(value), actual.High, actual.Low);
|
||||
}
|
||||
|
||||
private static DoubleDouble[] MultiplicationCommutativityCases()
|
||||
|
||||
@@ -36,7 +36,6 @@ public class DoubleDoubleCbrtTests
|
||||
{
|
||||
AssertBound(input, DoubleDouble.Cbrt(input));
|
||||
AssertBound(-input, DoubleDouble.Cbrt(-input));
|
||||
AssertBits(DoubleDouble.Cbrt(input), DDMath.Cbrt(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ public class DoubleDoubleExponentialFunctionsTests
|
||||
DoubleDouble actual = Evaluate(operation, input);
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
AssertBits(actual, EvaluateGeneric(operation, input));
|
||||
AssertBits(actual, EvaluateFacade(operation, input));
|
||||
if (overflow)
|
||||
{
|
||||
AssertBits(DoubleDouble.PositiveInfinity, actual);
|
||||
@@ -57,7 +56,7 @@ public class DoubleDoubleExponentialFunctionsTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NaturalExponentialIsAvailableOnTheTypeAndMatchesFacadeBits()
|
||||
public void NaturalExponentialMatchesGenericDispatchBits()
|
||||
{
|
||||
DoubleDouble[] values = [DoubleDouble.Zero, DoubleDouble.NegativeZero,
|
||||
DoubleDouble.NaN, DoubleDouble.PositiveInfinity, DoubleDouble.NegativeInfinity,
|
||||
@@ -65,7 +64,7 @@ public class DoubleDoubleExponentialFunctionsTests
|
||||
new(double.MaxValue), DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -1000))];
|
||||
foreach (DoubleDouble value in values)
|
||||
{
|
||||
AssertBits(DDMath.Exp(value), DoubleDouble.Exp(value));
|
||||
AssertBits(DoubleDouble.Exp(value), EvaluateGeneric("Exp", value));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -117,7 +116,6 @@ public class DoubleDoubleExponentialFunctionsTests
|
||||
DoubleDouble input = DoubleDouble.FromComponents(exponent, sign * double.Epsilon);
|
||||
DoubleDouble expected = DoubleDouble.FromComponents(Math.ScaleB(1.0, exponent), sign * expectedLow);
|
||||
AssertBits(expected, DoubleDouble.Exp2(input));
|
||||
AssertBits(expected, DDMath.Exp2(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,7 +126,7 @@ public class DoubleDoubleExponentialFunctionsTests
|
||||
[InlineData("ExpM1")]
|
||||
[InlineData("Exp2M1")]
|
||||
[InlineData("Exp10M1")]
|
||||
public void CompleteContractHasGenericDispatchAndThinFacade(string operation)
|
||||
public void CompleteContractHasGenericDispatch(string operation)
|
||||
{
|
||||
bool minusOne = operation.EndsWith("M1", StringComparison.Ordinal);
|
||||
DoubleDouble[] values = [DoubleDouble.Zero, DoubleDouble.NegativeZero,
|
||||
@@ -141,7 +139,6 @@ public class DoubleDoubleExponentialFunctionsTests
|
||||
DoubleDouble actual = Evaluate(operation, value);
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
AssertBits(actual, EvaluateGeneric(operation, value));
|
||||
AssertBits(actual, EvaluateFacade(operation, value));
|
||||
if (DoubleDouble.IsZero(value))
|
||||
{
|
||||
AssertBits(minusOne ? value : DoubleDouble.One, actual);
|
||||
@@ -189,20 +186,6 @@ public class DoubleDoubleExponentialFunctionsTests
|
||||
};
|
||||
}
|
||||
|
||||
private static DoubleDouble EvaluateFacade(string operation, DoubleDouble value)
|
||||
{
|
||||
return operation switch
|
||||
{
|
||||
"Exp" => DDMath.Exp(value),
|
||||
"Exp2" => DDMath.Exp2(value),
|
||||
"Exp10" => DDMath.Exp10(value),
|
||||
"ExpM1" => DDMath.ExpM1(value),
|
||||
"Exp2M1" => DDMath.Exp2M1(value),
|
||||
"Exp10M1" => DDMath.Exp10M1(value),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(operation))
|
||||
};
|
||||
}
|
||||
|
||||
private static BigInteger Units(double value)
|
||||
{
|
||||
long bits = BitConverter.DoubleToInt64Bits(value);
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
namespace Just.PreciseMath.Tests;
|
||||
|
||||
public class DoubleDoubleExtensionsTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("Abs")]
|
||||
[InlineData("Square")]
|
||||
[InlineData("Reciprocal")]
|
||||
[InlineData("Sqrt")]
|
||||
[InlineData("InvSqrt")]
|
||||
[InlineData("Cbrt")]
|
||||
[InlineData("Exp")]
|
||||
[InlineData("Exp2")]
|
||||
[InlineData("Exp10")]
|
||||
[InlineData("ExpM1")]
|
||||
[InlineData("Exp2M1")]
|
||||
[InlineData("Exp10M1")]
|
||||
[InlineData("Log")]
|
||||
public void UnaryExtensionsPreserveStaticFunctionComponentBits(string operation)
|
||||
{
|
||||
// These are forwarding contracts, not accuracy oracles. The static
|
||||
// kernels have separate independent numerical-reference tests.
|
||||
foreach (DoubleDouble value in Values())
|
||||
{
|
||||
(DoubleDouble expected, DoubleDouble actual) = operation switch
|
||||
{
|
||||
"Abs" => (DoubleDouble.Abs(value), value.Abs()),
|
||||
"Square" => (DoubleDouble.Square(value), value.Square()),
|
||||
"Reciprocal" => (DoubleDouble.Reciprocal(value), value.Reciprocal()),
|
||||
"Sqrt" => (DoubleDouble.Sqrt(value), value.Sqrt()),
|
||||
"InvSqrt" => (DoubleDouble.InvSqrt(value), value.InvSqrt()),
|
||||
"Cbrt" => (DoubleDouble.Cbrt(value), value.Cbrt()),
|
||||
"Exp" => (DoubleDouble.Exp(value), value.Exp()),
|
||||
"Exp2" => (DoubleDouble.Exp2(value), value.Exp2()),
|
||||
"Exp10" => (DoubleDouble.Exp10(value), value.Exp10()),
|
||||
"ExpM1" => (DoubleDouble.ExpM1(value), value.ExpM1()),
|
||||
"Exp2M1" => (DoubleDouble.Exp2M1(value), value.Exp2M1()),
|
||||
"Exp10M1" => (DoubleDouble.Exp10M1(value), value.Exp10M1()),
|
||||
"Log" => (DoubleDouble.Log(value), value.Log()),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(operation))
|
||||
};
|
||||
AssertBits(expected, actual);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtensionsCanBeChainedWithoutChangingTheReceiver()
|
||||
{
|
||||
DoubleDouble value = new(-4.0);
|
||||
DoubleDouble actual = value.Abs().Square().Sqrt().Reciprocal();
|
||||
// All intermediate results are exactly representable binary powers.
|
||||
AssertBits(new DoubleDouble(0.25), actual);
|
||||
AssertBits(new DoubleDouble(-4.0), value);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(int.MinValue)]
|
||||
[InlineData(-7)]
|
||||
[InlineData(-2)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(7)]
|
||||
[InlineData(int.MaxValue)]
|
||||
public void IntegerPowerExtensionPreservesStaticFunctionComponentBits(int exponent)
|
||||
{
|
||||
foreach (DoubleDouble value in Values())
|
||||
{
|
||||
AssertBits(DoubleDouble.Pow(value, exponent), value.Pow(exponent));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScalarPowerExtensionPreservesStaticFunctionComponentBits()
|
||||
{
|
||||
double[] exponents = [0.0, -0.0, 1.0, -1.0, 2.0, -2.0, 0.5, -0.5,
|
||||
1.25, -1.25, double.Epsilon, double.MaxValue, double.MinValue,
|
||||
double.NaN, double.PositiveInfinity, double.NegativeInfinity];
|
||||
foreach (DoubleDouble value in Values())
|
||||
{
|
||||
foreach (double exponent in exponents)
|
||||
{
|
||||
AssertBits(DoubleDouble.Pow(value, exponent), value.Pow(exponent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpansionPowerExtensionPreservesBothExponentComponents()
|
||||
{
|
||||
DoubleDouble[] exponents = [DoubleDouble.Zero, DoubleDouble.NegativeZero,
|
||||
DoubleDouble.One, DoubleDouble.NegativeOne, DoubleDouble.NaN,
|
||||
DoubleDouble.PositiveInfinity, DoubleDouble.NegativeInfinity,
|
||||
DoubleDouble.FromComponents(0.5, Math.ScaleB(1.0, -54)),
|
||||
DoubleDouble.FromComponents(0.5, -Math.ScaleB(1.0, -55)),
|
||||
DoubleDouble.FromComponents(1.0, double.Epsilon),
|
||||
DoubleDouble.FromComponents(Math.ScaleB(1.0, 54), 1.0)];
|
||||
foreach (DoubleDouble value in Values())
|
||||
{
|
||||
foreach (DoubleDouble exponent in exponents)
|
||||
{
|
||||
AssertBits(DoubleDouble.Pow(value, exponent), value.Pow(exponent));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(int.MinValue)]
|
||||
[InlineData(-7)]
|
||||
[InlineData(-3)]
|
||||
[InlineData(-2)]
|
||||
[InlineData(-1)]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
[InlineData(7)]
|
||||
[InlineData(int.MaxValue)]
|
||||
public void RootExtensionPreservesStaticFunctionComponentBits(int degree)
|
||||
{
|
||||
foreach (DoubleDouble value in Values())
|
||||
{
|
||||
AssertBits(DoubleDouble.RootN(value, degree), value.RootN(degree));
|
||||
}
|
||||
}
|
||||
|
||||
private static IEnumerable<DoubleDouble> Values()
|
||||
{
|
||||
yield return DoubleDouble.Zero;
|
||||
yield return DoubleDouble.NegativeZero;
|
||||
yield return DoubleDouble.NaN;
|
||||
yield return DoubleDouble.PositiveInfinity;
|
||||
yield return DoubleDouble.NegativeInfinity;
|
||||
yield return new DoubleDouble(double.MaxValue);
|
||||
yield return new DoubleDouble(double.MinValue);
|
||||
yield return DoubleDouble.One;
|
||||
yield return DoubleDouble.NegativeOne;
|
||||
yield return DoubleDouble.Pi;
|
||||
yield return -DoubleDouble.Pi;
|
||||
// Square's dedicated kernel can differ from multiplication; near-one
|
||||
// and sparse pairs also exercise low-component retention in roots/log.
|
||||
yield return DoubleDouble.FromComponents(1.0, -Math.ScaleB(1.0, -54));
|
||||
yield return DoubleDouble.FromComponents(1.0, double.Epsilon);
|
||||
yield return DoubleDouble.FromComponents(1.0, -double.Epsilon);
|
||||
foreach (int exponent in new[] { -1074, -1022, -451, -450, -1, 0, 1, 450, 451, 1020, 1021, 1023 })
|
||||
{
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double high = Math.ScaleB(sign * 1.25, exponent);
|
||||
double denseLow = Math.ScaleB(1.0, exponent - 54);
|
||||
foreach (double low in new[] { 0.0, denseLow, -denseLow, double.Epsilon, -double.Epsilon })
|
||||
{
|
||||
yield return DoubleDouble.FromComponents(high, low);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertBits(DoubleDouble expected, DoubleDouble actual)
|
||||
{
|
||||
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
|
||||
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(BitConverter.DoubleToInt64Bits(expected.Low));
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,6 @@ public class DoubleDoubleHypotTests
|
||||
DoubleDouble result = DoubleDouble.Hypot(x, y);
|
||||
AssertBound(x, y, result);
|
||||
AssertBits(result, DoubleDouble.Hypot(-y, x));
|
||||
AssertBits(result, DDMath.Hypot(x, y));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -134,7 +133,6 @@ public class DoubleDoubleHypotTests
|
||||
{
|
||||
AssertBound(new DoubleDouble(x), new DoubleDouble(y), new DoubleDouble(z), result);
|
||||
}
|
||||
AssertBits(result, DDMath.Hypot(new DoubleDouble(x), new DoubleDouble(y), new DoubleDouble(z)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,7 +184,6 @@ public class DoubleDoubleHypotTests
|
||||
AssertBound(x, y, z, result);
|
||||
AssertBits(result, DoubleDouble.Hypot(-z, x, -y));
|
||||
AssertBits(result, DoubleDouble.Hypot(y, -z, x));
|
||||
AssertBits(result, DDMath.Hypot(x, y, z));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -271,7 +268,6 @@ public class DoubleDoubleHypotTests
|
||||
AssertBits(expected, DoubleDouble.Hypot(b, c, a));
|
||||
AssertBits(expected, DoubleDouble.Hypot(c, a, b));
|
||||
AssertBits(expected, DoubleDouble.Hypot(c, b, a));
|
||||
AssertBits(expected, DDMath.Hypot(a, b, c));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -294,7 +290,6 @@ public class DoubleDoubleHypotTests
|
||||
AssertBound(first, second, result);
|
||||
}
|
||||
AssertBits(result, DoubleDouble.Hypot(second, first));
|
||||
AssertBits(result, DDMath.Hypot(first, second));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ namespace Just.PreciseMath.Tests;
|
||||
public class DoubleDoubleRootFunctionsTests
|
||||
{
|
||||
[Fact]
|
||||
public void ExistingRootsAreAvailableOnTheTypeAndMatchFacadeBits()
|
||||
public void RootsAreCanonicalAndSqrtMatchesGenericDispatchBits()
|
||||
{
|
||||
DoubleDouble[] values = [DoubleDouble.Zero, DoubleDouble.NegativeZero,
|
||||
DoubleDouble.NaN, DoubleDouble.PositiveInfinity, DoubleDouble.NegativeInfinity,
|
||||
@@ -11,11 +11,16 @@ public class DoubleDoubleRootFunctionsTests
|
||||
DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -1000))];
|
||||
foreach (DoubleDouble value in values)
|
||||
{
|
||||
AssertBits(DDMath.Sqrt(value), DoubleDouble.Sqrt(value));
|
||||
AssertBits(DDMath.InvSqrt(value), DoubleDouble.InvSqrt(value));
|
||||
AssertBits(DoubleDouble.Sqrt(value), GenericSqrt(value));
|
||||
DoubleDouble.IsCanonical(DoubleDouble.InvSqrt(value)).ShouldBeTrue();
|
||||
}
|
||||
}
|
||||
|
||||
private static T GenericSqrt<T>(T value) where T : IRootFunctions<T>
|
||||
{
|
||||
return T.Sqrt(value);
|
||||
}
|
||||
|
||||
private static void AssertBits(DoubleDouble expected, DoubleDouble actual)
|
||||
{
|
||||
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
|
||||
|
||||
@@ -68,7 +68,7 @@ public class DoubleDoubleRootNTests
|
||||
{
|
||||
AssertBits(value, DoubleDouble.RootN(value, 1));
|
||||
AssertBits(-value, DoubleDouble.RootN(-value, 1));
|
||||
AssertBits(DDMath.Reciprocal(value), DoubleDouble.RootN(value, -1));
|
||||
AssertBits(DoubleDouble.Reciprocal(value), DoubleDouble.RootN(value, -1));
|
||||
AssertBits(DoubleDouble.Sqrt(value), DoubleDouble.RootN(value, 2));
|
||||
AssertBits(DoubleDouble.InvSqrt(value), DoubleDouble.RootN(value, -2));
|
||||
AssertBits(DoubleDouble.Cbrt(value), DoubleDouble.RootN(value, 3));
|
||||
@@ -153,7 +153,7 @@ public class DoubleDoubleRootNTests
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GenericContractAndFacadeDispatchToTypeMembers()
|
||||
public void GenericContractDispatchesToTypeMembers()
|
||||
{
|
||||
DoubleDouble[] values = [DoubleDouble.Zero, DoubleDouble.NegativeZero, DoubleDouble.NaN,
|
||||
DoubleDouble.PositiveInfinity, DoubleDouble.NegativeInfinity, new(-2.0), new(double.Epsilon),
|
||||
@@ -167,7 +167,6 @@ public class DoubleDoubleRootNTests
|
||||
{
|
||||
DoubleDouble result = DoubleDouble.RootN(value, degree);
|
||||
AssertBits(result, GenericRootN(value, degree));
|
||||
AssertBits(result, DDMath.RootN(value, degree));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ public class PreciseMathExpTests
|
||||
// Inputs are exact stored binary64 sums, not rounded irrational constants.
|
||||
DoubleDouble input = DoubleDouble.FromComponents(high, low);
|
||||
(Units(input.High) + Units(input.Low)).ShouldBe(Units(high) + Units(low));
|
||||
DoubleDouble actual = DDMath.Exp(input);
|
||||
DoubleDouble actual = DoubleDouble.Exp(input);
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
if (overflow)
|
||||
{
|
||||
@@ -60,7 +60,7 @@ public class PreciseMathExpTests
|
||||
{
|
||||
// Independently round Decimal.exp(exact binary64 input)/2^-1074 to an
|
||||
// integer at 180 digits. Pin more than the general one-subnormal bound.
|
||||
DoubleDouble actual = DDMath.Exp(new DoubleDouble(input));
|
||||
DoubleDouble actual = DoubleDouble.Exp(new DoubleDouble(input));
|
||||
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(expectedBits);
|
||||
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(0L);
|
||||
}
|
||||
@@ -70,7 +70,7 @@ public class PreciseMathExpTests
|
||||
[InlineData(709.781)]
|
||||
public void LegacyFiniteResultsDoNotOverflowPrematurely(double input)
|
||||
{
|
||||
DoubleDouble actual = DDMath.Exp(new DoubleDouble(input));
|
||||
DoubleDouble actual = DoubleDouble.Exp(new DoubleDouble(input));
|
||||
DoubleDouble.IsFinite(actual).ShouldBeTrue();
|
||||
actual.Low.ShouldNotBe(0.0);
|
||||
// Component-aware accuracy for these exact inputs is pinned by ReferenceCases.
|
||||
@@ -83,7 +83,7 @@ public class PreciseMathExpTests
|
||||
double.MaxValue, double.MinValue];
|
||||
foreach (double input in inputs)
|
||||
{
|
||||
DoubleDouble actual = DDMath.Exp(new DoubleDouble(input));
|
||||
DoubleDouble actual = DoubleDouble.Exp(new DoubleDouble(input));
|
||||
double expected = Math.Exp(input);
|
||||
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(
|
||||
BitConverter.DoubleToInt64Bits(double.IsNaN(expected) ? double.NaN : expected));
|
||||
|
||||
@@ -11,7 +11,7 @@ public class PreciseMathInvSqrtTests
|
||||
public void IrrationalInverseRootsRetainMoreThanBinary64Precision(double value)
|
||||
{
|
||||
DoubleDouble input = new(value);
|
||||
DoubleDouble actual = DDMath.InvSqrt(input);
|
||||
DoubleDouble actual = DoubleDouble.InvSqrt(input);
|
||||
actual.Low.ShouldNotBe(0.0);
|
||||
AssertInvSqrtBound(input, actual);
|
||||
}
|
||||
@@ -23,7 +23,7 @@ public class PreciseMathInvSqrtTests
|
||||
double.NaN, -1.0, -double.Epsilon, double.MinValue];
|
||||
foreach (double value in values)
|
||||
{
|
||||
DoubleDouble actual = DDMath.InvSqrt(new DoubleDouble(value));
|
||||
DoubleDouble actual = DoubleDouble.InvSqrt(new DoubleDouble(value));
|
||||
// Binary64 is an independent oracle only for these special values.
|
||||
double expected = 1.0 / Math.Sqrt(value);
|
||||
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(
|
||||
@@ -33,7 +33,7 @@ public class PreciseMathInvSqrtTests
|
||||
}
|
||||
|
||||
DoubleDouble negative = DoubleDouble.FromComponents(-1.0, Math.ScaleB(1.0, -54));
|
||||
DoubleDouble.IsNaN(DDMath.InvSqrt(negative)).ShouldBeTrue();
|
||||
DoubleDouble.IsNaN(DoubleDouble.InvSqrt(negative)).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -42,7 +42,7 @@ public class PreciseMathInvSqrtTests
|
||||
// 1/sqrt(2^(2k)) = 2^-k exactly, including the minimum subnormal input.
|
||||
for (int exponent = -1074; exponent <= 1022; exponent += 2)
|
||||
{
|
||||
DoubleDouble actual = DDMath.InvSqrt(new DoubleDouble(Math.ScaleB(1.0, exponent)));
|
||||
DoubleDouble actual = DoubleDouble.InvSqrt(new DoubleDouble(Math.ScaleB(1.0, exponent)));
|
||||
actual.High.ShouldBe(Math.ScaleB(1.0, -(exponent / 2)));
|
||||
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(0L);
|
||||
}
|
||||
@@ -54,7 +54,7 @@ public class PreciseMathInvSqrtTests
|
||||
foreach (double low in new[] { Math.ScaleB(1.0, -54), -Math.ScaleB(1.0, -54) })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(1.0, low);
|
||||
DoubleDouble actual = DDMath.InvSqrt(input);
|
||||
DoubleDouble actual = DoubleDouble.InvSqrt(input);
|
||||
actual.High.ShouldBe(1.0);
|
||||
Math.Sign(actual.Low).ShouldBe(-Math.Sign(low));
|
||||
AssertInvSqrtBound(input, actual);
|
||||
@@ -78,7 +78,7 @@ public class PreciseMathInvSqrtTests
|
||||
if (boundary > 0.0)
|
||||
{
|
||||
DoubleDouble input = new(boundary);
|
||||
AssertInvSqrtBound(input, DDMath.InvSqrt(input));
|
||||
AssertInvSqrtBound(input, DoubleDouble.InvSqrt(input));
|
||||
}
|
||||
}
|
||||
foreach (double residual in new[] { 0.0, low, -low, double.Epsilon, -double.Epsilon })
|
||||
@@ -86,7 +86,7 @@ public class PreciseMathInvSqrtTests
|
||||
DoubleDouble input = DoubleDouble.FromComponents(high, residual);
|
||||
if (input.High > 0.0)
|
||||
{
|
||||
AssertInvSqrtBound(input, DDMath.InvSqrt(input));
|
||||
AssertInvSqrtBound(input, DoubleDouble.InvSqrt(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,7 +103,7 @@ public class PreciseMathInvSqrtTests
|
||||
DoubleDouble.FromComponents(0.0, double.Epsilon)];
|
||||
foreach (DoubleDouble input in boundaries)
|
||||
{
|
||||
AssertInvSqrtBound(input, DDMath.InvSqrt(input));
|
||||
AssertInvSqrtBound(input, DoubleDouble.InvSqrt(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -123,7 +123,7 @@ public class PreciseMathInvSqrtTests
|
||||
DoubleDouble input = DoubleDouble.FromComponents(high, low);
|
||||
if (DoubleDouble.IsFinite(input) && input.High > 0.0)
|
||||
{
|
||||
AssertInvSqrtBound(input, DDMath.InvSqrt(input));
|
||||
AssertInvSqrtBound(input, DoubleDouble.InvSqrt(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -140,7 +140,7 @@ public class PreciseMathInvSqrtTests
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double low = Math.ScaleB(sign, exponent);
|
||||
DoubleDouble actual = DDMath.InvSqrt(DoubleDouble.FromComponents(1.0, low));
|
||||
DoubleDouble actual = DoubleDouble.InvSqrt(DoubleDouble.FromComponents(1.0, low));
|
||||
actual.High.ShouldBe(1.0);
|
||||
actual.Low.ShouldBe(Math.ScaleB(-sign, exponent - 1));
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
|
||||
@@ -17,10 +17,10 @@ public class PreciseMathLogTests
|
||||
foreach (double value in values)
|
||||
{
|
||||
DoubleDouble expected = new(Math.Log(value));
|
||||
AssertBits(DDMath.Log(new DoubleDouble(value)), expected.High, 0.0);
|
||||
AssertBits(DoubleDouble.Log(new DoubleDouble(value)), expected.High, 0.0);
|
||||
}
|
||||
DoubleDouble negative = DoubleDouble.FromComponents(-1.0, Math.ScaleB(1.0, -54));
|
||||
AssertBits(DDMath.Log(negative), double.NaN, 0.0);
|
||||
AssertBits(DoubleDouble.Log(negative), double.NaN, 0.0);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
@@ -31,7 +31,7 @@ public class PreciseMathLogTests
|
||||
// 2200 digits and checked with Fraction. No binary64 log supplies the oracle.
|
||||
DoubleDouble input = DoubleDouble.FromComponents(high, low);
|
||||
(Units(input.High) + Units(input.Low)).ShouldBe(Units(high) + Units(low));
|
||||
DoubleDouble actual = DDMath.Log(input);
|
||||
DoubleDouble actual = DoubleDouble.Log(input);
|
||||
AssertReferenceBound(actual, reference, 1, $"{label}: ({high:R}, {low:R})");
|
||||
if (input == DoubleDouble.One)
|
||||
{
|
||||
@@ -51,7 +51,7 @@ public class PreciseMathLogTests
|
||||
for (int exponent = -1074; exponent <= 1023; ++exponent)
|
||||
{
|
||||
DoubleDouble input = new(Math.ScaleB(1.0, exponent));
|
||||
AssertReferenceBound(DDMath.Log(input), LogReferenceData.Ln2, exponent, $"2^{exponent}");
|
||||
AssertReferenceBound(DoubleDouble.Log(input), LogReferenceData.Ln2, exponent, $"2^{exponent}");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ public class PreciseMathLogTests
|
||||
{
|
||||
// The legacy Log(1e308) formed u+value and overflowed. The finite
|
||||
// kernel must reduce the mantissa before constructing its denominator.
|
||||
DoubleDouble actual = DDMath.Log(new DoubleDouble(input));
|
||||
DoubleDouble actual = DoubleDouble.Log(new DoubleDouble(input));
|
||||
DoubleDouble.IsFinite(actual).ShouldBeTrue();
|
||||
actual.Low.ShouldNotBe(0.0);
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
@@ -75,12 +75,12 @@ public class PreciseMathLogTests
|
||||
{
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
AssertBits(DDMath.Log(DoubleDouble.FromComponents(1.0, sign * double.Epsilon)),
|
||||
AssertBits(DoubleDouble.Log(DoubleDouble.FromComponents(1.0, sign * double.Epsilon)),
|
||||
sign * double.Epsilon, 0.0);
|
||||
double delta = sign * Math.ScaleB(1.0, -500);
|
||||
// ln(1+d) = d-d^2/2+O(d^3). Here the cubic tail is below half
|
||||
// the minimum subnormal, so these two binary components are exact.
|
||||
AssertBits(DDMath.Log(DoubleDouble.FromComponents(1.0, delta)),
|
||||
AssertBits(DoubleDouble.Log(DoubleDouble.FromComponents(1.0, delta)),
|
||||
delta, -Math.ScaleB(1.0, -1001));
|
||||
}
|
||||
}
|
||||
@@ -89,8 +89,8 @@ public class PreciseMathLogTests
|
||||
public void ArbitraryPairsAreNormalizedByThePublicFactoryBeforeLog()
|
||||
{
|
||||
DoubleDouble positive = DoubleDouble.FromComponents(0.0, 2.0);
|
||||
AssertReferenceBound(DDMath.Log(positive), LogReferenceData.Ln2, 1, "zero-high public construction");
|
||||
AssertBits(DDMath.Log(DoubleDouble.FromComponents(0.0, -2.0)), double.NaN, 0.0);
|
||||
AssertReferenceBound(DoubleDouble.Log(positive), LogReferenceData.Ln2, 1, "zero-high public construction");
|
||||
AssertBits(DoubleDouble.Log(DoubleDouble.FromComponents(0.0, -2.0)), double.NaN, 0.0);
|
||||
}
|
||||
|
||||
private static void AssertReferenceBound(DoubleDouble actual, string reference, int factor, string context)
|
||||
|
||||
@@ -6,17 +6,17 @@ public class PreciseMathPowTests
|
||||
public void NegativePowerDoesNotOverflowBeforeReciprocation()
|
||||
{
|
||||
// Legacy review: Pow(2, -1024) overflowed before taking the reciprocal.
|
||||
DoubleDouble actual = DDMath.Pow(new DoubleDouble(2.0), -1024);
|
||||
DoubleDouble actual = DoubleDouble.Pow(new DoubleDouble(2.0), -1024);
|
||||
AssertBits(actual, Math.ScaleB(1.0, -1024), 0.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MinimumIntegerExponentDoesNotOverflowItsMagnitude()
|
||||
{
|
||||
AssertBits(DDMath.Pow(DoubleDouble.One, int.MinValue), 1.0, 0.0);
|
||||
AssertBits(DDMath.Pow(DoubleDouble.NegativeOne, int.MinValue), 1.0, 0.0);
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(2.0), int.MinValue), 0.0, 0.0);
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(0.5), int.MinValue), double.PositiveInfinity, 0.0);
|
||||
AssertBits(DoubleDouble.Pow(DoubleDouble.One, int.MinValue), 1.0, 0.0);
|
||||
AssertBits(DoubleDouble.Pow(DoubleDouble.NegativeOne, int.MinValue), 1.0, 0.0);
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(2.0), int.MinValue), 0.0, 0.0);
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(0.5), int.MinValue), double.PositiveInfinity, 0.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -25,7 +25,7 @@ public class PreciseMathPowTests
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(sign * double.MaxValue, sign * double.Epsilon);
|
||||
AssertBits(DDMath.Pow(input, 1), input.High, input.Low);
|
||||
AssertBits(DoubleDouble.Pow(input, 1), input.High, input.Low);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class PreciseMathPowTests
|
||||
// Every int converts exactly to double. BCL is an independent
|
||||
// special-value/sign oracle, not the finite precision oracle.
|
||||
double expected = Math.Pow(input, exponent);
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(input), exponent),
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(input), exponent),
|
||||
double.IsNaN(expected) ? double.NaN : expected, 0.0);
|
||||
}
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public class PreciseMathPowTests
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double expected = sign < 0.0 && (power & 1) != 0 ? -magnitude : magnitude;
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(sign * Math.ScaleB(1.0, inputExponent)), power),
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(sign * Math.ScaleB(1.0, inputExponent)), power),
|
||||
expected, 0.0);
|
||||
}
|
||||
}
|
||||
@@ -118,13 +118,13 @@ public class PreciseMathPowTests
|
||||
AssertExactPowerBound(input, -1);
|
||||
if (scale == -500 || scale == 500)
|
||||
{
|
||||
DoubleDouble actual = DDMath.Pow(input, -2);
|
||||
DoubleDouble actual = DoubleDouble.Pow(input, -2);
|
||||
actual.Low.ShouldNotBe(0.0);
|
||||
AssertExactPowerBound(input, -2);
|
||||
}
|
||||
if (scale < 0)
|
||||
{
|
||||
DDMath.Pow(input, -1).Low.ShouldNotBe(0.0);
|
||||
DoubleDouble.Pow(input, -1).Low.ShouldNotBe(0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -141,11 +141,11 @@ public class PreciseMathPowTests
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble tiny = DoubleDouble.FromComponents(Math.ScaleB(1.0, -500), sign * double.Epsilon);
|
||||
AssertBits(DDMath.Pow(tiny, -1), Math.ScaleB(1.0, 500), -sign * Math.ScaleB(1.0, -74));
|
||||
AssertBits(DoubleDouble.Pow(tiny, -1), Math.ScaleB(1.0, 500), -sign * Math.ScaleB(1.0, -74));
|
||||
DoubleDouble huge = DoubleDouble.FromComponents(Math.ScaleB(1.0, 500), sign * Math.ScaleB(1.0, -74));
|
||||
AssertBits(DDMath.Pow(huge, -1), Math.ScaleB(1.0, -500), -sign * double.Epsilon);
|
||||
AssertBits(DoubleDouble.Pow(huge, -1), Math.ScaleB(1.0, -500), -sign * double.Epsilon);
|
||||
DoubleDouble tinySquare = DoubleDouble.FromComponents(Math.ScaleB(1.0, -250), sign * double.Epsilon);
|
||||
AssertBits(DDMath.Pow(tinySquare, -2), Math.ScaleB(1.0, 500), -sign * Math.ScaleB(1.0, -323));
|
||||
AssertBits(DoubleDouble.Pow(tinySquare, -2), Math.ScaleB(1.0, 500), -sign * Math.ScaleB(1.0, -323));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,7 +165,7 @@ public class PreciseMathPowTests
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(sign * Math.ScaleB(1.0, scale),
|
||||
sign * lowSign * Math.ScaleB(1.0, scale - 54));
|
||||
AssertBits(DDMath.Pow(input, exponent), sign * (aboveTie ? double.Epsilon : 0.0), 0.0);
|
||||
AssertBits(DoubleDouble.Pow(input, exponent), sign * (aboveTie ? double.Epsilon : 0.0), 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +183,7 @@ public class PreciseMathPowTests
|
||||
AssertExactPowerBound(finite, exponent);
|
||||
DoubleDouble overflowing = DoubleDouble.FromComponents(Math.ScaleB(1.0, scale),
|
||||
lowSign * Math.ScaleB(1.0, scale - 57));
|
||||
AssertBits(DDMath.Pow(overflowing, exponent), double.PositiveInfinity, 0.0);
|
||||
AssertBits(DoubleDouble.Pow(overflowing, exponent), double.PositiveInfinity, 0.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -208,7 +208,7 @@ public class PreciseMathPowTests
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(sign * high, sign * low);
|
||||
DoubleDouble actual = DDMath.Pow(input, exponent);
|
||||
DoubleDouble actual = DoubleDouble.Pow(input, exponent);
|
||||
actual.Low.ShouldNotBe(0.0);
|
||||
int resultSign = sign < 0.0 && (exponent & 1) != 0 ? -1 : 1;
|
||||
// Check both interval endpoints so the reference uncertainty is not
|
||||
@@ -239,7 +239,7 @@ public class PreciseMathPowTests
|
||||
{
|
||||
(numerator, denominator) = (denominator * numerator.Sign, BigInteger.Abs(numerator));
|
||||
}
|
||||
AssertRationalBound(DDMath.Pow(input, exponent), numerator, denominator, exponent,
|
||||
AssertRationalBound(DoubleDouble.Pow(input, exponent), numerator, denominator, exponent,
|
||||
$"({input.High:R}, {input.Low:R})^{exponent}");
|
||||
}
|
||||
|
||||
|
||||
@@ -10,10 +10,10 @@ public class PreciseMathRealPowSpecialTests
|
||||
DoubleDouble.FromComponents(double.MaxValue, double.Epsilon)];
|
||||
foreach (DoubleDouble value in values)
|
||||
{
|
||||
AssertBits(DDMath.Pow(value, 0.0), DoubleDouble.One);
|
||||
AssertBits(DDMath.Pow(value, new DoubleDouble(-0.0)), DoubleDouble.One);
|
||||
AssertBits(DDMath.Pow(value, 1.0), value);
|
||||
AssertBits(DDMath.Pow(value, DoubleDouble.One), value);
|
||||
AssertBits(DoubleDouble.Pow(value, 0.0), DoubleDouble.One);
|
||||
AssertBits(DoubleDouble.Pow(value, new DoubleDouble(-0.0)), DoubleDouble.One);
|
||||
AssertBits(DoubleDouble.Pow(value, 1.0), value);
|
||||
AssertBits(DoubleDouble.Pow(value, DoubleDouble.One), value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ public class PreciseMathRealPowSpecialTests
|
||||
continue;
|
||||
}
|
||||
DoubleDouble input = new(value);
|
||||
AssertBits(DDMath.Pow(input, exponent), new DoubleDouble(expected));
|
||||
AssertBits(DDMath.Pow(input, new DoubleDouble(exponent)), new DoubleDouble(expected));
|
||||
AssertBits(DoubleDouble.Pow(input, exponent), new DoubleDouble(expected));
|
||||
AssertBits(DoubleDouble.Pow(input, new DoubleDouble(exponent)), new DoubleDouble(expected));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -53,27 +53,27 @@ public class PreciseMathRealPowSpecialTests
|
||||
DoubleDouble odd = DoubleDouble.FromComponents(direction * high, 1.0);
|
||||
DoubleDouble even = DoubleDouble.FromComponents(direction * high, 2.0);
|
||||
DoubleDouble fractional = DoubleDouble.FromComponents(direction * high, 0.5);
|
||||
AssertBits(DDMath.Pow(DoubleDouble.NegativeOne, odd), DoubleDouble.NegativeOne);
|
||||
AssertBits(DDMath.Pow(DoubleDouble.NegativeOne, even), DoubleDouble.One);
|
||||
AssertBits(DDMath.Pow(DoubleDouble.NegativeOne, fractional), DoubleDouble.NaN);
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(-2.0), odd),
|
||||
AssertBits(DoubleDouble.Pow(DoubleDouble.NegativeOne, odd), DoubleDouble.NegativeOne);
|
||||
AssertBits(DoubleDouble.Pow(DoubleDouble.NegativeOne, even), DoubleDouble.One);
|
||||
AssertBits(DoubleDouble.Pow(DoubleDouble.NegativeOne, fractional), DoubleDouble.NaN);
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(-2.0), odd),
|
||||
new DoubleDouble(direction > 0.0 ? double.NegativeInfinity : -0.0));
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(-0.5), odd),
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(-0.5), odd),
|
||||
new DoubleDouble(direction > 0.0 ? -0.0 : double.NegativeInfinity));
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(-2.0), even),
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(-2.0), even),
|
||||
new DoubleDouble(direction > 0.0 ? double.PositiveInfinity : 0.0));
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(-0.0), odd),
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(-0.0), odd),
|
||||
new DoubleDouble(direction > 0.0 ? -0.0 : double.NegativeInfinity));
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(double.NegativeInfinity), odd),
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(double.NegativeInfinity), odd),
|
||||
new DoubleDouble(direction > 0.0 ? double.NegativeInfinity : -0.0));
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(-0.0), fractional),
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(-0.0), fractional),
|
||||
new DoubleDouble(direction > 0.0 ? 0.0 : double.PositiveInfinity));
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(double.NegativeInfinity), fractional),
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(double.NegativeInfinity), fractional),
|
||||
new DoubleDouble(direction > 0.0 ? double.PositiveInfinity : 0.0));
|
||||
}
|
||||
}
|
||||
DoubleDouble belowInteger = DoubleDouble.FromComponents(3.0, -double.Epsilon);
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(-2.0), belowInteger), DoubleDouble.NaN);
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(-2.0), belowInteger), DoubleDouble.NaN);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -84,9 +84,9 @@ public class PreciseMathRealPowSpecialTests
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble value = DoubleDouble.FromComponents(sign, sign * low);
|
||||
AssertBits(DDMath.Pow(value, double.PositiveInfinity),
|
||||
AssertBits(DoubleDouble.Pow(value, double.PositiveInfinity),
|
||||
new DoubleDouble(low > 0.0 ? double.PositiveInfinity : 0.0));
|
||||
AssertBits(DDMath.Pow(value, new DoubleDouble(double.NegativeInfinity)),
|
||||
AssertBits(DoubleDouble.Pow(value, new DoubleDouble(double.NegativeInfinity)),
|
||||
new DoubleDouble(low < 0.0 ? double.PositiveInfinity : 0.0));
|
||||
}
|
||||
}
|
||||
@@ -102,11 +102,11 @@ public class PreciseMathRealPowSpecialTests
|
||||
{
|
||||
foreach (int exponent in exponents)
|
||||
{
|
||||
DoubleDouble expected = DDMath.Pow(value, exponent);
|
||||
DoubleDouble expected = DoubleDouble.Pow(value, exponent);
|
||||
// Dispatch consistency, not an accuracy oracle: the integer suite
|
||||
// separately checks these algorithms against independent references.
|
||||
AssertBits(DDMath.Pow(value, (double)exponent), expected);
|
||||
AssertBits(DDMath.Pow(value, new DoubleDouble(exponent)), expected);
|
||||
AssertBits(DoubleDouble.Pow(value, (double)exponent), expected);
|
||||
AssertBits(DoubleDouble.Pow(value, new DoubleDouble(exponent)), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,8 +116,8 @@ public class PreciseMathRealPowSpecialTests
|
||||
{
|
||||
// Independently rounded sqrt(2) binary64 components; also used by the
|
||||
// constant-reference generator. Compare components, not high+low in double.
|
||||
foreach (DoubleDouble actual in new[] { DDMath.Pow(new DoubleDouble(2.0), 0.5),
|
||||
DDMath.Pow(new DoubleDouble(2.0), new DoubleDouble(0.5)) })
|
||||
foreach (DoubleDouble actual in new[] { DoubleDouble.Pow(new DoubleDouble(2.0), 0.5),
|
||||
DoubleDouble.Pow(new DoubleDouble(2.0), new DoubleDouble(0.5)) })
|
||||
{
|
||||
actual.High.ShouldBe(1.4142135623730951);
|
||||
Math.Abs(actual.Low - -9.667293313452913e-17).ShouldBeLessThan(Math.ScaleB(1.0, -90));
|
||||
@@ -132,8 +132,8 @@ public class PreciseMathRealPowSpecialTests
|
||||
{
|
||||
// 4^-537.25 = 2^-1074.5 lies above the half-epsilon threshold;
|
||||
// 4^-537.75 = 2^-1075.5 lies below it. These are well separated from ties.
|
||||
foreach (DoubleDouble actual in new[] { DDMath.Pow(new DoubleDouble(4.0), exponent),
|
||||
DDMath.Pow(new DoubleDouble(4.0), new DoubleDouble(exponent)) })
|
||||
foreach (DoubleDouble actual in new[] { DoubleDouble.Pow(new DoubleDouble(4.0), exponent),
|
||||
DoubleDouble.Pow(new DoubleDouble(4.0), new DoubleDouble(exponent)) })
|
||||
{
|
||||
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(expectedBits);
|
||||
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(0L);
|
||||
@@ -149,8 +149,8 @@ public class PreciseMathRealPowSpecialTests
|
||||
{
|
||||
bool grows = (value > 1.0) == (exponent > 0.0);
|
||||
DoubleDouble expected = new(grows ? double.PositiveInfinity : 0.0);
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(value), exponent), expected);
|
||||
AssertBits(DDMath.Pow(new DoubleDouble(value), new DoubleDouble(exponent)), expected);
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(value), exponent), expected);
|
||||
AssertBits(DoubleDouble.Pow(new DoubleDouble(value), new DoubleDouble(exponent)), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ public class PreciseMathRealPowTests
|
||||
(Units(input.High) + Units(input.Low)).ShouldBe(Units(high) + Units(low));
|
||||
BigInteger exponentUnits = Units(exponentHigh) + Units(scalar ? 0.0 : exponentLow);
|
||||
(Units(exponent.High) + Units(exponent.Low)).ShouldBe(exponentUnits);
|
||||
DoubleDouble actual = scalar ? DDMath.Pow(input, exponentHigh) : DDMath.Pow(input, exponent);
|
||||
DoubleDouble actual = scalar ? DoubleDouble.Pow(input, exponentHigh) : DoubleDouble.Pow(input, exponent);
|
||||
string context = $"{label}, scalar={scalar}: ({high:R}, {low:R})^({exponentHigh:R}, {exponentLow:R})";
|
||||
AssertReferenceBound(actual, reference, exponentUnits, context);
|
||||
}
|
||||
@@ -36,9 +36,9 @@ public class PreciseMathRealPowTests
|
||||
// These references are exact rational identities. The general ln/exp
|
||||
// implementation is approximate, so do not require universal bit equality.
|
||||
string reference = expected.ToString("e17", CultureInfo.InvariantCulture);
|
||||
AssertReferenceBound(DDMath.Pow(new DoubleDouble(input), exponent), reference,
|
||||
AssertReferenceBound(DoubleDouble.Pow(new DoubleDouble(input), exponent), reference,
|
||||
Units(exponent), "scalar exact binary reference");
|
||||
AssertReferenceBound(DDMath.Pow(new DoubleDouble(input), new DoubleDouble(exponent)), reference,
|
||||
AssertReferenceBound(DoubleDouble.Pow(new DoubleDouble(input), new DoubleDouble(exponent)), reference,
|
||||
Units(exponent), "DD exact binary reference");
|
||||
}
|
||||
|
||||
@@ -55,17 +55,17 @@ public class PreciseMathRealPowTests
|
||||
{
|
||||
foreach (int exponent in new[] { -63, -7, -1, 0, 1, 7, 63 })
|
||||
{
|
||||
DoubleDouble expected = DDMath.Pow(input, exponent);
|
||||
AssertBits(DDMath.Pow(input, (double)exponent), expected);
|
||||
AssertBits(DDMath.Pow(input, new DoubleDouble(exponent)), expected);
|
||||
DoubleDouble expected = DoubleDouble.Pow(input, exponent);
|
||||
AssertBits(DoubleDouble.Pow(input, (double)exponent), expected);
|
||||
AssertBits(DoubleDouble.Pow(input, new DoubleDouble(exponent)), expected);
|
||||
}
|
||||
}
|
||||
foreach (int exponent in new[] { int.MinValue, int.MaxValue })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -54));
|
||||
DoubleDouble expected = DDMath.Pow(input, exponent);
|
||||
AssertBits(DDMath.Pow(input, (double)exponent), expected);
|
||||
AssertBits(DDMath.Pow(input, new DoubleDouble(exponent)), expected);
|
||||
DoubleDouble expected = DoubleDouble.Pow(input, exponent);
|
||||
AssertBits(DoubleDouble.Pow(input, (double)exponent), expected);
|
||||
AssertBits(DoubleDouble.Pow(input, new DoubleDouble(exponent)), expected);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ public class PreciseMathRealPowTests
|
||||
foreach (double low in new[] { double.Epsilon, -double.Epsilon })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(high, low);
|
||||
AssertBits(DDMath.Pow(input, 1.0), input);
|
||||
AssertBits(DDMath.Pow(input, DoubleDouble.One), input);
|
||||
AssertBits(DoubleDouble.Pow(input, 1.0), input);
|
||||
AssertBits(DoubleDouble.Pow(input, DoubleDouble.One), input);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,7 @@ public class PreciseMathReciprocalTests
|
||||
foreach (double low in new[] { 0.0, Math.ScaleB(1.0, -54), -Math.ScaleB(1.0, -54) })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(high, low);
|
||||
DoubleDouble actual = DDMath.Reciprocal(input);
|
||||
DoubleDouble actual = DoubleDouble.Reciprocal(input);
|
||||
actual.Low.ShouldNotBe(0.0);
|
||||
AssertReciprocalBound(input, actual);
|
||||
}
|
||||
@@ -25,7 +25,7 @@ public class PreciseMathReciprocalTests
|
||||
double.NaN, BitConverter.Int64BitsToDouble(0x7ff0000000000001L)];
|
||||
foreach (double value in values)
|
||||
{
|
||||
DoubleDouble actual = DDMath.Reciprocal(new DoubleDouble(value));
|
||||
DoubleDouble actual = DoubleDouble.Reciprocal(new DoubleDouble(value));
|
||||
// Binary64 is an independent oracle for special values only.
|
||||
double expected = 1.0 / value;
|
||||
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(
|
||||
@@ -44,7 +44,7 @@ public class PreciseMathReciprocalTests
|
||||
{
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble actual = DDMath.Reciprocal(new DoubleDouble(Math.ScaleB(sign, exponent)));
|
||||
DoubleDouble actual = DoubleDouble.Reciprocal(new DoubleDouble(Math.ScaleB(sign, exponent)));
|
||||
actual.High.ShouldBe(Math.ScaleB(sign, -exponent));
|
||||
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(0L);
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
@@ -104,7 +104,7 @@ public class PreciseMathReciprocalTests
|
||||
{
|
||||
double low = Math.ScaleB(lowSign, exponent);
|
||||
DoubleDouble input = DoubleDouble.FromComponents(sign, low);
|
||||
DoubleDouble actual = DDMath.Reciprocal(input);
|
||||
DoubleDouble actual = DoubleDouble.Reciprocal(input);
|
||||
actual.High.ShouldBe(sign);
|
||||
actual.Low.ShouldBe(-low);
|
||||
AssertReciprocalBound(input, actual);
|
||||
@@ -116,7 +116,7 @@ public class PreciseMathReciprocalTests
|
||||
private static void AssertMatchesDivision(DoubleDouble input)
|
||||
{
|
||||
DoubleDouble expected = 1.0 / input;
|
||||
DoubleDouble actual = DDMath.Reciprocal(input);
|
||||
DoubleDouble actual = DoubleDouble.Reciprocal(input);
|
||||
string context = $"Input: ({input.High:R}, {input.Low:R})";
|
||||
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High), context);
|
||||
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(BitConverter.DoubleToInt64Bits(expected.Low), context);
|
||||
|
||||
@@ -10,7 +10,7 @@ public class PreciseMathSqrtTests
|
||||
[InlineData(2.25, 1.5)]
|
||||
public void ExactBinarySquaresHaveExactRoots(double square, double root)
|
||||
{
|
||||
DoubleDouble actual = DDMath.Sqrt(new DoubleDouble(square));
|
||||
DoubleDouble actual = DoubleDouble.Sqrt(new DoubleDouble(square));
|
||||
actual.High.ShouldBe(root);
|
||||
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(0L);
|
||||
}
|
||||
@@ -21,7 +21,7 @@ public class PreciseMathSqrtTests
|
||||
// sqrt(2^(2k)) = 2^k exactly, including the minimum subnormal input.
|
||||
for (int exponent = -1074; exponent <= 1022; exponent += 2)
|
||||
{
|
||||
DoubleDouble actual = DDMath.Sqrt(new DoubleDouble(Math.ScaleB(1.0, exponent)));
|
||||
DoubleDouble actual = DoubleDouble.Sqrt(new DoubleDouble(Math.ScaleB(1.0, exponent)));
|
||||
actual.High.ShouldBe(Math.ScaleB(1.0, exponent / 2));
|
||||
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(0L);
|
||||
}
|
||||
@@ -36,7 +36,7 @@ public class PreciseMathSqrtTests
|
||||
public void IrrationalRootsRetainMoreThanBinary64Precision(double value)
|
||||
{
|
||||
DoubleDouble input = new(value);
|
||||
DoubleDouble actual = DDMath.Sqrt(input);
|
||||
DoubleDouble actual = DoubleDouble.Sqrt(input);
|
||||
actual.Low.ShouldNotBe(0.0);
|
||||
AssertSqrtBound(input, actual);
|
||||
}
|
||||
@@ -47,7 +47,7 @@ public class PreciseMathSqrtTests
|
||||
foreach (double low in new[] { Math.ScaleB(1.0, -54), -Math.ScaleB(1.0, -54) })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(1.0, low);
|
||||
DoubleDouble actual = DDMath.Sqrt(input);
|
||||
DoubleDouble actual = DoubleDouble.Sqrt(input);
|
||||
actual.High.ShouldBe(1.0);
|
||||
(Math.Sign(actual.Low) == Math.Sign(low)).ShouldBeTrue();
|
||||
AssertSqrtBound(input, actual);
|
||||
@@ -61,7 +61,7 @@ public class PreciseMathSqrtTests
|
||||
double.NaN, -1.0, -double.Epsilon, double.MinValue];
|
||||
foreach (double value in values)
|
||||
{
|
||||
DoubleDouble actual = DDMath.Sqrt(new DoubleDouble(value));
|
||||
DoubleDouble actual = DoubleDouble.Sqrt(new DoubleDouble(value));
|
||||
double expected = Math.Sqrt(value);
|
||||
// Construction canonicalizes all NaNs to double.NaN, including
|
||||
// domain errors; zeros must instead be compared by their sign bits.
|
||||
@@ -72,7 +72,7 @@ public class PreciseMathSqrtTests
|
||||
}
|
||||
|
||||
DoubleDouble negative = DoubleDouble.FromComponents(-1.0, Math.ScaleB(1.0, -54));
|
||||
DoubleDouble.IsNaN(DDMath.Sqrt(negative)).ShouldBeTrue();
|
||||
DoubleDouble.IsNaN(DoubleDouble.Sqrt(negative)).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -92,7 +92,7 @@ public class PreciseMathSqrtTests
|
||||
if (boundary > 0.0)
|
||||
{
|
||||
DoubleDouble input = new(boundary);
|
||||
AssertSqrtBound(input, DDMath.Sqrt(input));
|
||||
AssertSqrtBound(input, DoubleDouble.Sqrt(input));
|
||||
}
|
||||
}
|
||||
foreach (double residual in new[] { 0.0, low, -low, double.Epsilon, -double.Epsilon })
|
||||
@@ -100,7 +100,7 @@ public class PreciseMathSqrtTests
|
||||
DoubleDouble input = DoubleDouble.FromComponents(high, residual);
|
||||
if (input.High > 0.0)
|
||||
{
|
||||
AssertSqrtBound(input, DDMath.Sqrt(input));
|
||||
AssertSqrtBound(input, DoubleDouble.Sqrt(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,7 +120,7 @@ public class PreciseMathSqrtTests
|
||||
DoubleDouble.FromComponents(4.0, -Math.ScaleB(1.0, -52))];
|
||||
foreach (DoubleDouble input in boundaries)
|
||||
{
|
||||
AssertSqrtBound(input, DDMath.Sqrt(input));
|
||||
AssertSqrtBound(input, DoubleDouble.Sqrt(input));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -142,7 +142,7 @@ public class PreciseMathSqrtTests
|
||||
DoubleDouble input = DoubleDouble.FromComponents(Math.ScaleB(high, exponent), Math.ScaleB(low, exponent));
|
||||
if (DoubleDouble.IsFinite(input) && input.High > 0.0)
|
||||
{
|
||||
AssertSqrtBound(input, DDMath.Sqrt(input));
|
||||
AssertSqrtBound(input, DoubleDouble.Sqrt(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,9 +156,9 @@ public class PreciseMathSqrtTests
|
||||
foreach (double low in new[] { double.Epsilon, 1e-308, 2.0, double.MaxValue })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(0.0, low);
|
||||
AssertSqrtBound(input, DDMath.Sqrt(input));
|
||||
AssertSqrtBound(input, DoubleDouble.Sqrt(input));
|
||||
}
|
||||
DoubleDouble.IsNaN(DDMath.Sqrt(DoubleDouble.FromComponents(0.0, -1.0))).ShouldBeTrue();
|
||||
DoubleDouble.IsNaN(DoubleDouble.Sqrt(DoubleDouble.FromComponents(0.0, -1.0))).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -177,7 +177,7 @@ public class PreciseMathSqrtTests
|
||||
DoubleDouble input = DoubleDouble.FromComponents(high, low);
|
||||
if (DoubleDouble.IsFinite(input) && input.High > 0.0)
|
||||
{
|
||||
AssertSqrtBound(input, DDMath.Sqrt(input));
|
||||
AssertSqrtBound(input, DoubleDouble.Sqrt(input));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -194,7 +194,7 @@ public class PreciseMathSqrtTests
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double low = Math.ScaleB(sign, exponent);
|
||||
DoubleDouble actual = DDMath.Sqrt(DoubleDouble.FromComponents(1.0, low));
|
||||
DoubleDouble actual = DoubleDouble.Sqrt(DoubleDouble.FromComponents(1.0, low));
|
||||
actual.High.ShouldBe(1.0);
|
||||
actual.Low.ShouldBe(Math.ScaleB(sign, exponent - 1));
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
@@ -358,9 +358,6 @@ public class PreciseMathSqrtTests
|
||||
DoubleDouble actual = DoubleDouble.Sqrt(input);
|
||||
actual.High.ShouldBe(high);
|
||||
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(BitConverter.DoubleToInt64Bits(low));
|
||||
DoubleDouble facade = DDMath.Sqrt(input);
|
||||
BitConverter.DoubleToInt64Bits(facade.High).ShouldBe(BitConverter.DoubleToInt64Bits(actual.High));
|
||||
BitConverter.DoubleToInt64Bits(facade.Low).ShouldBe(BitConverter.DoubleToInt64Bits(actual.Low));
|
||||
AssertSqrtBound(input, actual);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ public class PreciseMathTests
|
||||
foreach (double scalar in scalars)
|
||||
{
|
||||
// Binary64 is an independent reference when the input has no residual.
|
||||
DoubleDouble actual = DDMath.Abs(new DoubleDouble(scalar));
|
||||
DoubleDouble actual = DoubleDouble.Abs(new DoubleDouble(scalar));
|
||||
AssertBits(actual, double.IsNaN(scalar) ? double.NaN : Math.Abs(scalar), 0.0);
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
}
|
||||
@@ -19,8 +19,8 @@ public class PreciseMathTests
|
||||
{
|
||||
DoubleDouble positive = DoubleDouble.FromComponents(1.0, low);
|
||||
DoubleDouble negative = DoubleDouble.FromComponents(-1.0, -low);
|
||||
AssertBits(DDMath.Abs(positive), 1.0, low);
|
||||
AssertBits(DDMath.Abs(negative), 1.0, low);
|
||||
AssertBits(DoubleDouble.Abs(positive), 1.0, low);
|
||||
AssertBits(DoubleDouble.Abs(negative), 1.0, low);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,8 +22,11 @@ already-normalized results. Mathematical constants use precomputed high/low pair
|
||||
checked against independently computed high-precision values; accessing them does
|
||||
not perform double-double arithmetic or allocate on the heap.
|
||||
|
||||
- Arithmetic: unary `+`/`-`, binary `+`, `-`, `*`, `/`, and both operand orders with
|
||||
a `double`. Addition retains residuals under cancellation; multiplication uses
|
||||
- Arithmetic: static `Add`, `Subtract`, `Multiply`, and `Divide` methods accept
|
||||
`DoubleDouble` pairs and both operand orders with a `double`. `Plus` and `Negate`
|
||||
provide unary operations. The arithmetic operators (`+`, `-`, `*`, `/`, unary
|
||||
`+`/`-`, and `++`/`--`) are thin wrappers around these methods.
|
||||
Addition retains residuals under cancellation; multiplication uses
|
||||
fused multiply-add; division uses residual corrections. Mixed `double` operators
|
||||
use specialized scalar paths rather than promoting the scalar to `DoubleDouble`.
|
||||
Division retains the complete first remainder and applies two quotient corrections,
|
||||
@@ -92,7 +95,7 @@ DoubleDouble quarterTurn = DoubleDouble.PiOver2;
|
||||
The factors avoid deriving constants at runtime; the multiplication itself remains
|
||||
approximate DD arithmetic, so conversions are not guaranteed exact round trips.
|
||||
The list is mathematical and dimensionless, not a table of unit-dependent physical
|
||||
constants. The natural logarithm function is provided separately by `DDMath.Log`.
|
||||
constants. The natural logarithm function is provided separately by `DoubleDouble.Log`.
|
||||
|
||||
## Mathematical functions
|
||||
|
||||
@@ -102,16 +105,40 @@ The additional `InvSqrt` helper and three-argument `Hypot` overload live alongsi
|
||||
them. Exponential functions live in `DoubleDouble.ExponentialFunctions.cs`:
|
||||
`Exp`, `Exp2`, `Exp10`, `ExpM1`, `Exp2M1`, and `Exp10M1` complete
|
||||
`IExponentialFunctions<DoubleDouble>`, including cancellation-safe overrides of
|
||||
the interface's default minus-one methods. `DDMath` exposes thin forwarding
|
||||
wrappers for roots and exponentials; logarithm, power, and reciprocal kernels
|
||||
remain in `DDMath`.
|
||||
the interface's default minus-one methods. Natural logarithm and power kernels
|
||||
live in `DoubleDouble.Log.cs` and `DoubleDouble.Pow.cs`; reciprocal lives in
|
||||
`DoubleDouble.NumberBase.cs`. Arithmetic implementations live in
|
||||
`DoubleDouble.Arithmetic.cs`, with forwarding operators in `DoubleDouble.Operators.cs`.
|
||||
|
||||
The `DDMath` static class provides:
|
||||
All mathematical functions are static members of `DoubleDouble`. The former
|
||||
`DDMath` facade has been removed: migrate `DDMath.Function(...)` calls to
|
||||
`DoubleDouble.Function(...)`. This is a breaking API change; no compatibility
|
||||
facade is provided.
|
||||
|
||||
`DoubleDoubleExtensions`, in the same `Just.PreciseMath` namespace, also provides
|
||||
instance-style calls for `Abs`, `Square`, `Reciprocal`, `Sqrt`, `InvSqrt`, `Cbrt`,
|
||||
all six exponential functions, `Log`, `RootN(int)`, and `Pow` with `int`, `double`,
|
||||
or `DoubleDouble` exponents. These extensions return a result without modifying
|
||||
the receiver and forward directly to the corresponding static method, preserving
|
||||
its numerical contract and component bits. Arithmetic remains available through
|
||||
operators and named static methods; `Hypot` remains a static multi-operand function.
|
||||
|
||||
```csharp
|
||||
using Just.PreciseMath;
|
||||
|
||||
DoubleDouble value = new(-4.0);
|
||||
DoubleDouble magnitude = value.Abs();
|
||||
DoubleDouble inverse = value.Abs().Square().Sqrt().Reciprocal();
|
||||
DoubleDouble power = magnitude.Pow(3);
|
||||
DoubleDouble root = magnitude.RootN(2);
|
||||
// value is still -4; inverse is 0.25.
|
||||
```
|
||||
|
||||
`DoubleDouble` provides:
|
||||
|
||||
- `Abs(DoubleDouble)`: preserves both components, maps either signed zero to
|
||||
positive zero and either infinity to positive infinity, and returns canonical NaN.
|
||||
It shares the existing `DoubleDouble.Abs` implementation.
|
||||
- `Square(DoubleDouble)`: forwards to the specialized `DoubleDouble.Square` kernel.
|
||||
- `Square(DoubleDouble)`: uses a specialized double-double squaring kernel.
|
||||
It combines the equal cross terms with FMA, normalizes, then incorporates the
|
||||
low-component square without losing selected exactly representable tails.
|
||||
Either signed zero maps to positive zero, either infinity to positive infinity,
|
||||
@@ -216,17 +243,17 @@ DoubleDouble cubeRoot = DoubleDouble.Cbrt(new DoubleDouble(-8.0));
|
||||
DoubleDouble distance = DoubleDouble.Hypot(new DoubleDouble(3.0), new DoubleDouble(4.0));
|
||||
DoubleDouble norm3D = DoubleDouble.Hypot(new DoubleDouble(2.0), new DoubleDouble(3.0), new DoubleDouble(6.0));
|
||||
DoubleDouble fifthRoot = DoubleDouble.RootN(new DoubleDouble(2.0), 5);
|
||||
DoubleDouble reciprocal = DDMath.Reciprocal(new DoubleDouble(3.0));
|
||||
DoubleDouble magnitude = DDMath.Abs(-root);
|
||||
DoubleDouble smallPower = DDMath.Pow(new DoubleDouble(2.0), -1024);
|
||||
DoubleDouble reciprocal = DoubleDouble.Reciprocal(new DoubleDouble(3.0));
|
||||
DoubleDouble magnitude = DoubleDouble.Abs(-root);
|
||||
DoubleDouble smallPower = DoubleDouble.Pow(new DoubleDouble(2.0), -1024);
|
||||
DoubleDouble exponential = DoubleDouble.Exp(new DoubleDouble(1.0));
|
||||
DoubleDouble binaryPower = DoubleDouble.Exp2(new DoubleDouble(-1000.0));
|
||||
DoubleDouble decimalPower = DoubleDouble.Exp10(new DoubleDouble(0.5));
|
||||
DoubleDouble tinyChange = DoubleDouble.ExpM1(new DoubleDouble(1e-30));
|
||||
DoubleDouble logarithm = DDMath.Log(new DoubleDouble(10.0));
|
||||
DoubleDouble fractionalPower = DDMath.Pow(new DoubleDouble(2.0), 0.5);
|
||||
DoubleDouble logarithm = DoubleDouble.Log(new DoubleDouble(10.0));
|
||||
DoubleDouble fractionalPower = DoubleDouble.Pow(new DoubleDouble(2.0), 0.5);
|
||||
DoubleDouble preciseExponent = DoubleDouble.FromComponents(0.5, 1e-30);
|
||||
DoubleDouble precisePower = DDMath.Pow(new DoubleDouble(2.0), preciseExponent);
|
||||
DoubleDouble precisePower = DoubleDouble.Pow(new DoubleDouble(2.0), preciseExponent);
|
||||
```
|
||||
|
||||
Reciprocal tests check bitwise equivalence with `1.0 / value` and independently
|
||||
@@ -254,7 +281,7 @@ at 450 and 650 digits, including `int.MinValue` and `int.MaxValue`.
|
||||
`RootN(x, -1)` retains the reciprocal contract, including its subnormal error floor.
|
||||
Hypotenuse tests use exact squared inequalities with `2^-100` relative error plus
|
||||
one minimum binary64 subnormal. Tests also check exact binary cases, sparse lows,
|
||||
special-value signs, facade component-bit agreement, and constrained generic dispatch.
|
||||
special-value signs and constrained generic dispatch.
|
||||
The 2D and 3D hypotenuse overflow tests bracket the exact threshold, including scaled
|
||||
55/48/73 and 1/2/2/3 Pythagorean cases at the midpoint. Three-coordinate tests include
|
||||
all signs and permutations of boundary cases and sparse corrections, including
|
||||
@@ -275,9 +302,9 @@ check `2^-100` relative error plus one minimum binary64 subnormal, with a separa
|
||||
bounded reference-rounding allowance. The base-two/base-ten and minus-one fixtures
|
||||
are checked at 450/650 digits, including adjacent lows at true range boundaries,
|
||||
series transitions, and tiny inputs. Additional component checks pin sparse
|
||||
base-two corrections; constrained generic calls and facade calls match direct
|
||||
calls bit-for-bit. Integer-power tests use exact rational
|
||||
references and high-precision fixtures for large exponents; their tested absolute
|
||||
base-two corrections; constrained generic calls match direct calls bit-for-bit.
|
||||
Integer-power tests use exact rational references and high-precision fixtures for
|
||||
large exponents; their tested absolute
|
||||
error bound is `|exact result| * (|exponent| + 1) * 2^-100 + 2^-1074`, not uniform
|
||||
relative accuracy independent of the exponent. These functions are approximate;
|
||||
precision decreases near underflow and approximation can affect results extremely
|
||||
@@ -402,8 +429,8 @@ bool success = DoubleDouble.TryParse("1.25e-2".AsSpan(), CultureInfo.InvariantCu
|
||||
|
||||
## Deferred scope
|
||||
|
||||
Natural `DDMath.Log`, the complete exponential family on `DoubleDouble`, and all
|
||||
three `DDMath.Pow` overloads are implemented.
|
||||
Natural `DoubleDouble.Log`, the complete exponential family on `DoubleDouble`, and all
|
||||
three `DoubleDouble.Pow` overloads are implemented.
|
||||
Logarithms in other bases, generic-math interfaces beyond `ISignedNumber`,
|
||||
`IFloatingPointConstants`, `IRootFunctions`, and `IExponentialFunctions`, additional text formats/shortest-
|
||||
round-trip formatting, and non-arithmetic performance benchmarks remain deferred.
|
||||
|
||||
Reference in New Issue
Block a user