the first optimization round
.NET Test / .NET tests (push) Successful in 1m26s

This commit is contained in:
2026-09-14 13:58:04 +04:00
parent cb2524980d
commit 08036a31d5
12 changed files with 1316 additions and 49 deletions
@@ -0,0 +1,134 @@
using BenchmarkDotNet.Attributes;
namespace Just.PreciseMath.Benchmarks;
/// <summary>Compares a+b with a-(-b) using matched results and normalization paths.</summary>
/// <remarks>Includes array and loop costs. Negation and fixture validation are outside timing.</remarks>
[MemoryDiagnoser]
public class AddSubtractMatchedBenchmarks
{
private const int Count = 64;
private readonly DoubleDouble[] _left = new DoubleDouble[Count];
private readonly DoubleDouble[] _right = new DoubleDouble[Count];
private readonly DoubleDouble[] _results = new DoubleDouble[Count];
[Params("ZeroCorrection", "NonzeroCorrection", "Cancellation", "MixedCorrection")]
public string Scenario { get; set; } = "NonzeroCorrection";
[GlobalSetup(Target = nameof(Add))]
public void SetupAdd()
{
Setup(false);
}
[GlobalSetup(Target = nameof(Subtract))]
public void SetupSubtract()
{
Setup(true);
}
[Benchmark(Baseline = true, OperationsPerInvoke = Count)]
public DoubleDouble[] Add()
{
for (int i = 0; i < Count; i++)
{
_results[i] = _left[i] + _right[i];
}
return _results;
}
[Benchmark(OperationsPerInvoke = Count)]
public DoubleDouble[] Subtract()
{
for (int i = 0; i < Count; i++)
{
_results[i] = _left[i] - _right[i];
}
return _results;
}
private void Setup(bool subtract)
{
if (Scenario is not ("ZeroCorrection" or "NonzeroCorrection" or "Cancellation" or "MixedCorrection"))
{
throw new InvalidOperationException($"Unknown matched add/subtract scenario: {Scenario}.");
}
for (int i = 0; i < Count; i++)
{
// Cycle both high signs and moderate scales. High sums/differences
// are exact: the significands are small integer multiples of 1/128.
int exponent = (((i / 4) % 3) - 1) * 80;
double leftSign = i % 2 == 0 ? 1.0 : -1.0;
double rightSign = i % 4 < 2 ? 1.0 : -1.0;
double leftHigh = leftSign * Math.ScaleB(1.25 + ((i % 7) / 32.0), exponent);
double rightHigh = rightSign * Math.ScaleB(1.0 + (((i % 5) - 2) / 128.0), exponent);
bool zeroCorrection = Scenario == "ZeroCorrection" || (Scenario == "MixedCorrection" && i % 3 == 0);
double leftLow;
double rightLow;
double expectedHigh;
double expectedLow;
if (Scenario == "Cancellation")
{
// Equal/opposite highs leave the exact normalized expansion
// (sign*2^(e-54), sign*2^(e-108)), not a binary64-only result.
rightHigh = -leftHigh;
leftLow = leftSign * Math.ScaleB(1.0, exponent - 54);
rightLow = leftSign * Math.ScaleB(1.0, exponent - 108);
expectedHigh = leftLow;
expectedLow = rightLow;
}
else
{
leftLow = leftSign * Math.ScaleB(1.0, exponent - 80);
rightLow = zeroCorrection ? -leftLow : leftLow / 2.0;
expectedHigh = leftHigh + rightHigh;
// This exact low sum is far below half an ulp of the high sum.
expectedLow = zeroCorrection ? 0.0 : leftSign * Math.ScaleB(3.0, exponent - 81);
}
DoubleDouble left = DoubleDouble.FromComponents(leftHigh, leftLow);
DoubleDouble right = DoubleDouble.FromComponents(rightHigh, rightLow);
DoubleDouble negativeRight = -right;
CheckComponents(left, leftHigh, leftLow, i, "left input");
CheckComponents(right, rightHigh, rightLow, i, "right input");
CheckComponents(negativeRight, -rightHigh, -rightLow, i, "negated right input");
if (!DoubleDouble.IsFinite(left) || !DoubleDouble.IsFinite(right)
|| left.High == 0.0 || right.High == 0.0
|| Math.Abs(Math.ILogB(left.High)) > 200 || Math.Abs(Math.ILogB(right.High)) > 200)
{
throw new InvalidOperationException($"{Scenario}[{i}] is outside the ordinary finite input range.");
}
// Independent dyadic expectations, not one DD operator as the other's oracle.
CheckComponents(left + right, expectedHigh, expectedLow, i, "addition");
CheckComponents(left - negativeRight, expectedHigh, expectedLow, i, "subtraction");
// Both operators feed these same effective components into AddFinite.
if ((FinalCorrection(left, right) == 0.0) != zeroCorrection)
{
throw new InvalidOperationException($"{Scenario}[{i}] does not select the expected normalization path.");
}
_left[i] = left;
// Target-specific setup keeps array fields and timed memory access identical.
_right[i] = subtract ? negativeRight : right;
}
}
private void CheckComponents(DoubleDouble actual, double high, double low, int index, string operation)
{
if (BitConverter.DoubleToInt64Bits(actual.High) != BitConverter.DoubleToInt64Bits(high)
|| BitConverter.DoubleToInt64Bits(actual.Low) != BitConverter.DoubleToInt64Bits(low))
{
throw new InvalidOperationException($"{Scenario}[{index}] has unexpected {operation} components.");
}
}
// Setup-only characterization, not an accuracy oracle. Keep this path check
// synchronized with AddFinite if the production kernel changes in the future.
private static double FinalCorrection(DoubleDouble left, DoubleDouble right)
{
(double high, double highError) = PreciseMathHelper.TwoAdd(left.High, right.High);
(double low, double lowError) = PreciseMathHelper.TwoAdd(left.Low, right.Low);
(double middle, double middleError) = PreciseMathHelper.TwoAdd(highError, low);
(double _, double sumError) = PreciseMathHelper.TwoAdd(high, middle);
return sumError + (middleError + lowError);
}
}