Files
Just.PreciseMath/1-tests/Just.PreciseMath.Tests/DoubleDoubleExtensionsTests.cs
T
just ac93744579
.NET Test / .NET tests (push) Successful in 2m11s
operators refactoring
2026-09-24 00:39:59 +04:00

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));
}
}