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