169 lines
6.9 KiB
C#
169 lines
6.9 KiB
C#
using System.Numerics;
|
|
using Shouldly;
|
|
using Xunit;
|
|
|
|
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;
|
|
}
|
|
}
|