This commit is contained in:
@@ -978,6 +978,114 @@ public class DoubleDoubleArithmeticTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-450)]
|
||||
[InlineData(0)]
|
||||
[InlineData(450)]
|
||||
public void SquareRetainsExactCrossAndLowSquaredTerms(int exponent)
|
||||
{
|
||||
// (h - h*2^-54)^2 = h^2 - h^2*2^-53 + h^2*2^-108.
|
||||
// (h + h*2^-53)^2 = h^2 + h^2*2^-52 + h^2*2^-106.
|
||||
// Both sums fit exactly in two components, including fast-range endpoints.
|
||||
double high = Math.ScaleB(1.0, exponent);
|
||||
double highSquared = Math.ScaleB(1.0, 2 * exponent);
|
||||
DoubleDouble below = DoubleDouble.FromComponents(high, -Math.ScaleB(1.0, exponent - 54));
|
||||
DoubleDouble above = DoubleDouble.FromComponents(high, Math.ScaleB(1.0, exponent - 53));
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SquareHandlesSpecialValuesAndExactBinaryPowers()
|
||||
{
|
||||
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++)
|
||||
{
|
||||
// A binary power squared needs only exponent arithmetic, not a DD oracle.
|
||||
double high = Math.ScaleB(1.0, exponent);
|
||||
double expected = Math.ScaleB(1.0, 2 * exponent);
|
||||
CheckBits(DoubleDouble.Square(new DoubleDouble(high)), expected);
|
||||
CheckBits(DoubleDouble.Square(new DoubleDouble(-high)), expected);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SquareMeetsExactRationalBoundsAcrossRangesAndDispatchTransitions()
|
||||
{
|
||||
Random random = new(57721);
|
||||
for (int exponent = -1074; exponent <= 1023; exponent++)
|
||||
{
|
||||
double high = Math.ScaleB(1.0 + random.NextDouble(), exponent);
|
||||
double low = Math.ScaleB(random.NextDouble(), exponent - 53);
|
||||
foreach (double residual in new[] { 0.0, low, -low, double.Epsilon, -double.Epsilon })
|
||||
{
|
||||
AssertSquare(DoubleDouble.FromComponents(high, residual));
|
||||
}
|
||||
double power = Math.ScaleB(1.0, exponent);
|
||||
AssertSquare(new DoubleDouble(Math.BitDecrement(power)));
|
||||
AssertSquare(new DoubleDouble(power));
|
||||
AssertSquare(new DoubleDouble(Math.BitIncrement(power)));
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SquareUsesTheCompleteInputForOverflowAndUnderflowClassification()
|
||||
{
|
||||
// Neighborhoods of sqrt(overflow midpoint) and sqrt(epsilon/2).
|
||||
// The exact integer oracle, not Math.Sqrt or another DD operation,
|
||||
// decides which side of each threshold the actual stored input lies on.
|
||||
double overflowHigh = Math.ScaleB(1.0, 512);
|
||||
// At low=-2^457, the square exceeds the midpoint by low^2;
|
||||
// the next more-negative low puts it strictly below the midpoint.
|
||||
double overflowLow = -Math.ScaleB(1.0, 457);
|
||||
double underflowHigh = Math.ScaleB(Math.Sqrt(0.5), -537);
|
||||
double underflowLow = Math.ScaleB(1.0, -591);
|
||||
foreach (double high in new[] { Math.BitDecrement(overflowHigh), overflowHigh, Math.BitIncrement(overflowHigh) })
|
||||
{
|
||||
foreach (double low in new[] { Math.BitDecrement(overflowLow), overflowLow, Math.BitIncrement(overflowLow), 0.0 })
|
||||
{
|
||||
AssertSquare(DoubleDouble.FromComponents(high, low));
|
||||
}
|
||||
}
|
||||
foreach (double high in new[] { Math.BitDecrement(underflowHigh), underflowHigh, Math.BitIncrement(underflowHigh) })
|
||||
{
|
||||
foreach (double low in new[] { -underflowLow, 0.0, underflowLow })
|
||||
{
|
||||
AssertSquare(DoubleDouble.FromComponents(high, low));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertSquare(DoubleDouble value)
|
||||
{
|
||||
BigInteger input = Units(value);
|
||||
BigInteger numerator = input * input;
|
||||
BigInteger denominator = BigInteger.One << 1074;
|
||||
BigInteger overflow = Units(double.MaxValue) + Units(Math.ScaleB(1.0, 970));
|
||||
DoubleDouble actual = DoubleDouble.Square(value);
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
DoubleDouble.IsNegative(actual).ShouldBeFalse();
|
||||
if (numerator >= overflow * denominator)
|
||||
{
|
||||
CheckBits(actual, double.PositiveInfinity);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssertRelative(actual, numerator, denominator);
|
||||
// The absolute error floor must not hide a wrong zero/nonzero result.
|
||||
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 (double[] Scalars, DoubleDouble[] Values) ScalarArithmeticCompatibilityCases()
|
||||
{
|
||||
List<double> scalars = [0.0, -0.0, 1.0, -1.0, 2.0, -2.0, 3.0, -3.0, 100.0, -100.0,
|
||||
|
||||
Reference in New Issue
Block a user