166 lines
6.3 KiB
C#
166 lines
6.3 KiB
C#
namespace Just.PreciseMath.Tests;
|
|
|
|
public class DoubleDoubleExtensionsTests
|
|
{
|
|
[Theory]
|
|
[InlineData("Abs")]
|
|
[InlineData("Square")]
|
|
[InlineData("Reciprocal")]
|
|
[InlineData("Sqrt")]
|
|
[InlineData("InvSqrt")]
|
|
[InlineData("Cbrt")]
|
|
[InlineData("Exp")]
|
|
[InlineData("Exp2")]
|
|
[InlineData("Exp10")]
|
|
[InlineData("ExpM1")]
|
|
[InlineData("Exp2M1")]
|
|
[InlineData("Exp10M1")]
|
|
[InlineData("Log")]
|
|
public void UnaryExtensionsPreserveStaticFunctionComponentBits(string operation)
|
|
{
|
|
// These are forwarding contracts, not accuracy oracles. The static
|
|
// kernels have separate independent numerical-reference tests.
|
|
foreach (DoubleDouble value in Values())
|
|
{
|
|
(DoubleDouble expected, DoubleDouble actual) = operation switch
|
|
{
|
|
"Abs" => (DoubleDouble.Abs(value), value.Abs()),
|
|
"Square" => (DoubleDouble.Square(value), value.Square()),
|
|
"Reciprocal" => (DoubleDouble.Reciprocal(value), value.Reciprocal()),
|
|
"Sqrt" => (DoubleDouble.Sqrt(value), value.Sqrt()),
|
|
"InvSqrt" => (DoubleDouble.InvSqrt(value), value.InvSqrt()),
|
|
"Cbrt" => (DoubleDouble.Cbrt(value), value.Cbrt()),
|
|
"Exp" => (DoubleDouble.Exp(value), value.Exp()),
|
|
"Exp2" => (DoubleDouble.Exp2(value), value.Exp2()),
|
|
"Exp10" => (DoubleDouble.Exp10(value), value.Exp10()),
|
|
"ExpM1" => (DoubleDouble.ExpM1(value), value.ExpM1()),
|
|
"Exp2M1" => (DoubleDouble.Exp2M1(value), value.Exp2M1()),
|
|
"Exp10M1" => (DoubleDouble.Exp10M1(value), value.Exp10M1()),
|
|
"Log" => (DoubleDouble.Log(value), value.Log()),
|
|
_ => throw new ArgumentOutOfRangeException(nameof(operation))
|
|
};
|
|
AssertBits(expected, actual);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ExtensionsCanBeChainedWithoutChangingTheReceiver()
|
|
{
|
|
DoubleDouble value = new(-4.0);
|
|
DoubleDouble actual = value.Abs().Square().Sqrt().Reciprocal();
|
|
// All intermediate results are exactly representable binary powers.
|
|
AssertBits(new DoubleDouble(0.25), actual);
|
|
AssertBits(new DoubleDouble(-4.0), value);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(int.MinValue)]
|
|
[InlineData(-7)]
|
|
[InlineData(-2)]
|
|
[InlineData(-1)]
|
|
[InlineData(0)]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
[InlineData(7)]
|
|
[InlineData(int.MaxValue)]
|
|
public void IntegerPowerExtensionPreservesStaticFunctionComponentBits(int exponent)
|
|
{
|
|
foreach (DoubleDouble value in Values())
|
|
{
|
|
AssertBits(DoubleDouble.Pow(value, exponent), value.Pow(exponent));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ScalarPowerExtensionPreservesStaticFunctionComponentBits()
|
|
{
|
|
double[] exponents = [0.0, -0.0, 1.0, -1.0, 2.0, -2.0, 0.5, -0.5,
|
|
1.25, -1.25, double.Epsilon, double.MaxValue, double.MinValue,
|
|
double.NaN, double.PositiveInfinity, double.NegativeInfinity];
|
|
foreach (DoubleDouble value in Values())
|
|
{
|
|
foreach (double exponent in exponents)
|
|
{
|
|
AssertBits(DoubleDouble.Pow(value, exponent), value.Pow(exponent));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void ExpansionPowerExtensionPreservesBothExponentComponents()
|
|
{
|
|
DoubleDouble[] exponents = [DoubleDouble.Zero, DoubleDouble.NegativeZero,
|
|
DoubleDouble.One, DoubleDouble.NegativeOne, DoubleDouble.NaN,
|
|
DoubleDouble.PositiveInfinity, DoubleDouble.NegativeInfinity,
|
|
DoubleDouble.FromComponents(0.5, Math.ScaleB(1.0, -54)),
|
|
DoubleDouble.FromComponents(0.5, -Math.ScaleB(1.0, -55)),
|
|
DoubleDouble.FromComponents(1.0, double.Epsilon),
|
|
DoubleDouble.FromComponents(Math.ScaleB(1.0, 54), 1.0)];
|
|
foreach (DoubleDouble value in Values())
|
|
{
|
|
foreach (DoubleDouble exponent in exponents)
|
|
{
|
|
AssertBits(DoubleDouble.Pow(value, exponent), value.Pow(exponent));
|
|
}
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(int.MinValue)]
|
|
[InlineData(-7)]
|
|
[InlineData(-3)]
|
|
[InlineData(-2)]
|
|
[InlineData(-1)]
|
|
[InlineData(0)]
|
|
[InlineData(1)]
|
|
[InlineData(2)]
|
|
[InlineData(3)]
|
|
[InlineData(7)]
|
|
[InlineData(int.MaxValue)]
|
|
public void RootExtensionPreservesStaticFunctionComponentBits(int degree)
|
|
{
|
|
foreach (DoubleDouble value in Values())
|
|
{
|
|
AssertBits(DoubleDouble.RootN(value, degree), value.RootN(degree));
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<DoubleDouble> Values()
|
|
{
|
|
yield return DoubleDouble.Zero;
|
|
yield return DoubleDouble.NegativeZero;
|
|
yield return DoubleDouble.NaN;
|
|
yield return DoubleDouble.PositiveInfinity;
|
|
yield return DoubleDouble.NegativeInfinity;
|
|
yield return new DoubleDouble(double.MaxValue);
|
|
yield return new DoubleDouble(double.MinValue);
|
|
yield return DoubleDouble.One;
|
|
yield return DoubleDouble.NegativeOne;
|
|
yield return DoubleDouble.Pi;
|
|
yield return -DoubleDouble.Pi;
|
|
// Square's dedicated kernel can differ from multiplication; near-one
|
|
// and sparse pairs also exercise low-component retention in roots/log.
|
|
yield return DoubleDouble.FromComponents(1.0, -Math.ScaleB(1.0, -54));
|
|
yield return DoubleDouble.FromComponents(1.0, double.Epsilon);
|
|
yield return DoubleDouble.FromComponents(1.0, -double.Epsilon);
|
|
foreach (int exponent in new[] { -1074, -1022, -451, -450, -1, 0, 1, 450, 451, 1020, 1021, 1023 })
|
|
{
|
|
foreach (double sign in new[] { -1.0, 1.0 })
|
|
{
|
|
double high = Math.ScaleB(sign * 1.25, exponent);
|
|
double denseLow = Math.ScaleB(1.0, exponent - 54);
|
|
foreach (double low in new[] { 0.0, denseLow, -denseLow, double.Epsilon, -double.Epsilon })
|
|
{
|
|
yield return DoubleDouble.FromComponents(high, low);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static void AssertBits(DoubleDouble expected, DoubleDouble actual)
|
|
{
|
|
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
|
|
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(BitConverter.DoubleToInt64Bits(expected.Low));
|
|
}
|
|
}
|