This commit is contained in:
@@ -0,0 +1,337 @@
|
||||
namespace Just.PreciseMath.Tests;
|
||||
|
||||
public class DoubleDoubleModulusTests
|
||||
{
|
||||
[Fact]
|
||||
public void ModulusRetainsTheDividendResidualThroughNamedOperatorAndGenericCalls()
|
||||
{
|
||||
// (2^53 + 1) % 2 = 1 exactly; using only High would return zero.
|
||||
DoubleDouble left = DoubleDouble.FromComponents(9007199254740992.0, 1.0);
|
||||
DoubleDouble right = new(2.0);
|
||||
AssertBits(DoubleDouble.Modulus(left, right), DoubleDouble.One);
|
||||
AssertBits(left % right, DoubleDouble.One);
|
||||
AssertBits(GenericModulus(left, right), DoubleDouble.One);
|
||||
AssertBits(DoubleDouble.Modulus(left, 2.0), DoubleDouble.One);
|
||||
AssertBits(left % 2.0, DoubleDouble.One);
|
||||
AssertBits(GenericScalarModulus(left, 2.0), DoubleDouble.One);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModulusRetainsTheDivisorResidualWithAScalarDividend()
|
||||
{
|
||||
// 2 % (1 + 2^-54) = 1 - 2^-54 exactly, with truncated quotient 1.
|
||||
DoubleDouble right = DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -54));
|
||||
DoubleDouble expected = DoubleDouble.FromComponents(1.0, -Math.ScaleB(1.0, -54));
|
||||
AssertBits(DoubleDouble.Modulus(2.0, right), expected);
|
||||
AssertBits(2.0 % right, expected);
|
||||
AssertBits(new DoubleDouble(2.0) % right, expected);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ScalarSpecialValuesAndRemainderSignsMatchBinary64()
|
||||
{
|
||||
double[] values = [0.0, -0.0, 1.0, -1.0, 2.0, -2.0, 5.5, -5.5,
|
||||
double.Epsilon, -double.Epsilon, double.MaxValue, -double.MaxValue,
|
||||
double.PositiveInfinity, double.NegativeInfinity, double.NaN];
|
||||
foreach (double left in values)
|
||||
{
|
||||
foreach (double right in values)
|
||||
{
|
||||
// The BCL is an independent oracle for zero-low inputs. NaNs
|
||||
// are canonicalized by this library, regardless of the CPU payload.
|
||||
DoubleDouble expected = new(left % right);
|
||||
AssertBits(DoubleDouble.Modulus(new DoubleDouble(left), new DoubleDouble(right)), expected);
|
||||
AssertBits(new DoubleDouble(left) % new DoubleDouble(right), expected);
|
||||
AssertBits(new DoubleDouble(left) % right, expected);
|
||||
AssertBits(left % new DoubleDouble(right), expected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ExactCasesIncludeSparseLowsCancellationAndUnrepresentableQuotients()
|
||||
{
|
||||
// Exact dyadic derivations; no expected result is calculated with DD arithmetic.
|
||||
(DoubleDouble Left, DoubleDouble Right, DoubleDouble Expected)[] cases =
|
||||
[
|
||||
// Tiny perturbations must decide the integer quotient before rounding.
|
||||
(new(2.0, -double.Epsilon), new(1.0), new(1.0, -double.Epsilon)),
|
||||
(new(2.0, double.Epsilon), new(1.0), new(double.Epsilon)),
|
||||
(new(2.0), new(1.0, double.Epsilon), new(1.0, -double.Epsilon)),
|
||||
(new(2.0), new(1.0, -double.Epsilon), new(2.0 * double.Epsilon)),
|
||||
// Huge quotient exceeds both binary64 range and DD integer precision.
|
||||
// MaxValue/epsilon = (2^53 - 1)*2^2045, congruent to 2 modulo 3.
|
||||
(new(double.MaxValue, double.Epsilon), new(3.0 * double.Epsilon), new(0.0)),
|
||||
(new(Math.ScaleB(1.0, 1000), 1.0), new(3.0), new(2.0)),
|
||||
// Dividend smaller than the divisor, and an exact multiple with a low.
|
||||
(new(1.0, -double.Epsilon), new(1.0), new(1.0, -double.Epsilon)),
|
||||
(new(2.0, Math.ScaleB(1.0, -53)), new(1.0, Math.ScaleB(1.0, -54)), new(0.0)),
|
||||
// Exact component sums satisfy left = 5*right, but High/High
|
||||
// rounds below 5. The estimate must be corrected upward, not accepted.
|
||||
(new(7.350727155000126, 2.4501029207264783e-16), new(1.4701454310000253, -8.422470454048922e-17), new(0.0)),
|
||||
// Subnormal integer units: 5 % 3 = 2.
|
||||
(new(5.0 * double.Epsilon), new(3.0 * double.Epsilon), new(2.0 * double.Epsilon))
|
||||
];
|
||||
foreach ((DoubleDouble left, DoubleDouble right, DoubleDouble expected) in cases)
|
||||
{
|
||||
foreach (int leftSign in new[] { -1, 1 })
|
||||
{
|
||||
foreach (int rightSign in new[] { -1, 1 })
|
||||
{
|
||||
DoubleDouble a = leftSign < 0 ? -left : left;
|
||||
DoubleDouble b = rightSign < 0 ? -right : right;
|
||||
DoubleDouble result = leftSign < 0 ? -expected : expected;
|
||||
AssertBits(a % b, result);
|
||||
AssertBits(DoubleDouble.Modulus(a, b), result);
|
||||
AssertBits(GenericModulus(a, b), result);
|
||||
if (b.Low == 0.0)
|
||||
{
|
||||
AssertBits(a % b.High, result);
|
||||
}
|
||||
if (a.Low == 0.0)
|
||||
{
|
||||
AssertBits(a.High % b, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonfiniteDispatchDoesNotDiscardAFiniteDividendLow()
|
||||
{
|
||||
DoubleDouble value = new(1.0, double.Epsilon);
|
||||
foreach (DoubleDouble left in new[] { value, -value })
|
||||
{
|
||||
AssertBits(left % DoubleDouble.PositiveInfinity, left);
|
||||
AssertBits(left % DoubleDouble.NegativeInfinity, left);
|
||||
AssertBits(left % double.PositiveInfinity, left);
|
||||
AssertBits(left % DoubleDouble.Zero, DoubleDouble.NaN);
|
||||
AssertBits(left % DoubleDouble.NegativeZero, DoubleDouble.NaN);
|
||||
AssertBits(left % DoubleDouble.NaN, DoubleDouble.NaN);
|
||||
AssertBits(DoubleDouble.PositiveInfinity % left, DoubleDouble.NaN);
|
||||
AssertBits(DoubleDouble.NaN % left, DoubleDouble.NaN);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModulusMeetsExactRationalBoundAcrossExponentsAndOverloads()
|
||||
{
|
||||
DoubleDouble[] values = FiniteCases();
|
||||
foreach (DoubleDouble left in values)
|
||||
{
|
||||
foreach (DoubleDouble right in values)
|
||||
{
|
||||
AssertRemainder(left % right, left, right);
|
||||
AssertRemainder(left % right.High, left, new DoubleDouble(right.High));
|
||||
AssertRemainder(left.High % right, new DoubleDouble(left.High), right);
|
||||
AssertBits(DoubleDouble.Modulus(left, right), left % right);
|
||||
AssertBits(DoubleDouble.Modulus(left, right.High), left % right.High);
|
||||
AssertBits(DoubleDouble.Modulus(left.High, right), left.High % right);
|
||||
AssertBits(left % right.High, left % new DoubleDouble(right.High));
|
||||
AssertBits(left.High % right, new DoubleDouble(left.High) % right);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModulusSamplesEveryFiniteBinaryExponent()
|
||||
{
|
||||
for (int exponent = -1074; exponent <= 1023; ++exponent)
|
||||
{
|
||||
double high = Math.ScaleB(1.5, exponent);
|
||||
DoubleDouble left = DoubleDouble.FromComponents(high, Math.ScaleB(high, -54));
|
||||
DoubleDouble right = DoubleDouble.FromComponents(3.0, -double.Epsilon);
|
||||
AssertRemainder(left % right, left, right);
|
||||
AssertRemainder(right % left, right, left);
|
||||
AssertRemainder(-left % right, -left, right);
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
public void OrdinaryTwoComponentModulusDoesNotAllocate(int overload)
|
||||
{
|
||||
// Include ordinary/dense operands, sparse quotient-boundary corrections,
|
||||
// and common power-of-two rescalings; assertions/setup are not measured.
|
||||
(DoubleDouble Left, DoubleDouble Right)[] cases =
|
||||
[
|
||||
(new(5.5, Math.ScaleB(1.0, -54)), new(2.0, Math.ScaleB(1.0, -53))),
|
||||
(new(2.0, -double.Epsilon), new(1.0, double.Epsilon)),
|
||||
(new(2.0, double.Epsilon), new(1.0, -double.Epsilon)),
|
||||
(new(2.0, Math.ScaleB(1.0, -53)), new(1.0, Math.ScaleB(1.0, -54))),
|
||||
(new(7.350727155000126, 2.4501029207264783e-16), new(1.4701454310000253, -8.422470454048922e-17)),
|
||||
(new(Math.ScaleB(5.5, -1000), Math.ScaleB(1.0, -1054)), new(Math.ScaleB(2.0, -1000), Math.ScaleB(1.0, -1053))),
|
||||
(new(Math.ScaleB(5.5, 1000), Math.ScaleB(1.0, 946)), new(Math.ScaleB(2.0, 1000), Math.ScaleB(1.0, 947))),
|
||||
// Identity/equality need no arithmetic even outside the kernel range.
|
||||
(new(1.0, -double.Epsilon), new(double.MaxValue, double.Epsilon)),
|
||||
(new(double.MaxValue, double.Epsilon), new(double.MaxValue, double.Epsilon))
|
||||
];
|
||||
foreach ((DoubleDouble left, DoubleDouble right) in cases)
|
||||
{
|
||||
foreach (int sign in new[] { -1, 1 })
|
||||
{
|
||||
DoubleDouble dividend = sign < 0 ? -left : left;
|
||||
_ = MeasureModulusAllocations(overload, dividend, right, out _);
|
||||
long allocated = MeasureModulusAllocations(overload, dividend, right, out DoubleDouble result);
|
||||
AssertRemainder(result, overload == 2 ? new DoubleDouble(dividend.High) : dividend,
|
||||
overload == 1 ? new DoubleDouble(right.High) : right);
|
||||
allocated.ShouldBe(0L, $"overload {overload}: ({dividend.High:R}, {dividend.Low:R}) % ({right.High:R}, {right.Low:R})");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ModulusHandlesKernelRangeAndQuotientGuardNeighbors()
|
||||
{
|
||||
foreach (int exponent in new[] { -1000, -969, -54, 0, 450, 968, 1018, 1019 })
|
||||
{
|
||||
DoubleDouble left = DoubleDouble.FromComponents(Math.ScaleB(5.5, exponent), Math.ScaleB(1.0, exponent - 54));
|
||||
DoubleDouble right = DoubleDouble.FromComponents(Math.ScaleB(2.0, exponent), -Math.ScaleB(1.0, exponent - 53));
|
||||
AssertRemainder(left % right, left, right);
|
||||
AssertRemainder(left % right.High, left, new DoubleDouble(right.High));
|
||||
AssertRemainder(left.High % right, new DoubleDouble(left.High), right);
|
||||
}
|
||||
double limit = Math.ScaleB(1.0, 52);
|
||||
foreach (double high in new[] { Math.BitDecrement(limit), limit, Math.BitIncrement(limit), limit + 1.0 })
|
||||
{
|
||||
foreach (double low in new[] { -double.Epsilon, 0.0, double.Epsilon, -0.25, 0.25 })
|
||||
{
|
||||
DoubleDouble left = DoubleDouble.FromComponents(high, low);
|
||||
foreach (DoubleDouble right in new[] { new DoubleDouble(1.0, -double.Epsilon),
|
||||
DoubleDouble.One, new DoubleDouble(1.0, double.Epsilon) })
|
||||
{
|
||||
AssertRemainder(left % right, left, right);
|
||||
AssertRemainder(-left % right, -left, right);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RandomSignificandsMeetTheIndependentRemainderBound()
|
||||
{
|
||||
Random random = new(0x51a7);
|
||||
int[] gaps = [0, 1, 2, 10, 51, 52, 53, 54, 500];
|
||||
for (int i = 0; i < 5000; ++i)
|
||||
{
|
||||
int exponent = random.Next(-1000, 501);
|
||||
int gap = gaps[i % gaps.Length];
|
||||
DoubleDouble left = DoubleDouble.FromComponents(Math.ScaleB(1.0 + random.NextDouble(), exponent + gap),
|
||||
Math.ScaleB(random.NextDouble() - 0.5, exponent + gap - 53));
|
||||
DoubleDouble right = DoubleDouble.FromComponents(Math.ScaleB(1.0 + random.NextDouble(), exponent),
|
||||
Math.ScaleB(random.NextDouble() - 0.5, exponent - 53));
|
||||
AssertRemainder(left % right, left, right);
|
||||
AssertRemainder(-left % -right, -left, -right);
|
||||
AssertRemainder(left % right.High, left, new DoubleDouble(right.High));
|
||||
AssertRemainder(left.High % right, new DoubleDouble(left.High), right);
|
||||
AssertBits(left % right.High, left % new DoubleDouble(right.High));
|
||||
AssertBits(left.High % right, new DoubleDouble(left.High) % right);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnsafeQuotientAndUpperRangeRetainTheExactAllocatingFallback()
|
||||
{
|
||||
(DoubleDouble Left, DoubleDouble Right)[] cases =
|
||||
[
|
||||
(new(Math.ScaleB(1.0, 1000), 1.0), new(3.0)),
|
||||
(new(Math.ScaleB(1.0, 1022), Math.ScaleB(1.0, 968)), new(Math.ScaleB(3.0, 1020), Math.ScaleB(1.0, 966)))
|
||||
];
|
||||
foreach ((DoubleDouble left, DoubleDouble right) in cases)
|
||||
{
|
||||
_ = MeasureModulusAllocations(0, left, right, out _);
|
||||
long allocated = MeasureModulusAllocations(0, left, right, out DoubleDouble result);
|
||||
allocated.ShouldBeGreaterThan(0L);
|
||||
AssertRemainder(result, left, right);
|
||||
}
|
||||
}
|
||||
|
||||
private static long MeasureModulusAllocations(int overload, DoubleDouble left, DoubleDouble right, out DoubleDouble result)
|
||||
{
|
||||
result = default;
|
||||
long before = GC.GetAllocatedBytesForCurrentThread();
|
||||
for (int i = 0; i < 128; ++i)
|
||||
{
|
||||
result = overload switch
|
||||
{
|
||||
0 => left % right,
|
||||
1 => left % right.High,
|
||||
2 => left.High % right,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(overload))
|
||||
};
|
||||
}
|
||||
return GC.GetAllocatedBytesForCurrentThread() - before;
|
||||
}
|
||||
|
||||
private static DoubleDouble[] FiniteCases()
|
||||
{
|
||||
List<DoubleDouble> values = [];
|
||||
foreach (int exponent in new[] { -1074, -1022, -969, -450, -54, 0, 54, 450, 969, 1023 })
|
||||
{
|
||||
double high = Math.ScaleB(1.5, exponent);
|
||||
foreach (double low in new[] { 0.0, Math.ScaleB(high, -54), -Math.ScaleB(high, -54), double.Epsilon, -double.Epsilon })
|
||||
{
|
||||
DoubleDouble value = DoubleDouble.FromComponents(high, low);
|
||||
if (!DoubleDouble.IsZero(value))
|
||||
{
|
||||
values.Add(value);
|
||||
values.Add(-value);
|
||||
}
|
||||
}
|
||||
}
|
||||
values.Add(DoubleDouble.FromComponents(double.MaxValue, Math.BitDecrement(Math.ScaleB(1.0, 970))));
|
||||
values.Add(new DoubleDouble(1.0, -Math.ScaleB(1.0, -54)));
|
||||
values.Add(new DoubleDouble(2.0, -double.Epsilon));
|
||||
return values.ToArray();
|
||||
}
|
||||
|
||||
private static void AssertRemainder(DoubleDouble actual, DoubleDouble left, DoubleDouble right)
|
||||
{
|
||||
// Independent exact dyadic oracle: extract significands with BCL scaling,
|
||||
// then truncate the exact rational quotient as an integer. Never divide,
|
||||
// multiply or subtract using the implementation under test.
|
||||
BigInteger dividend = Units(left.High) + Units(left.Low);
|
||||
BigInteger divisor = Units(right.High) + Units(right.Low);
|
||||
BigInteger expected = dividend - ((dividend / divisor) * divisor);
|
||||
DoubleDouble.IsFinite(actual).ShouldBeTrue();
|
||||
DoubleDouble.IsCanonical(actual).ShouldBeTrue();
|
||||
DoubleDouble.IsNegative(actual).ShouldBe(DoubleDouble.IsNegative(left));
|
||||
if (expected.IsZero)
|
||||
{
|
||||
AssertBits(actual, new DoubleDouble(Math.CopySign(0.0, left.High)));
|
||||
return;
|
||||
}
|
||||
|
||||
actual.High.ShouldNotBe(0.0);
|
||||
BigInteger observed = Units(actual.High) + Units(actual.Low);
|
||||
BigInteger error = BigInteger.Abs(observed - expected);
|
||||
// The usual arithmetic bound, in units of epsilon: relative 2^-100
|
||||
// plus one minimum subnormal. Keep both components in the comparison.
|
||||
(error << 100).ShouldBeLessThanOrEqualTo(BigInteger.Abs(expected) + (BigInteger.One << 100));
|
||||
}
|
||||
|
||||
private static BigInteger Units(double value)
|
||||
{
|
||||
if (value == 0.0)
|
||||
{
|
||||
return BigInteger.Zero;
|
||||
}
|
||||
int exponent = Math.ILogB(value);
|
||||
BigInteger significand = new(Math.ScaleB(Math.Abs(value), -exponent) * 4503599627370496.0);
|
||||
BigInteger units = significand << (exponent + 1022);
|
||||
return double.IsNegative(value) ? -units : units;
|
||||
}
|
||||
|
||||
private static T GenericModulus<T>(T left, T right) where T : INumber<T> => left % right;
|
||||
|
||||
private static T GenericScalarModulus<T>(T left, double right) where T : IModulusOperators<T, double, T> => left % right;
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
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<ArgumentException>(() => 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<ArithmeticException>(() => Sign(x));
|
||||
Should.Throw<ArithmeticException>(() => 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<ArgumentException>(() => ClampNative(DoubleDouble.One, upper, lower));
|
||||
}
|
||||
#endif
|
||||
|
||||
private static T Clamp<T>(T value, T min, T max) where T : INumber<T> => T.Clamp(value, min, max);
|
||||
|
||||
private static T CopySign<T>(T value, T sign) where T : INumber<T> => T.CopySign(value, sign);
|
||||
|
||||
private static T Max<T>(T x, T y) where T : INumber<T> => T.Max(x, y);
|
||||
|
||||
private static T MaxNumber<T>(T x, T y) where T : INumber<T> => T.MaxNumber(x, y);
|
||||
|
||||
private static T Min<T>(T x, T y) where T : INumber<T> => T.Min(x, y);
|
||||
|
||||
private static T MinNumber<T>(T x, T y) where T : INumber<T> => T.MinNumber(x, y);
|
||||
|
||||
private static int Sign<T>(T value) where T : INumber<T> => T.Sign(value);
|
||||
|
||||
#if NET10_0_OR_GREATER
|
||||
private static T ClampNative<T>(T value, T min, T max) where T : INumber<T> => T.ClampNative(value, min, max);
|
||||
|
||||
private static T MaxNative<T>(T x, T y) where T : INumber<T> => T.MaxNative(x, y);
|
||||
|
||||
private static T MinNative<T>(T x, T y) where T : INumber<T> => 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));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user