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,129 @@
namespace Just.PreciseMath.Benchmarks;
// Input construction stays outside timed benchmark methods.
internal sealed class ArithmeticInputs
{
internal const int Count = 64;
internal DoubleDouble[] Left { get; } = new DoubleDouble[Count];
internal DoubleDouble[] Right { get; } = new DoubleDouble[Count];
internal static ArithmeticInputs Create(string scenario, bool chain)
{
if (chain && scenario is not ("BothResidual" or "Mixed"))
{
throw new ArgumentOutOfRangeException(nameof(scenario));
}
if (scenario is not ("BinaryExact" or "DecimalResidual" or "BothResidual" or "Mixed" or "SignsAndScales" or "Cancellation"))
{
throw new ArgumentOutOfRangeException(nameof(scenario));
}
ArithmeticInputs inputs = new();
for (int i = 0; i < Count; i++)
{
// Exact dyadic construction, not decimal-to-double residual estimation.
double high = 1.25 + ((i % 7) / 32.0);
double low = Math.ScaleB(1.0, -56);
DoubleDouble left = DoubleDouble.FromComponents(high, low);
DoubleDouble right = DoubleDouble.FromComponents(1.0 + (((i % 5) - 2) / 128.0), -low);
if (!chain)
{
switch (scenario)
{
case "BinaryExact":
left = new DoubleDouble(high);
right = new DoubleDouble(0.75);
break;
case "DecimalResidual":
left = (DoubleDouble)1.1m;
right = new DoubleDouble(0.75);
break;
case "SignsAndScales":
int exponent = ((i % 7) - 3) * 40;
double sign = i % 2 == 0 ? 1.0 : -1.0;
left = DoubleDouble.FromComponents(Math.ScaleB(sign * high, exponent), Math.ScaleB(low, exponent));
right = DoubleDouble.FromComponents(Math.ScaleB(1.125, -exponent), Math.ScaleB(-low, -exponent));
break;
case "Cancellation":
// Alternate near cancellation in addition and subtraction.
right = DoubleDouble.FromComponents(i % 2 == 0 ? -high : high, -low / 2.0);
break;
}
}
if (scenario == "Mixed")
{
// Alternate both residuals, right zero-low, left zero-low, both zero-low.
if (i % 4 is 2 or 3)
{
left = new DoubleDouble(left.High);
}
if (i % 4 is 1 or 3)
{
right = new DoubleDouble(right.High);
}
}
inputs.Left[i] = left;
inputs.Right[i] = right;
}
inputs.Validate(scenario, chain);
return inputs;
}
private void Validate(string scenario, bool chain)
{
for (int i = 0; i < Count; i++)
{
ValidateOrdinary(Left[i]);
ValidateOrdinary(Right[i]);
bool expectedLeftLow = scenario != "BinaryExact" && (scenario != "Mixed" || i % 4 < 2);
bool expectedRightLow = scenario is not ("BinaryExact" or "DecimalResidual") && (scenario != "Mixed" || i % 2 == 0);
if ((Left[i].Low != 0.0) != expectedLeftLow || (Right[i].Low != 0.0) != expectedRightLow)
{
throw new InvalidOperationException("Benchmark residual shape does not match its scenario.");
}
}
if (!chain)
{
return;
}
// Setup-only guard against a chain drifting into special or exponent-boundary
// paths. This is fixture validation, not an independent accuracy oracle.
for (int operation = 0; operation < 12; operation++)
{
DoubleDouble value = Left[0];
for (int i = 0; i < Count; i++)
{
DoubleDouble right = Right[i];
value = operation switch
{
0 => value + right,
1 => value - right,
2 => value * right,
3 => value / right,
4 => value + right.High,
5 => value - right.High,
6 => value * right.High,
7 => value / right.High,
8 => right.High + value,
9 => right.High - value,
10 => right.High * value,
11 => right.High / value,
_ => throw new InvalidOperationException()
};
ValidateOrdinary(value);
}
}
}
private static void ValidateOrdinary(DoubleDouble value)
{
if (!DoubleDouble.IsFinite(value) || value.High == 0.0 || Math.Abs(Math.ILogB(value.High)) > 200
|| DoubleDouble.FromComponents(value.High, value.Low) != value
|| (value.Low == 0.0 && BitConverter.DoubleToInt64Bits(value.Low) != 0))
{
throw new InvalidOperationException("Benchmark input/chain left the normalized ordinary finite range.");
}
}
}