Files
Just.PreciseMath/1-tests/Just.PreciseMath.Tests/PreciseMathRealPowSpecialTests.cs
T
just 5797bf4884
.NET Test / .NET tests (push) Successful in 3m55s
added sanity checks
2026-09-18 14:10:24 +04:00

165 lines
7.8 KiB
C#

namespace Just.PreciseMath.Tests;
public class PreciseMathRealPowSpecialTests
{
[Fact]
public void ZeroAndOneExponentsPreserveTheIdentityContractsInBothOverloads()
{
DoubleDouble[] values = [DoubleDouble.Zero, new(-0.0), DoubleDouble.One,
new(double.NaN), new(double.PositiveInfinity), new(double.NegativeInfinity),
DoubleDouble.FromComponents(double.MaxValue, double.Epsilon)];
foreach (DoubleDouble value in values)
{
AssertBits(DDMath.Pow(value, 0.0), DoubleDouble.One);
AssertBits(DDMath.Pow(value, new DoubleDouble(-0.0)), DoubleDouble.One);
AssertBits(DDMath.Pow(value, 1.0), value);
AssertBits(DDMath.Pow(value, DoubleDouble.One), value);
}
}
[Fact]
public void SpecialValueMatrixMatchesBinary64RulesInBothOverloads()
{
double[] values = [double.NegativeInfinity, -2.0, -1.0, -0.0, 0.0,
0.5, 1.0, 2.0, double.PositiveInfinity, double.NaN];
double[] exponents = [double.NegativeInfinity, -3.0, -0.5, -0.0, 0.0,
0.5, 3.0, double.PositiveInfinity, double.NaN];
foreach (double value in values)
{
foreach (double exponent in exponents)
{
double expected = Math.Pow(value, exponent);
// Math.Pow is only a special-value/exact-binary oracle here.
// Irrational finite results use independent high-precision tests.
if (double.IsFinite(expected) && expected != 0.0 && Math.Abs(expected) != 1.0
&& !double.IsInteger(exponent))
{
continue;
}
DoubleDouble input = new(value);
AssertBits(DDMath.Pow(input, exponent), new DoubleDouble(expected));
AssertBits(DDMath.Pow(input, new DoubleDouble(exponent)), new DoubleDouble(expected));
}
}
}
[Fact]
public void ExponentLowDeterminesIntegralityAndParity()
{
foreach (double high in new[] { Math.ScaleB(1.0, 53), Math.ScaleB(1.0, 100), double.MaxValue })
{
foreach (double direction in new[] { -1.0, 1.0 })
{
DoubleDouble odd = DoubleDouble.FromComponents(direction * high, 1.0);
DoubleDouble even = DoubleDouble.FromComponents(direction * high, 2.0);
DoubleDouble fractional = DoubleDouble.FromComponents(direction * high, 0.5);
AssertBits(DDMath.Pow(DoubleDouble.NegativeOne, odd), DoubleDouble.NegativeOne);
AssertBits(DDMath.Pow(DoubleDouble.NegativeOne, even), DoubleDouble.One);
AssertBits(DDMath.Pow(DoubleDouble.NegativeOne, fractional), DoubleDouble.NaN);
AssertBits(DDMath.Pow(new DoubleDouble(-2.0), odd),
new DoubleDouble(direction > 0.0 ? double.NegativeInfinity : -0.0));
AssertBits(DDMath.Pow(new DoubleDouble(-0.5), odd),
new DoubleDouble(direction > 0.0 ? -0.0 : double.NegativeInfinity));
AssertBits(DDMath.Pow(new DoubleDouble(-2.0), even),
new DoubleDouble(direction > 0.0 ? double.PositiveInfinity : 0.0));
AssertBits(DDMath.Pow(new DoubleDouble(-0.0), odd),
new DoubleDouble(direction > 0.0 ? -0.0 : double.NegativeInfinity));
AssertBits(DDMath.Pow(new DoubleDouble(double.NegativeInfinity), odd),
new DoubleDouble(direction > 0.0 ? double.NegativeInfinity : -0.0));
AssertBits(DDMath.Pow(new DoubleDouble(-0.0), fractional),
new DoubleDouble(direction > 0.0 ? 0.0 : double.PositiveInfinity));
AssertBits(DDMath.Pow(new DoubleDouble(double.NegativeInfinity), fractional),
new DoubleDouble(direction > 0.0 ? double.PositiveInfinity : 0.0));
}
}
DoubleDouble belowInteger = DoubleDouble.FromComponents(3.0, -double.Epsilon);
AssertBits(DDMath.Pow(new DoubleDouble(-2.0), belowInteger), DoubleDouble.NaN);
}
[Fact]
public void InfiniteExponentsCompareTheCompleteBaseWithOne()
{
foreach (double low in new[] { -double.Epsilon, double.Epsilon })
{
foreach (double sign in new[] { -1.0, 1.0 })
{
DoubleDouble value = DoubleDouble.FromComponents(sign, sign * low);
AssertBits(DDMath.Pow(value, double.PositiveInfinity),
new DoubleDouble(low > 0.0 ? double.PositiveInfinity : 0.0));
AssertBits(DDMath.Pow(value, new DoubleDouble(double.NegativeInfinity)),
new DoubleDouble(low < 0.0 ? double.PositiveInfinity : 0.0));
}
}
}
[Fact]
public void IntegralExponentsWithinIntRangeReuseTheIntegerContract()
{
DoubleDouble[] values = [new(2.0), new(-2.0),
DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -54)), new(double.Epsilon)];
int[] exponents = [int.MinValue, int.MaxValue, -1075, -1024, -1, 2, 3, 1024];
foreach (DoubleDouble value in values)
{
foreach (int exponent in exponents)
{
DoubleDouble expected = DDMath.Pow(value, exponent);
// Dispatch consistency, not an accuracy oracle: the integer suite
// separately checks these algorithms against independent references.
AssertBits(DDMath.Pow(value, (double)exponent), expected);
AssertBits(DDMath.Pow(value, new DoubleDouble(exponent)), expected);
}
}
}
[Fact]
public void FractionalPowersRetainTheIrrationalLowComponent()
{
// Independently rounded sqrt(2) binary64 components; also used by the
// constant-reference generator. Compare components, not high+low in double.
foreach (DoubleDouble actual in new[] { DDMath.Pow(new DoubleDouble(2.0), 0.5),
DDMath.Pow(new DoubleDouble(2.0), new DoubleDouble(0.5)) })
{
actual.High.ShouldBe(1.4142135623730951);
Math.Abs(actual.Low - -9.667293313452913e-17).ShouldBeLessThan(Math.ScaleB(1.0, -90));
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
}
}
[Theory]
[InlineData(-537.25, 1L)]
[InlineData(-537.75, 0L)]
public void FractionalPowersRespectTheSubnormalRoundingFloor(double exponent, long expectedBits)
{
// 4^-537.25 = 2^-1074.5 lies above the half-epsilon threshold;
// 4^-537.75 = 2^-1075.5 lies below it. These are well separated from ties.
foreach (DoubleDouble actual in new[] { DDMath.Pow(new DoubleDouble(4.0), exponent),
DDMath.Pow(new DoubleDouble(4.0), new DoubleDouble(exponent)) })
{
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(expectedBits);
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(0L);
}
}
[Fact]
public void FiniteOperandsWithOverflowingLogProductsReturnCanonicalRangeResults()
{
foreach (double value in new[] { double.MaxValue, double.Epsilon })
{
foreach (double exponent in new[] { double.MaxValue, -double.MaxValue })
{
bool grows = (value > 1.0) == (exponent > 0.0);
DoubleDouble expected = new(grows ? double.PositiveInfinity : 0.0);
AssertBits(DDMath.Pow(new DoubleDouble(value), exponent), expected);
AssertBits(DDMath.Pow(new DoubleDouble(value), new DoubleDouble(exponent)), expected);
}
}
}
private static void AssertBits(DoubleDouble actual, DoubleDouble expected)
{
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected.High));
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(BitConverter.DoubleToInt64Bits(expected.Low));
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
}
}