This commit is contained in:
@@ -35,6 +35,53 @@ public class DoubleDoubleArithmeticTests
|
||||
Check(6.0 / new DoubleDouble(2.0), 3.0, 0.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpansionCancellationRetainsExactComponentsAcrossTheAdditionGuard()
|
||||
{
|
||||
// (2^e + 2^(e-54)) - (2^e - 2^(e-108)) is exactly the
|
||||
// normalized pair (2^(e-54), 2^(e-108)). The smallest residual
|
||||
// is epsilon; the largest case exercises the boundary fallback.
|
||||
foreach (int exponent in new[] { -966, -450, 0, 450, 1020, 1021 })
|
||||
{
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double high = sign * Math.ScaleB(1.0, exponent);
|
||||
double small = sign * Math.ScaleB(1.0, exponent - 54);
|
||||
double tiny = sign * Math.ScaleB(1.0, exponent - 108);
|
||||
DoubleDouble left = DoubleDouble.FromComponents(high, small);
|
||||
DoubleDouble right = DoubleDouble.FromComponents(high, -tiny);
|
||||
CheckBoundary(left - right, small, tiny);
|
||||
CheckBoundary(right - left, -small, -tiny);
|
||||
CheckBoundary(left + (-right), small, tiny);
|
||||
CheckBoundary((-right) + left, small, tiny);
|
||||
CheckBoundary(left - left, 0.0, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpansionSubtractionHandlesSpecialValuesAlongsideNonzeroResiduals()
|
||||
{
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble value = DoubleDouble.FromComponents(sign, sign * double.Epsilon);
|
||||
foreach (double special in new[] { 0.0, -0.0, double.NegativeInfinity, double.PositiveInfinity, double.NaN })
|
||||
{
|
||||
DoubleDouble other = new(special);
|
||||
if (special == 0.0)
|
||||
{
|
||||
CheckBoundary(value - other, value.High, value.Low);
|
||||
CheckBoundary(other - value, -value.High, -value.Low);
|
||||
}
|
||||
else
|
||||
{
|
||||
CheckBits(value - other, sign - special);
|
||||
CheckBits(other - value, special - sign);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScalarLeftSubtractionAppliesTheRequestedOperandOrder()
|
||||
{
|
||||
@@ -135,6 +182,41 @@ public class DoubleDoubleArithmeticTests
|
||||
Check(scalar * value, high, low);
|
||||
Check(value * (-scalar), -high, -low);
|
||||
Check((-scalar) * value, -high, -low);
|
||||
CheckBoundary(value * new DoubleDouble(scalar), high, low);
|
||||
CheckBoundary(new DoubleDouble(scalar) * value, high, low);
|
||||
CheckBoundary(value * new DoubleDouble(-scalar), -high, -low);
|
||||
CheckBoundary(new DoubleDouble(-scalar) * value, -high, -low);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExpansionProductRetainsTheLowLowTermAtFastRangeEndpoints()
|
||||
{
|
||||
// (1 + 2^-53)(1 - 2^-54) = 1 + 2^-54 - 2^-107 exactly.
|
||||
// Its residual is BitDecrement(2^-54); omitting low*low loses that bit.
|
||||
// Power-of-two scaling keeps both expected components representable,
|
||||
// including exponent sums at each inclusive fast-path endpoint.
|
||||
foreach (int leftExponent in new[] { -450, 0, 450 })
|
||||
{
|
||||
foreach (int rightExponent in new[] { -450, 0, 450 })
|
||||
{
|
||||
foreach (double leftSign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
foreach (double rightSign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble left = DoubleDouble.FromComponents(leftSign * Math.ScaleB(1.0, leftExponent),
|
||||
leftSign * Math.ScaleB(1.0, leftExponent - 53));
|
||||
DoubleDouble right = DoubleDouble.FromComponents(rightSign * Math.ScaleB(1.0, rightExponent),
|
||||
-rightSign * Math.ScaleB(1.0, rightExponent - 54));
|
||||
int exponent = leftExponent + rightExponent;
|
||||
double sign = leftSign * rightSign;
|
||||
double high = sign * Math.ScaleB(1.0, exponent);
|
||||
double low = sign * Math.ScaleB(Math.BitDecrement(Math.ScaleB(1.0, -54)), exponent);
|
||||
CheckBoundary(left * right, high, low);
|
||||
CheckBoundary(right * left, high, low);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
@@ -150,6 +232,47 @@ public class DoubleDoubleArithmeticTests
|
||||
AssertRelative(-1.0 / value, -numerator, Units(value));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DivisionRetainsSubnormalCorrectionsWithOrdinaryHighComponents()
|
||||
{
|
||||
// Dividing (1 + epsilon) by +/-1 is exact. The outer division is
|
||||
// ordinary, but its second residual product can use the boundary path.
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble numerator = DoubleDouble.FromComponents(sign, sign * double.Epsilon);
|
||||
foreach (double denominator in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double high = sign / denominator;
|
||||
double low = high * double.Epsilon;
|
||||
CheckBoundary(numerator / new DoubleDouble(denominator), high, low);
|
||||
CheckBoundary(numerator / denominator, high, low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubnormalProductsWithLargeNormalsRetainExactResultsInBothOrders()
|
||||
{
|
||||
// 2^-1074 * 2^1023 = 2^-51 exactly, despite the subnormal input.
|
||||
foreach (double leftSign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
foreach (double rightSign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double tiny = leftSign * double.Epsilon;
|
||||
double large = rightSign * Math.ScaleB(1.0, 1023);
|
||||
double expected = (leftSign * rightSign) * Math.ScaleB(1.0, -51);
|
||||
DoubleDouble left = new(tiny);
|
||||
DoubleDouble right = new(large);
|
||||
CheckBoundary(left * right, expected, 0.0);
|
||||
CheckBoundary(right * left, expected, 0.0);
|
||||
CheckBoundary(left * large, expected, 0.0);
|
||||
CheckBoundary(large * left, expected, 0.0);
|
||||
CheckBoundary(tiny * right, expected, 0.0);
|
||||
CheckBoundary(right * tiny, expected, 0.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExtremeFiniteOperationsDoNotOverflowIntermediates()
|
||||
{
|
||||
@@ -170,6 +293,29 @@ public class DoubleDoubleArithmeticTests
|
||||
Check(below + Math.ScaleB(1.0, 969), double.MaxValue, 0.0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SubnormalHighProductsRetainANormalPartnersResidual()
|
||||
{
|
||||
// (2^-1074, 0) * (2^1023, 2^969) = (2^-51, 2^-105), exactly.
|
||||
// The high product is ordinary despite the subnormal input high.
|
||||
foreach (double leftSign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
foreach (double rightSign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double scalar = leftSign * double.Epsilon;
|
||||
DoubleDouble tiny = new(scalar);
|
||||
DoubleDouble large = DoubleDouble.FromComponents(rightSign * Math.ScaleB(1.0, 1023),
|
||||
rightSign * Math.ScaleB(1.0, 969));
|
||||
double high = (leftSign * rightSign) * Math.ScaleB(1.0, -51);
|
||||
double low = (leftSign * rightSign) * Math.ScaleB(1.0, -105);
|
||||
CheckBoundary(tiny * large, high, low);
|
||||
CheckBoundary(large * tiny, high, low);
|
||||
CheckBoundary(scalar * large, high, low);
|
||||
CheckBoundary(large * scalar, high, low);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(1.0)]
|
||||
[InlineData(-1.0)]
|
||||
|
||||
Reference in New Issue
Block a user