small arithmetic optimization
.NET Test / .NET tests (push) Successful in 1m56s

This commit is contained in:
2026-09-20 00:52:24 +04:00
parent 34076c2466
commit 75925ab184
18 changed files with 1366 additions and 21 deletions
@@ -141,7 +141,21 @@ public class ArithmeticRangeTests
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;
// Pin the final-add caller invariants before removing public
// dispatch. Sparse correction products above can use the exact
// fallback; these bounds must cover their results too.
normalized.High.ShouldNotBe(0.0);
(Math.Abs(correction) < Math.Abs(quotient) * 0.5).ShouldBeTrue();
(Math.Abs(normalized.High) < Math.ScaleB(1.0, 909)).ShouldBeTrue();
(Math.Abs(remainder.High) < Math.ScaleB(1.0, 470)).ShouldBeTrue();
(Math.Abs(finalCorrection) < Math.ScaleB(1.0, 921)).ShouldBeTrue();
PreciseMathHelper.IsAdditionWithinFastRange(normalized.High).ShouldBeTrue();
PreciseMathHelper.IsAdditionWithinFastRange(finalCorrection).ShouldBeTrue();
(Math.ILogB(normalized.High) <= 1020).ShouldBeTrue();
(Math.ILogB(finalCorrection) <= 1020).ShouldBeTrue();
expected = PreviousFiniteScalarAdd(normalized.High, normalized.Low, finalCorrection);
AssertComponentBits(normalized + finalCorrection, expected);
AssertComponentBits(PreciseMathHelper.AddScalarFinite(normalized.High, normalized.Low, finalCorrection), expected);
actual = left / right;
}
else if (operation == "*")
@@ -231,6 +245,58 @@ public class ArithmeticRangeTests
}
}
[Fact]
public void ScalarAdditionPreservesThePreviousFiniteSequence()
{
// Characterization, not an accuracy oracle. Include cancellation, sparse
// lows, signed high zeros, and the negated zero low used by subtraction.
double[] highs = [0.0, -0.0, double.Epsilon, -double.Epsilon,
Math.ScaleB(1.0, -1022), 1.0, -1.0, Math.BitDecrement(2.0),
Math.BitDecrement(Math.ScaleB(1.0, 909)),
-Math.BitDecrement(Math.ScaleB(1.0, 909)),
Math.BitDecrement(Math.ScaleB(1.0, 921)),
-Math.BitDecrement(Math.ScaleB(1.0, 921)),
Math.BitDecrement(Math.ScaleB(1.0, 1021))];
foreach (double high in highs)
{
foreach (double low in new[] { 0.0, -0.0, double.Epsilon, -double.Epsilon,
Math.ScaleB(high, -54), -Math.ScaleB(high, -54) })
{
DoubleDouble value = DoubleDouble.FromComponents(high, low);
foreach (double scalar in highs.Concat([-high, Math.BitIncrement(high), Math.BitDecrement(high)]))
{
if ((value.High == 0.0 && scalar == 0.0)
|| Math.ILogB(value.High) > 1020 || Math.ILogB(scalar) > 1020)
{
continue; // Public signed-zero and boundary dispatch remain.
}
foreach (double signedLow in value.Low == 0.0 ? new[] { 0.0, -0.0 } : new[] { value.Low })
{
DoubleDouble expected = PreviousFiniteScalarAdd(value.High, signedLow, scalar);
AssertComponentBits(PreciseMathHelper.AddScalar(value.High, signedLow, scalar), expected);
AssertComponentBits(PreciseMathHelper.AddScalarFinite(value.High, signedLow, scalar), expected);
}
}
}
}
}
private static DoubleDouble PreviousFiniteScalarAdd(double high, double low, double value)
{
// Freeze the pre-extraction scalar sequence and public normalization.
// Do not redirect this reference to the extracted production helper.
(double sum, double error) = PreciseMathHelper.TwoAdd(high, value);
(double middle, double middleError) = PreciseMathHelper.TwoAdd(error, low);
(double result, double residual) = PreciseMathHelper.TwoAdd(sum, middle);
return DoubleDouble.FromComponents(result, residual + middleError);
}
private static void AssertComponentBits(DoubleDouble actual, DoubleDouble expected)
{
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(BitConverter.DoubleToInt64Bits(expected.Low));
}
private static void AssertDivisionResidualCancellation(DoubleDouble remainder, DoubleDouble product)
{
DoubleDouble expected = remainder - product;