This commit is contained in:
@@ -202,6 +202,83 @@ public class PreciseMathSqrtTests
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(-1.0)]
|
||||
[InlineData(1.0)]
|
||||
public void ScalingPreservesRepresentableSparseRootCorrections(double sign)
|
||||
{
|
||||
// sqrt(2^1000 + d) = 2^500 + d/2^501 + O(d^2/2^1500).
|
||||
// Scaling d=+/-2^-174 down by 2^1000 erases it, but the root's
|
||||
// +/-2^-675 low is representable. Prove its rounding interval exactly.
|
||||
DoubleDouble input = DoubleDouble.FromComponents(Math.ScaleB(1.0, 1000), Math.ScaleB(sign, -174));
|
||||
AssertSparseRoot(input, Math.ScaleB(1.0, 500), Math.ScaleB(sign, -675));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SparseRootCorrectionsSurviveScalingAndCorrectionDivisionBoundaries()
|
||||
{
|
||||
// Exact squares with both even and odd high exponents. Include scaled
|
||||
// lows on both sides of the separate-correction dispatch, subnormal
|
||||
// scaled lows, fully erased scaled lows, and subnormal output lows.
|
||||
foreach (int exponent in new[] { 0, 2, 100, 1000, 1022 })
|
||||
{
|
||||
foreach (double rootMantissa in new[] { 1.0, 1.5 })
|
||||
{
|
||||
double high = Math.ScaleB(rootMantissa * rootMantissa, exponent);
|
||||
double rootHigh = Math.ScaleB(rootMantissa, exponent / 2);
|
||||
foreach (int gap in new[] { -1019, -1020, -1021, -1074, -1075, -1174 })
|
||||
{
|
||||
double low = Math.ScaleB(1.0, exponent + gap);
|
||||
double rootLow = Math.ScaleB(low, -(exponent / 2)) / (2.0 * rootMantissa);
|
||||
if (rootLow == 0.0)
|
||||
{
|
||||
continue; // This matrix targets representable nonzero corrections.
|
||||
}
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
// AssertSparseRoot independently proves this candidate's
|
||||
// rounding interval; the first-order formula is not an oracle.
|
||||
AssertSparseRoot(DoubleDouble.FromComponents(high, sign * low), rootHigh, sign * rootLow);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SparseRootDispatchNeighborsRetainBothSignsWithoutDoubleCounting()
|
||||
{
|
||||
// With high=2^1000, lows around 2^-20 scale to the dispatch at 2^-1020.
|
||||
// Keep the predicted output normal so candidate scaling is exact.
|
||||
double threshold = Math.ScaleB(1.0, -20);
|
||||
foreach (double low in new[] { Math.BitDecrement(threshold), threshold, Math.BitIncrement(threshold) })
|
||||
{
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
DoubleDouble input = DoubleDouble.FromComponents(Math.ScaleB(1.0, 1000), sign * low);
|
||||
AssertSparseRoot(input, Math.ScaleB(1.0, 500), Math.ScaleB(sign * low, -501));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertSparseRoot(DoubleDouble input, double high, double low)
|
||||
{
|
||||
BigInteger x = (Units(input.High) + Units(input.Low)) << 1074;
|
||||
BigInteger center = Units(high) + Units(low);
|
||||
BigInteger previous = Units(high) + Units(Math.BitDecrement(low));
|
||||
BigInteger following = Units(high) + Units(Math.BitIncrement(low));
|
||||
// Strict bounds avoid relying on a tie rule or a rounded sqrt oracle.
|
||||
((previous + center) * (previous + center) < (x << 2)).ShouldBeTrue();
|
||||
((center + following) * (center + following) > (x << 2)).ShouldBeTrue();
|
||||
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);
|
||||
}
|
||||
|
||||
private static void AssertSqrtBound(DoubleDouble input, DoubleDouble actual)
|
||||
{
|
||||
DoubleDouble.IsFinite(actual).ShouldBeTrue();
|
||||
|
||||
Reference in New Issue
Block a user