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 { above, one }.Count.ShouldBe(2); ((IComparable)one).CompareTo(null).ShouldBe(1); Should.Throw(() => ((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 { 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); } }