165 lines
7.8 KiB
C#
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();
|
|
}
|
|
}
|