108 lines
4.6 KiB
C#
108 lines
4.6 KiB
C#
using Shouldly;
|
|
using Xunit;
|
|
|
|
namespace Just.PreciseMath.Tests;
|
|
|
|
public class DoubleDoubleComparisonTests
|
|
{
|
|
[Fact]
|
|
public void RelationalOperatorsCoverEqualHighComponentsAndSpecialValues()
|
|
{
|
|
// Explicit numerical order, including opposite low-component signs and
|
|
// equivalent signed zeros. NaNs are tested separately as unordered.
|
|
DoubleDouble[] ordered =
|
|
[
|
|
new(double.NegativeInfinity), new(-double.MaxValue),
|
|
DoubleDouble.FromComponents(-1.0, -double.Epsilon), new(-1.0), DoubleDouble.FromComponents(-1.0, double.Epsilon),
|
|
new(-double.Epsilon), new(-0.0), new(0.0), new(double.Epsilon),
|
|
DoubleDouble.FromComponents(1.0, -double.Epsilon), new(1.0), DoubleDouble.FromComponents(1.0, double.Epsilon),
|
|
new(double.MaxValue), new(double.PositiveInfinity)
|
|
];
|
|
for (int i = 0; i < ordered.Length; ++i)
|
|
{
|
|
for (int j = 0; j < ordered.Length; ++j)
|
|
{
|
|
bool bothZero = (i is 6 or 7) && (j is 6 or 7);
|
|
(ordered[i] < ordered[j]).ShouldBe(i < j && !bothZero);
|
|
(ordered[i] > ordered[j]).ShouldBe(i > j && !bothZero);
|
|
(ordered[i] <= ordered[j]).ShouldBe(i <= j || bothZero);
|
|
(ordered[i] >= ordered[j]).ShouldBe(i >= j || bothZero);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void OrderingUsesLowComponentAndSupportsCollections()
|
|
{
|
|
DoubleDouble one = DoubleDouble.One;
|
|
DoubleDouble above = DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -100));
|
|
(above > one).ShouldBeTrue();
|
|
(one < above).ShouldBeTrue();
|
|
(one <= above).ShouldBeTrue();
|
|
(above >= one).ShouldBeTrue();
|
|
above.CompareTo(one).ShouldBeGreaterThan(0);
|
|
new SortedSet<DoubleDouble> { above, one }.Count.ShouldBe(2);
|
|
((IComparable)one).CompareTo(null).ShouldBe(1);
|
|
Should.Throw<ArgumentException>(() => ((IComparable)one).CompareTo("1"));
|
|
}
|
|
|
|
[Fact]
|
|
public void DistinctLowComponentsAreNotCollapsedByOrderingOrCollections()
|
|
{
|
|
// Review-1 §3: a = (1, 0) and b = (1, 1e-30) are distinct values. The former
|
|
// high-only CompareTo reported them equal, so a SortedSet retained only one.
|
|
DoubleDouble a = DoubleDouble.One;
|
|
DoubleDouble b = DoubleDouble.FromComponents(1.0, 1e-30);
|
|
b.High.ShouldBe(1.0);
|
|
b.Low.ShouldBe(1e-30);
|
|
(b > a).ShouldBeTrue();
|
|
(a < b).ShouldBeTrue();
|
|
b.CompareTo(a).ShouldBeGreaterThan(0);
|
|
b.Equals(a).ShouldBeFalse();
|
|
new SortedSet<DoubleDouble> { b, a }.Count.ShouldBe(2);
|
|
}
|
|
|
|
[Fact]
|
|
public void NaNOperatorsAreUnorderedButCompareToProvidesTotalOrder()
|
|
{
|
|
DoubleDouble nan = DoubleDouble.NaN;
|
|
foreach (DoubleDouble other in new[] { nan, DoubleDouble.Zero, new DoubleDouble(-0.0),
|
|
new DoubleDouble(double.NegativeInfinity), new DoubleDouble(double.PositiveInfinity),
|
|
DoubleDouble.FromComponents(1.0, double.Epsilon), DoubleDouble.FromComponents(-1.0, -double.Epsilon) })
|
|
{
|
|
(nan < other).ShouldBeFalse();
|
|
(nan > other).ShouldBeFalse();
|
|
(nan <= other).ShouldBeFalse();
|
|
(nan >= other).ShouldBeFalse();
|
|
(other < nan).ShouldBeFalse();
|
|
(other > nan).ShouldBeFalse();
|
|
(other <= nan).ShouldBeFalse();
|
|
(other >= nan).ShouldBeFalse();
|
|
}
|
|
nan.CompareTo(nan).ShouldBe(0);
|
|
nan.CompareTo(DoubleDouble.Zero).ShouldBeLessThan(0);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1.0, 0.0)]
|
|
[InlineData(0.0, -1.0)]
|
|
[InlineData(double.NaN, 0.0)]
|
|
[InlineData(0.0, double.NaN)]
|
|
[InlineData(double.PositiveInfinity, 0.0)]
|
|
[InlineData(0.0, double.NegativeInfinity)]
|
|
[InlineData(double.PositiveInfinity, double.NegativeInfinity)]
|
|
public void ClassificationFollowsCanonicalHigh(double high, double low)
|
|
{
|
|
DoubleDouble value = DoubleDouble.FromComponents(high, low);
|
|
DoubleDouble.IsNaN(value).ShouldBe(double.IsNaN(value.High));
|
|
DoubleDouble.IsFinite(value).ShouldBe(double.IsFinite(value.High));
|
|
DoubleDouble.IsInfinity(value).ShouldBe(double.IsInfinity(value.High));
|
|
DoubleDouble.IsPositiveInfinity(value).ShouldBe(double.IsPositiveInfinity(value.High));
|
|
DoubleDouble.IsNegativeInfinity(value).ShouldBe(double.IsNegativeInfinity(value.High));
|
|
DoubleDouble.IsNegative(value).ShouldBe(double.IsNegative(value.High));
|
|
value.Decompose(out double actualHigh, out double actualLow);
|
|
actualHigh.ShouldBe(value.High);
|
|
actualLow.ShouldBe(value.Low);
|
|
}
|
|
}
|