using System.Globalization; using System.Numerics; using Shouldly; using Xunit; namespace Just.PreciseMath.Tests; public class DoubleDoubleSpecialValueTests { [Theory] [InlineData("+NaN")] [InlineData("-NaN")] [InlineData("+Infinity")] [InlineData("-Infinity")] [InlineData(" NaN ")] [InlineData(" \t+Infinity\r\n")] public void StandardSpecialValuesMatchBinary64WithoutStyleFlags(string text) { double.TryParse(text, NumberStyles.None, CultureInfo.InvariantCulture, out double expected).ShouldBeTrue(); AssertAllParsers(text, CultureInfo.InvariantCulture, expected); } [Theory] [InlineData("inf", double.PositiveInfinity)] [InlineData("+INF", double.PositiveInfinity)] [InlineData("-iNf", double.NegativeInfinity)] [InlineData(" \t-inf\r\n", double.NegativeInfinity)] public void ShortInfinityAliasIsCaseInsensitive(string text, double expected) { AssertAllParsers(text, CultureInfo.InvariantCulture, expected); } [Fact] public void ShortInfinityUsesCultureSignsAndRespectsCustomSymbolPrecedence() { NumberFormatInfo info = new() { PositiveSign = "plus", NegativeSign = "minus" }; AssertAllParsers(" minusINF ", info, double.NegativeInfinity); AssertAllParsers("plusinf", info, double.PositiveInfinity); info.NaNSymbol = "inf"; AssertAllParsers("inf", info, double.NaN); info.NaNSymbol = "missing"; info.NegativeInfinitySymbol = "inf"; AssertAllParsers("inf", info, double.NegativeInfinity); } [Theory] [InlineData("in")] [InlineData("infx")] [InlineData("infinite")] [InlineData("--inf")] [InlineData("+ inf")] [InlineData("inf-")] public void MalformedAliasesAreRejected(string text) { DoubleDouble.TryParse(text, CultureInfo.InvariantCulture, out DoubleDouble result).ShouldBeFalse(); result.ShouldBe(DoubleDouble.Zero); DoubleDouble.TryParse(text.AsSpan(), NumberStyles.Any, CultureInfo.InvariantCulture, out result).ShouldBeFalse(); result.ShouldBe(DoubleDouble.Zero); } [Fact] public void SpecialValuesStillRespectRawLengthAndInvalidStyles() { AssertAllParsers(new string(' ', 2045) + "inf", CultureInfo.InvariantCulture, double.PositiveInfinity); string tooLong = new string(' ', 2046) + "inf"; DoubleDouble.TryParse(tooLong, CultureInfo.InvariantCulture, out _).ShouldBeFalse(); DoubleDouble.TryParse(tooLong.AsSpan(), NumberStyles.None, CultureInfo.InvariantCulture, out _).ShouldBeFalse(); Should.Throw(() => DoubleDouble.Parse(tooLong, NumberStyles.None, CultureInfo.InvariantCulture)); Should.Throw(() => DoubleDouble.TryParse("inf", NumberStyles.HexNumber, null, out _)); DoubleDouble.TryParse(" +1 ", NumberStyles.None, CultureInfo.InvariantCulture, out _).ShouldBeFalse(); } [Fact] public void AbsCanonicalizesRawNaNPayloadsOfEitherSign() { foreach (long bits in new[] { 0x7ff8000000000001L, unchecked((long)0xfff8000000000001UL) }) { DoubleDouble result = DoubleDouble.Abs(new DoubleDouble(BitConverter.Int64BitsToDouble(bits), 0.0)); BitConverter.DoubleToInt64Bits(result.High).ShouldBe(BitConverter.DoubleToInt64Bits(double.NaN)); BitConverter.DoubleToInt64Bits(result.Low).ShouldBe(0L); DoubleDouble.IsCanonical(result).ShouldBeTrue(); } } private static void AssertAllParsers(string text, IFormatProvider provider, double expected) { DoubleDouble.TryParse(text, provider, out DoubleDouble fromString).ShouldBeTrue(); DoubleDouble.TryParse(text.AsSpan(), provider, out DoubleDouble fromSpan).ShouldBeTrue(); DoubleDouble[] values = [fromString, fromSpan, DoubleDouble.Parse(text, provider), DoubleDouble.Parse(text.AsSpan(), provider), ParseStyled(text, provider)]; foreach (DoubleDouble value in values) { BitConverter.DoubleToInt64Bits(value.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected)); BitConverter.DoubleToInt64Bits(value.Low).ShouldBe(0L); } } private static T ParseStyled(string text, IFormatProvider provider) where T : INumberBase { T.TryParse(text, NumberStyles.None, provider, out T? fromString).ShouldBeTrue(); T.TryParse(text.AsSpan(), NumberStyles.None, provider, out T? fromSpan).ShouldBeTrue(); T result = T.Parse(text, NumberStyles.None, provider); fromString.ShouldBe(result); fromSpan.ShouldBe(result); T.Parse(text.AsSpan(), NumberStyles.None, provider).ShouldBe(result); return result; } }