130 lines
5.2 KiB
C#
130 lines
5.2 KiB
C#
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.");
|
|
}
|
|
}
|
|
}
|