Files
Just.PreciseMath/1-tests/Just.PreciseMath.Tests/DoubleDoubleSignedNumberTests.cs
T
just 5797bf4884
.NET Test / .NET tests (push) Successful in 3m55s
added sanity checks
2026-09-18 14:10:24 +04:00

165 lines
6.9 KiB
C#

namespace Just.PreciseMath.Tests;
public class DoubleDoubleSignedNumberTests
{
[Fact]
public void GenericSignedNumberExposesNegativeOneAndBinaryRadix()
{
DoubleDouble value = NegativeOne<DoubleDouble>();
value.High.ShouldBe(-1.0);
BitConverter.DoubleToInt64Bits(value.Low).ShouldBe(0L);
DoubleDouble.Radix.ShouldBe(2);
}
[Theory]
[InlineData(0.0)]
[InlineData(1.0)]
[InlineData(-1.5)]
[InlineData(double.Epsilon)]
[InlineData(-double.Epsilon)]
[InlineData(double.MaxValue)]
[InlineData(double.PositiveInfinity)]
[InlineData(double.NegativeInfinity)]
[InlineData(double.NaN)]
public void ScalarClassificationMatchesBinary64(double scalar)
{
DoubleDouble value = new(scalar);
DoubleDouble.IsCanonical(value).ShouldBeTrue();
DoubleDouble.IsComplexNumber(value).ShouldBeFalse();
DoubleDouble.IsImaginaryNumber(value).ShouldBeFalse();
DoubleDouble.IsRealNumber(value).ShouldBe(!double.IsNaN(scalar));
DoubleDouble.IsPositive(value).ShouldBe(double.IsPositive(value.High));
DoubleDouble.IsNormal(value).ShouldBe(double.IsNormal(scalar));
DoubleDouble.IsSubnormal(value).ShouldBe(double.IsSubnormal(scalar));
DoubleDouble.IsZero(value).ShouldBe(scalar == 0.0);
DoubleDouble.IsInteger(value).ShouldBe(double.IsInteger(scalar));
DoubleDouble.IsEvenInteger(value).ShouldBe(double.IsEvenInteger(scalar));
DoubleDouble.IsOddInteger(value).ShouldBe(double.IsOddInteger(scalar));
}
[Theory]
[InlineData(9007199254740992.0, 1.0, true, false)]
[InlineData(9007199254740992.0, -1.0, true, false)]
[InlineData(18014398509481984.0, 2.0, true, true)]
[InlineData(1.0, 5.551115123125783e-17, false, false)]
[InlineData(9007199254740992.0, 0.5, false, false)]
public void IntegerClassificationIncludesResidual(double high, double low, bool isIntegral, bool even)
{
// Exact binary sums: the residual determines fractional bits and parity above 2^53.
foreach (DoubleDouble value in new[] { DoubleDouble.FromComponents(high, low), -DoubleDouble.FromComponents(high, low) })
{
DoubleDouble.IsInteger(value).ShouldBe(isIntegral);
DoubleDouble.IsEvenInteger(value).ShouldBe(even);
DoubleDouble.IsOddInteger(value).ShouldBe(isIntegral && !even);
DoubleDouble.IsCanonical(value).ShouldBeTrue();
}
}
[Fact]
public void CanonicalClassificationRejectsUnnormalizedAndNoncanonicalRawPairs()
{
DoubleDouble[] values = [new(1.0, 1.0), new(0.0, 1.0), new(1.0, -0.0),
new(double.PositiveInfinity, 1.0), new(double.NaN, 1.0),
new(BitConverter.Int64BitsToDouble(0x7ff8000000000001L), 0.0)];
foreach (DoubleDouble value in values)
{
DoubleDouble.IsCanonical(value).ShouldBeFalse();
}
}
[Fact]
public void NegativeZeroClassificationPreservesSign()
{
DoubleDouble value = new(-0.0);
DoubleDouble.IsCanonical(value).ShouldBeTrue();
DoubleDouble.IsZero(value).ShouldBeTrue();
DoubleDouble.IsPositive(value).ShouldBeFalse();
DoubleDouble.IsNegative(value).ShouldBeTrue();
DoubleDouble.IsEvenInteger(value).ShouldBeTrue();
DoubleDouble.IsOddInteger(value).ShouldBeFalse();
}
[Fact]
public void MagnitudeSelectionUsesBothComponentsAndBreaksTiesBySign()
{
DoubleDouble smaller = new(1.0);
DoubleDouble larger = DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -54));
foreach ((DoubleDouble left, DoubleDouble right) in new[] { (smaller, -larger), (-larger, smaller) })
{
DoubleDouble.MaxMagnitude(left, right).ShouldBe(-larger);
DoubleDouble.MaxMagnitudeNumber(left, right).ShouldBe(-larger);
DoubleDouble.MinMagnitude(left, right).ShouldBe(smaller);
DoubleDouble.MinMagnitudeNumber(left, right).ShouldBe(smaller);
}
foreach ((DoubleDouble left, DoubleDouble right) in new[] { (larger, -larger), (-larger, larger) })
{
DoubleDouble.MaxMagnitude(left, right).ShouldBe(larger);
DoubleDouble.MinMagnitude(left, right).ShouldBe(-larger);
}
}
[Fact]
public void MagnitudeSpecialValuesMatchBinary64()
{
double[] values = [0.0, -0.0, 1.0, -1.0, double.Epsilon, double.MaxValue,
double.PositiveInfinity, double.NegativeInfinity, double.NaN];
foreach (double left in values)
{
foreach (double right in values)
{
AssertBits(DoubleDouble.MaxMagnitude(new(left), new(right)), double.MaxMagnitude(left, right));
AssertBits(DoubleDouble.MinMagnitude(new(left), new(right)), double.MinMagnitude(left, right));
AssertBits(DoubleDouble.MaxMagnitudeNumber(new(left), new(right)), double.MaxMagnitudeNumber(left, right));
AssertBits(DoubleDouble.MinMagnitudeNumber(new(left), new(right)), double.MinMagnitudeNumber(left, right));
}
AssertBits(DoubleDouble.Abs(new(left)), double.IsNaN(left) ? double.NaN : double.Abs(left));
}
DoubleDouble negative = DoubleDouble.FromComponents(-1.0, Math.ScaleB(1.0, -54));
DoubleDouble.Abs(negative).High.ShouldBe(1.0);
DoubleDouble.Abs(negative).Low.ShouldBe(-Math.ScaleB(1.0, -54));
}
[Fact]
public void IncrementAndDecrementRetainResidualAndSupportGenericDispatch()
{
// 2^53 +/- 1 are exact two-component integers.
DoubleDouble start = new(9007199254740992.0);
DoubleDouble incremented = Increment(start);
incremented.High.ShouldBe(9007199254740992.0);
incremented.Low.ShouldBe(1.0);
DoubleDouble decremented = Decrement(incremented);
decremented.ShouldBe(start);
DoubleDouble post = start++;
post.High.ShouldBe(9007199254740992.0);
start.Low.ShouldBe(1.0);
post = start--;
post.Low.ShouldBe(1.0);
start.Low.ShouldBe(0.0);
Increment(DoubleDouble.NegativeOne).ShouldBe(DoubleDouble.Zero);
Decrement(DoubleDouble.One).ShouldBe(DoubleDouble.Zero);
Increment(DoubleDouble.NaN).ShouldBe(DoubleDouble.NaN);
AssertBits(Decrement(new DoubleDouble(double.NegativeInfinity)), double.NegativeInfinity);
}
private static T Increment<T>(T value) where T : ISignedNumber<T>
{
return ++value;
}
private static T Decrement<T>(T value) where T : ISignedNumber<T>
{
return --value;
}
private static void AssertBits(DoubleDouble actual, double expected)
{
BitConverter.DoubleToInt64Bits(actual.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected));
BitConverter.DoubleToInt64Bits(actual.Low).ShouldBe(0L);
}
private static T NegativeOne<T>() where T : ISignedNumber<T>
{
return T.NegativeOne;
}
}