This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user