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")] [InlineData("Log2")] [InlineData("Log10")] [InlineData("LogP1")] [InlineData("Log2P1")] [InlineData("Log10P1")] 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 original = value; (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()), "Log2" => (DoubleDouble.Log2(value), value.Log2()), "Log10" => (DoubleDouble.Log10(value), value.Log10()), "LogP1" => (DoubleDouble.LogP1(value), value.LogP1()), "Log2P1" => (DoubleDouble.Log2P1(value), value.Log2P1()), "Log10P1" => (DoubleDouble.Log10P1(value), value.Log10P1()), _ => throw new ArgumentOutOfRangeException(nameof(operation)) }; AssertBits(expected, actual); AssertBits(original, value); } } [Fact] public void BaseLogarithmExtensionPreservesBothOperands() { DoubleDouble[] bases = [DoubleDouble.Zero, DoubleDouble.NegativeZero, DoubleDouble.One, DoubleDouble.NegativeOne, DoubleDouble.NaN, DoubleDouble.PositiveInfinity, DoubleDouble.NegativeInfinity, new(0.5), new(2.0), new(10.0), new(double.Epsilon), new(double.MaxValue), DoubleDouble.FromComponents(1.0, double.Epsilon), DoubleDouble.FromComponents(1.0, -double.Epsilon), DoubleDouble.FromComponents(2.0, Math.ScaleB(1.0, -54))]; foreach (DoubleDouble value in Values()) { foreach (DoubleDouble newBase in bases) { DoubleDouble originalValue = value; DoubleDouble originalBase = newBase; AssertBits(DoubleDouble.Log(value, newBase), value.Log(newBase)); AssertBits(originalValue, value); AssertBits(originalBase, newBase); } } } [Fact] public void LogarithmExtensionsCanBeChainedWithoutChangingTheReceiver() { DoubleDouble value = new(8.0); AssertBits(new DoubleDouble(8.0), value.Log2().Exp2()); AssertBits(new DoubleDouble(8.0), value); } [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 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)); } }