namespace Just.PreciseMath.Tests; public class DoubleDoubleNumberTests { [Fact] public void NumberDefaultsUseBothComponents() { DoubleDouble lower = new(1.0, -double.Epsilon); DoubleDouble upper = new(1.0, double.Epsilon); AssertBits(Max(lower, upper), upper); AssertBits(Max(upper, lower), upper); AssertBits(MaxNumber(lower, upper), upper); AssertBits(Min(lower, upper), lower); AssertBits(Min(upper, lower), lower); AssertBits(MinNumber(lower, upper), lower); AssertBits(Clamp(DoubleDouble.Zero, lower, upper), lower); AssertBits(Clamp(DoubleDouble.One, lower, upper), DoubleDouble.One); AssertBits(Clamp(new DoubleDouble(2.0), lower, upper), upper); Should.Throw(() => Clamp(DoubleDouble.One, upper, lower)); AssertBits(CopySign(upper, DoubleDouble.NegativeZero), new DoubleDouble(-1.0, -double.Epsilon)); AssertBits(CopySign(-upper, DoubleDouble.Zero), upper); Sign(lower).ShouldBe(1); Sign(-upper).ShouldBe(-1); } [Fact] public void NumberDefaultsHandleSpecialValuesAndSignedZeros() { double[] values = [0.0, -0.0, 1.0, -1.0, double.Epsilon, -double.Epsilon, double.MaxValue, double.PositiveInfinity, double.NegativeInfinity, double.NaN]; foreach (double left in values) { DoubleDouble x = new(left); foreach (double right in values) { DoubleDouble y = new(right); AssertBits(Max(x, y), new DoubleDouble(double.Max(left, right))); AssertBits(Min(x, y), new DoubleDouble(double.Min(left, right))); AssertBits(MaxNumber(x, y), new DoubleDouble(double.MaxNumber(left, right))); AssertBits(MinNumber(x, y), new DoubleDouble(double.MinNumber(left, right))); AssertBits(CopySign(x, y), new DoubleDouble(double.CopySign(left, right))); } AssertBits(Clamp(x, DoubleDouble.NegativeOne, DoubleDouble.One), new DoubleDouble(double.Clamp(left, -1.0, 1.0))); if (double.IsNaN(left)) { Should.Throw(() => Sign(x)); Should.Throw(() => DoubleDouble.Sign(x)); } else { Sign(x).ShouldBe(double.Sign(left)); DoubleDouble.Sign(x).ShouldBe(double.Sign(left)); } } } #if NET10_0_OR_GREATER [Fact] public void NativeDefaultsRetainFullComponentOrdering() { DoubleDouble lower = new(1.0, -double.Epsilon); DoubleDouble upper = new(1.0, double.Epsilon); AssertBits(MaxNative(lower, upper), upper); AssertBits(MinNative(lower, upper), lower); AssertBits(ClampNative(DoubleDouble.Zero, lower, upper), lower); AssertBits(ClampNative(DoubleDouble.One, lower, upper), DoubleDouble.One); AssertBits(ClampNative(new DoubleDouble(2.0), lower, upper), upper); Should.Throw(() => ClampNative(DoubleDouble.One, upper, lower)); } #endif private static T Clamp(T value, T min, T max) where T : INumber => T.Clamp(value, min, max); private static T CopySign(T value, T sign) where T : INumber => T.CopySign(value, sign); private static T Max(T x, T y) where T : INumber => T.Max(x, y); private static T MaxNumber(T x, T y) where T : INumber => T.MaxNumber(x, y); private static T Min(T x, T y) where T : INumber => T.Min(x, y); private static T MinNumber(T x, T y) where T : INumber => T.MinNumber(x, y); private static int Sign(T value) where T : INumber => T.Sign(value); #if NET10_0_OR_GREATER private static T ClampNative(T value, T min, T max) where T : INumber => T.ClampNative(value, min, max); private static T MaxNative(T x, T y) where T : INumber => T.MaxNative(x, y); private static T MinNative(T x, T y) where T : INumber => T.MinNative(x, y); #endif 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)); } }