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
@@ -261,6 +261,91 @@ public class PreciseMathSqrtTests
}
}
[Fact]
public void FiniteNormalizationMatchesBothPreviousSqrtSitesAcrossTheRange()
{
// Replay the unchanged scaled Newton sequence with public factories.
// This pins normalization compatibility, not root accuracy: the exact
// squared-inequality tests above remain the independent accuracy oracle.
bool sawSeparateLow = false;
bool sawZeroCorrection = false;
bool sawNonzeroCorrection = false;
foreach (DoubleDouble input in NormalizationInputs())
{
int exponent = Math.ILogB(input.High) & ~1;
double high = Math.ScaleB(input.High, -exponent);
double low = Math.ScaleB(input.Low, -exponent);
bool separateLow = exponent > 0 && input.Low != 0.0 && Math.Abs(low) < Math.ScaleB(1.0, -1020);
sawSeparateLow |= separateLow;
if (separateLow)
{
low = 0.0;
}
double estimate = Math.Sqrt(high);
double residual = Math.FusedMultiplyAdd(-estimate, estimate, high) + low;
double correction = residual / (2.0 * estimate);
sawZeroCorrection |= correction == 0.0;
sawNonzeroCorrection |= correction != 0.0;
(estimate >= 1.0 && estimate <= 2.0).ShouldBeTrue();
(Math.Abs(correction) < Math.ScaleB(1.0, -48)).ShouldBeTrue();
(estimate + correction < 4.0).ShouldBeTrue();
DoubleDouble root = AssertFiniteNormalization(estimate, correction);
double scaledHigh = Math.ScaleB(root.High, exponent / 2);
double scaledLow = Math.ScaleB(root.Low, exponent / 2);
(Math.Abs(scaledHigh) < Math.ScaleB(1.0, 514)).ShouldBeTrue();
(Math.Abs(scaledLow) < Math.ScaleB(1.0, 461)).ShouldBeTrue();
(scaledHigh + scaledLow < Math.ScaleB(1.0, 514)).ShouldBeTrue();
DoubleDouble expected = AssertFiniteNormalization(scaledHigh, scaledLow);
if (!separateLow)
{
DoubleDouble actual = DoubleDouble.Sqrt(input);
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(BitConverter.DoubleToInt64Bits(expected.Low));
}
// The sparse branch also passed both site checks. Its additional
// correction is pinned independently by AssertSparseRoot above.
}
sawSeparateLow.ShouldBeTrue();
sawZeroCorrection.ShouldBeTrue();
sawNonzeroCorrection.ShouldBeTrue();
}
private static IEnumerable<DoubleDouble> NormalizationInputs()
{
for (int exponent = -1074; exponent <= 1023; ++exponent)
{
foreach (double significand in new[] { 1.0, Math.BitIncrement(1.0), 1.5, Math.BitDecrement(2.0) })
{
double high = Math.ScaleB(significand, exponent);
double dense = Math.ScaleB(1.0, exponent - 54);
double threshold = Math.ScaleB(1.0, (exponent & ~1) - 1020);
foreach (double low in new[] { 0.0, dense, -dense, double.Epsilon, -double.Epsilon,
Math.BitDecrement(threshold), threshold, Math.BitIncrement(threshold), -threshold })
{
DoubleDouble input = DoubleDouble.FromComponents(high, low);
if (input.High > 0.0 && DoubleDouble.IsFinite(input))
{
yield return input;
}
}
}
}
yield return DoubleDouble.FromComponents(double.MaxValue, Math.BitDecrement(Math.ScaleB(1.0, 970)));
}
private static DoubleDouble AssertFiniteNormalization(double high, double low)
{
double.IsFinite(high).ShouldBeTrue();
double.IsFinite(low).ShouldBeTrue();
double.IsFinite(high + low).ShouldBeTrue();
DoubleDouble expected = DoubleDouble.FromComponents(high, low);
DoubleDouble actual = PreciseMathHelper.NormalizeFinite(high, low);
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(BitConverter.DoubleToInt64Bits(expected.Low));
return expected;
}
private static void AssertSparseRoot(DoubleDouble input, double high, double low)
{
BigInteger x = (Units(input.High) + Units(input.Low)) << 1074;