162 lines
7.7 KiB
C#
162 lines
7.7 KiB
C#
using System.Globalization;
|
|
|
|
namespace Just.PreciseMath.Tests;
|
|
|
|
public class DoubleDoubleNumberStylesTests
|
|
{
|
|
[Theory]
|
|
[InlineData(" 9,007,199,254,740,993 ", NumberStyles.Number, 9007199254740992.0, 1.0)]
|
|
[InlineData("-1.000000000000000055511151231257827021181583404541015625", NumberStyles.Float, -1.0, -5.5511151231257827e-17)]
|
|
public void GenericStyledParsingRetainsExactResidual(string text, NumberStyles style, double high, double low)
|
|
{
|
|
// Exact integers 2^53 + 1 and dyadic -(1 + 2^-54), not rounded double parses.
|
|
AssertGenericStyledParsing<DoubleDouble>(text, style, high, low);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("1,000", NumberStyles.Float)]
|
|
[InlineData("1e+", NumberStyles.Any)]
|
|
[InlineData("not-a-number", NumberStyles.Number)]
|
|
public void GenericStyledParsingRejectsInvalidInput(string text, NumberStyles style)
|
|
{
|
|
AssertGenericStyledParsingFailure<DoubleDouble>(text, style);
|
|
}
|
|
|
|
private static void AssertGenericStyledParsing<T>(string text, NumberStyles style, double high, double low)
|
|
where T : INumberBase<T>
|
|
{
|
|
T fromString = T.Parse(text, style, CultureInfo.InvariantCulture);
|
|
T fromSpan = T.Parse(text.AsSpan(), style, CultureInfo.InvariantCulture);
|
|
T.TryParse(text, style, CultureInfo.InvariantCulture, out T? triedString).ShouldBeTrue();
|
|
T.TryParse(text.AsSpan(), style, CultureInfo.InvariantCulture, out T? triedSpan).ShouldBeTrue();
|
|
foreach (T? parsed in new[] { fromString, fromSpan, triedString, triedSpan })
|
|
{
|
|
DoubleDouble value = parsed.ShouldBeOfType<DoubleDouble>();
|
|
value.High.ShouldBe(high);
|
|
value.Low.ShouldBe(low);
|
|
}
|
|
}
|
|
|
|
private static void AssertGenericStyledParsingFailure<T>(string text, NumberStyles style)
|
|
where T : INumberBase<T>
|
|
{
|
|
Should.Throw<FormatException>(() => T.Parse(text, style, CultureInfo.InvariantCulture));
|
|
Should.Throw<FormatException>(() => T.Parse(text.AsSpan(), style, CultureInfo.InvariantCulture));
|
|
T.TryParse(text, style, CultureInfo.InvariantCulture, out T? fromString).ShouldBeFalse();
|
|
T.TryParse(text.AsSpan(), style, CultureInfo.InvariantCulture, out T? fromSpan).ShouldBeFalse();
|
|
foreach (T? parsed in new[] { fromString, fromSpan })
|
|
{
|
|
DoubleDouble value = parsed.ShouldBeOfType<DoubleDouble>();
|
|
BitConverter.DoubleToInt64Bits(value.High).ShouldBe(0L);
|
|
BitConverter.DoubleToInt64Bits(value.Low).ShouldBe(0L);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("123", NumberStyles.None, 123.0)]
|
|
[InlineData(" -1.25e+2 ", NumberStyles.Float, -125.0)]
|
|
[InlineData("1,234.5-", NumberStyles.Number, -1234.5)]
|
|
[InlineData("(¤1,234.5)", NumberStyles.Currency, -1234.5)]
|
|
[InlineData("1,234.5¤", NumberStyles.Currency, 1234.5)]
|
|
[InlineData("(1.25E2)", NumberStyles.Any, -125.0)]
|
|
public void DecimalStylesSupportTheirStandardSyntax(string text, NumberStyles style, double expected)
|
|
{
|
|
DoubleDouble.Parse(text, style, CultureInfo.InvariantCulture).ShouldBe(new DoubleDouble(expected));
|
|
DoubleDouble.Parse(text.AsSpan(), style, CultureInfo.InvariantCulture).ShouldBe(new DoubleDouble(expected));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(" 1", NumberStyles.None)]
|
|
[InlineData("1 ", NumberStyles.None)]
|
|
[InlineData("-1", NumberStyles.None)]
|
|
[InlineData("1-", NumberStyles.Float)]
|
|
[InlineData("1.0", NumberStyles.Integer)]
|
|
[InlineData("1e2", NumberStyles.Number)]
|
|
[InlineData("1,000", NumberStyles.Float)]
|
|
[InlineData("(1)", NumberStyles.Number)]
|
|
[InlineData("¤1", NumberStyles.Number)]
|
|
[InlineData("--1", NumberStyles.Any)]
|
|
[InlineData("1.2,3", NumberStyles.Any)]
|
|
[InlineData("(1)-", NumberStyles.Any)]
|
|
[InlineData("1e+", NumberStyles.Any)]
|
|
public void DisallowedOrMalformedSyntaxFailsWithZero(string text, NumberStyles style)
|
|
{
|
|
DoubleDouble.TryParse(text, style, CultureInfo.InvariantCulture, out DoubleDouble result).ShouldBeFalse();
|
|
result.ShouldBe(DoubleDouble.Zero);
|
|
DoubleDouble.TryParse(text.AsSpan(), style, CultureInfo.InvariantCulture, out result).ShouldBeFalse();
|
|
result.ShouldBe(DoubleDouble.Zero);
|
|
Should.Throw<FormatException>(() => DoubleDouble.Parse(text, style, CultureInfo.InvariantCulture));
|
|
Should.Throw<FormatException>(() => DoubleDouble.Parse(text.AsSpan(), style, CultureInfo.InvariantCulture));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(NumberStyles.HexNumber)]
|
|
[InlineData(NumberStyles.BinaryNumber)]
|
|
[InlineData((NumberStyles)1024)]
|
|
[InlineData((NumberStyles)(-1))]
|
|
public void InvalidStylesThrowEvenForNullTryParse(NumberStyles style)
|
|
{
|
|
Should.Throw<ArgumentException>(() => DoubleDouble.Parse("1", style, null));
|
|
Should.Throw<ArgumentException>(() => DoubleDouble.Parse("1".AsSpan(), style, null));
|
|
Should.Throw<ArgumentException>(() => DoubleDouble.TryParse((string?)null, style, null, out _));
|
|
Should.Throw<ArgumentException>(() => DoubleDouble.TryParse(ReadOnlySpan<char>.Empty, style, null, out _));
|
|
}
|
|
|
|
[Fact]
|
|
public void NullAndLengthContractsArePreserved()
|
|
{
|
|
Should.Throw<ArgumentNullException>(() => DoubleDouble.Parse(null!, NumberStyles.Any, null));
|
|
DoubleDouble.TryParse((string?)null, NumberStyles.Any, null, out DoubleDouble result).ShouldBeFalse();
|
|
result.ShouldBe(DoubleDouble.Zero);
|
|
DoubleDouble.TryParse(new string('0', 2049), NumberStyles.Any, null, out result).ShouldBeFalse();
|
|
DoubleDouble.Parse(new string('0', 2048), NumberStyles.None, null).ShouldBe(DoubleDouble.Zero);
|
|
DoubleDouble.TryParse("1,000", CultureInfo.InvariantCulture, out _).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void CultureAndExponentPreserveExactComponents()
|
|
{
|
|
NumberFormatInfo info = new()
|
|
{
|
|
NegativeSign = "minus",
|
|
PositiveSign = "plus",
|
|
NumberDecimalSeparator = ";;",
|
|
NumberGroupSeparator = "_",
|
|
CurrencyDecimalSeparator = ":",
|
|
CurrencyGroupSeparator = "~",
|
|
CurrencySymbol = "USD"
|
|
};
|
|
DoubleDouble.Parse("minus9_007_199_254_740_993;;0Eplus0", NumberStyles.Any, info)
|
|
.ShouldBe(DoubleDouble.FromComponents(-9007199254740992.0, -1.0));
|
|
DoubleDouble.Parse("USD9~007~199~254~740~993:0", NumberStyles.Currency, info)
|
|
.ShouldBe(DoubleDouble.FromComponents(9007199254740992.0, 1.0));
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData("-0", -0.0)]
|
|
[InlineData("-1e-9999", -0.0)]
|
|
[InlineData("1e9999", double.PositiveInfinity)]
|
|
[InlineData("-Infinity", double.NegativeInfinity)]
|
|
[InlineData("NaN", double.NaN)]
|
|
public void NonfiniteAndSignedZeroContractsArePreserved(string text, double expected)
|
|
{
|
|
DoubleDouble value = DoubleDouble.Parse(text, NumberStyles.Float, CultureInfo.InvariantCulture);
|
|
BitConverter.DoubleToInt64Bits(value.High).ShouldBe(BitConverter.DoubleToInt64Bits(expected));
|
|
value.Low.ShouldBe(0.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void StyledParsingRetainsExactIntegerResidualAcrossOverloads()
|
|
{
|
|
const string text = " 9,007,199,254,740,993 ";
|
|
NumberStyles style = NumberStyles.Number;
|
|
DoubleDouble expected = DoubleDouble.FromComponents(9007199254740992.0, 1.0);
|
|
DoubleDouble.Parse(text, style, CultureInfo.InvariantCulture).ShouldBe(expected);
|
|
DoubleDouble.Parse(text.AsSpan(), style, CultureInfo.InvariantCulture).ShouldBe(expected);
|
|
DoubleDouble.TryParse(text, style, CultureInfo.InvariantCulture, out DoubleDouble fromString).ShouldBeTrue();
|
|
fromString.ShouldBe(expected);
|
|
DoubleDouble.TryParse(text.AsSpan(), style, CultureInfo.InvariantCulture, out DoubleDouble fromSpan).ShouldBeTrue();
|
|
fromSpan.ShouldBe(expected);
|
|
}
|
|
}
|