This commit is contained in:
@@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user