add precomputed mathematical constants and specialize the second division residual
.NET Test / .NET tests (push) Successful in 2m23s
.NET Test / .NET tests (push) Successful in 2m23s
This commit is contained in:
@@ -80,6 +80,7 @@ public class ArithmeticRangeTests
|
||||
[InlineData("+")]
|
||||
[InlineData("-")]
|
||||
[InlineData("*")]
|
||||
[InlineData("/")]
|
||||
public void FiniteKernelsPreservePreviousComponentBits(string operation)
|
||||
{
|
||||
// Differential characterization, not an independent accuracy oracle.
|
||||
@@ -113,7 +114,39 @@ public class ArithmeticRangeTests
|
||||
{
|
||||
DoubleDouble expected;
|
||||
DoubleDouble actual;
|
||||
if (operation == "*")
|
||||
if (operation == "/")
|
||||
{
|
||||
if (left.High == 0.0 || right.High == 0.0
|
||||
|| Math.Abs(Math.ILogB(left.High)) > 450 || Math.Abs(Math.ILogB(right.High)) > 450)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
// Retain the original division expression, including its public
|
||||
// normalization. Sparse corrections may use MultiplyBoundary.
|
||||
double quotient = left.High / right.High;
|
||||
DoubleDouble remainder = left - (right * quotient);
|
||||
double correction = remainder.High / right.High;
|
||||
double.IsFinite(quotient).ShouldBeTrue();
|
||||
double.IsFinite(correction).ShouldBeTrue();
|
||||
double.IsFinite(quotient + correction).ShouldBeTrue();
|
||||
// Conservative bounds from the entry domain and the first
|
||||
// scalar product/four-TwoSum remainder, not an accuracy claim.
|
||||
(Math.Abs(quotient) <= Math.ScaleB(1.0, 901)).ShouldBeTrue();
|
||||
(Math.Abs(remainder.High) <= Math.ScaleB(1.0, 457)).ShouldBeTrue();
|
||||
(Math.Abs(correction) <= Math.ScaleB(1.0, 908)).ShouldBeTrue();
|
||||
(Math.Abs(quotient + correction) < Math.ScaleB(1.0, 909)).ShouldBeTrue();
|
||||
DoubleDouble correctionProduct = right * correction;
|
||||
AssertDivisionResidualCancellation(remainder, correctionProduct);
|
||||
remainder -= correctionProduct;
|
||||
double finalCorrection = remainder.High / right.High;
|
||||
DoubleDouble normalized = DoubleDouble.FromComponents(quotient, correction);
|
||||
DoubleDouble finiteNormalized = PreciseMathHelper.NormalizeFinite(quotient, correction);
|
||||
BitConverter.DoubleToInt64Bits(finiteNormalized.High).ShouldBe(BitConverter.DoubleToInt64Bits(normalized.High));
|
||||
BitConverter.DoubleToInt64Bits(finiteNormalized.Low).ShouldBe(BitConverter.DoubleToInt64Bits(normalized.Low));
|
||||
expected = normalized + finalCorrection;
|
||||
actual = left / right;
|
||||
}
|
||||
else if (operation == "*")
|
||||
{
|
||||
if (left.High == 0.0 || right.High == 0.0)
|
||||
{
|
||||
@@ -161,6 +194,62 @@ public class ArithmeticRangeTests
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DivisionResidualCancellationPreservesHighBitsAtBinadeEdges()
|
||||
{
|
||||
// Include both sides of normal/subnormal and binade transitions, with
|
||||
// canonical zero, dense and sparse lows. Cross-binade/sign/zero cases
|
||||
// are intentionally ineligible and must retain the general subtraction.
|
||||
List<DoubleDouble> values = [new(0.0), new(-0.0)];
|
||||
foreach (int exponent in new[] { -1074, -1022, -900, -54, 0, 1, 457, 461 })
|
||||
{
|
||||
foreach (double significand in new[] { 1.0, Math.BitIncrement(1.0), Math.BitDecrement(2.0) })
|
||||
{
|
||||
foreach (double sign in new[] { -1.0, 1.0 })
|
||||
{
|
||||
double high = sign * Math.ScaleB(significand, exponent);
|
||||
foreach (double low in new[] { 0.0, Math.ScaleB(high, -54), -Math.ScaleB(high, -54),
|
||||
double.Epsilon, -double.Epsilon })
|
||||
{
|
||||
values.Add(DoubleDouble.FromComponents(high, low));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (DoubleDouble remainder in values)
|
||||
{
|
||||
foreach (DoubleDouble product in values)
|
||||
{
|
||||
AssertDivisionResidualCancellation(remainder, product);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void AssertDivisionResidualCancellation(DoubleDouble remainder, DoubleDouble product)
|
||||
{
|
||||
DoubleDouble expected = remainder - product;
|
||||
double actual = PreciseMathHelper.SubtractDivisionCorrectionHigh(remainder, product);
|
||||
BitConverter.DoubleToInt64Bits(actual).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
|
||||
// Independent BCL classification, rather than the proposed exponent-bit
|
||||
// guard. Within one normal binade, same-sign subtraction is exact.
|
||||
if (!double.IsNormal(remainder.High) || !double.IsNormal(product.High)
|
||||
|| Math.Sign(remainder.High) != Math.Sign(product.High)
|
||||
|| Math.ILogB(remainder.High) != Math.ILogB(product.High))
|
||||
{
|
||||
return;
|
||||
}
|
||||
(double high, double highError) = PreciseMathHelper.TwoAdd(remainder.High, -product.High);
|
||||
BitConverter.DoubleToInt64Bits(highError).ShouldBe(0L);
|
||||
(double low, double lowError) = PreciseMathHelper.TwoAdd(remainder.Low, -product.Low);
|
||||
(double middle, double middleError) = PreciseMathHelper.TwoAdd(highError, low);
|
||||
BitConverter.DoubleToInt64Bits(middle).ShouldBe(BitConverter.DoubleToInt64Bits(low));
|
||||
BitConverter.DoubleToInt64Bits(middleError).ShouldBe(0L);
|
||||
(double sum, double sumError) = PreciseMathHelper.TwoAdd(high, low);
|
||||
double error = sumError + lowError;
|
||||
double simplifiedHigh = error == 0.0 ? sum : sum + error;
|
||||
BitConverter.DoubleToInt64Bits(simplifiedHigh).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
|
||||
}
|
||||
|
||||
private static void AssertMultiplicationRange(double left, double right)
|
||||
{
|
||||
int exponent = Math.ILogB(left) + Math.ILogB(right);
|
||||
|
||||
Reference in New Issue
Block a user