square function and some sanity checks
.NET Test / .NET tests (push) Successful in 1m35s

This commit is contained in:
2026-09-18 16:59:28 +04:00
parent 5797bf4884
commit 620faca7eb
10 changed files with 455 additions and 28 deletions
@@ -4,6 +4,79 @@ namespace Just.PreciseMath.Tests;
public class DoubleDoubleFormattingTests
{
[Theory]
[InlineData(1.25, 0.0, "1.25")]
[InlineData(-1.25, 0.0, "-1.25")]
[InlineData(1000.0, 0.0, "1000")]
[InlineData(1.0, 5.551115123125783e-17, "1.000000000000000055511151231257827021181583404541015625")]
[InlineData(9007199254740992.0, 1.0, "9007199254740993")]
public void ExactFormattingPreservesTheCompleteDyadicValue(double high, double low, string expected)
{
// Exact binary fractions: the nonzero fractional low is 2^-54.
DoubleDouble value = DoubleDouble.FromComponents(high, low);
value.ToStringExact().ShouldBe(expected);
}
[Fact]
public void ExactFormattingUsesInvariantSpecialSymbolsAndPreservesZeroSigns()
{
CultureInfo original = CultureInfo.CurrentCulture;
try
{
CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo("fr-FR");
new DoubleDouble(-1.25).ToStringExact().ShouldBe("-1.25");
DoubleDouble[] values = [DoubleDouble.Zero, DoubleDouble.NegativeZero,
DoubleDouble.NaN, DoubleDouble.PositiveInfinity, DoubleDouble.NegativeInfinity];
string[] expected = ["0", "-0", "NaN", "Infinity", "-Infinity"];
for (int i = 0; i < values.Length; i++)
{
string text = values[i].ToStringExact();
text.ShouldBe(expected[i]);
DoubleDouble parsed = DoubleDouble.Parse(text, CultureInfo.InvariantCulture);
BitConverter.DoubleToInt64Bits(parsed.High).ShouldBe(BitConverter.DoubleToInt64Bits(values[i].High));
BitConverter.DoubleToInt64Bits(parsed.Low).ShouldBe(0L);
}
}
finally
{
CultureInfo.CurrentCulture = original;
}
}
[Fact]
public void ExactFormattingPreservesSparsePairsBeyondTheStandardPrecisionLimit()
{
DoubleDouble[] values = [new(double.Epsilon), new(double.MaxValue), DoubleDouble.Pi,
DoubleDouble.FromComponents(1.0, double.Epsilon),
DoubleDouble.FromComponents(1.0, -double.Epsilon),
DoubleDouble.FromComponents(double.MaxValue, double.Epsilon),
DoubleDouble.FromComponents(double.MaxValue, -double.Epsilon),
DoubleDouble.FromComponents(double.MaxValue, Math.BitDecrement(Math.ScaleB(1.0, 970)))];
foreach (DoubleDouble value in values)
{
AssertExactText(value);
AssertExactText(-value);
}
DoubleDouble.FromComponents(1.0, double.Epsilon).ToStringExact().Length.ShouldBeGreaterThan(999);
}
[Fact]
public void ExactFormattingMatchesIndependentIntegerValuesAcrossEveryExponent()
{
Random random = new(271828);
for (int exponent = -1074; exponent <= 1023; exponent++)
{
double high = Math.ScaleB(1.0 + random.NextDouble(), exponent);
double denseLow = Math.ScaleB(random.NextDouble(), exponent - 53);
foreach (double low in new[] { 0.0, denseLow, -denseLow, double.Epsilon, -double.Epsilon })
{
DoubleDouble value = DoubleDouble.FromComponents(high, low);
AssertExactText(value);
AssertExactText(-value);
}
}
}
[Theory]
[InlineData("G0")]
[InlineData("g0")]
@@ -138,4 +211,40 @@ public class DoubleDoubleFormattingTests
{
Should.Throw<FormatException>(() => DoubleDouble.One.ToString(format, CultureInfo.InvariantCulture));
}
private static void AssertExactText(DoubleDouble value)
{
string text = value.ToStringExact();
text.Length.ShouldBeLessThanOrEqualTo(2048);
text.ShouldNotContain("E");
text.ShouldNotContain("e");
int point = text.IndexOf('.', StringComparison.Ordinal);
int places = point < 0 ? 0 : text.Length - point - 1;
if (point >= 0)
{
text.ShouldNotEndWith("0");
text.ShouldNotEndWith(".");
}
string digits = point < 0 ? text : text.Remove(point, 1);
BigInteger coefficient = BigInteger.Parse(digits, CultureInfo.InvariantCulture);
// Independently decode the input as integer multiples of 2^-1074 and
// compare with the printed integer coefficient / 10^places exactly.
BigInteger units = ExactFormattingUnits(value.High) + ExactFormattingUnits(value.Low);
(coefficient << 1074).ShouldBe(units * BigInteger.Pow(10, places));
DoubleDouble parsed = DoubleDouble.Parse(text, CultureInfo.InvariantCulture);
BitConverter.DoubleToInt64Bits(parsed.High).ShouldBe(BitConverter.DoubleToInt64Bits(value.High));
BitConverter.DoubleToInt64Bits(parsed.Low).ShouldBe(BitConverter.DoubleToInt64Bits(value.Low));
}
private static BigInteger ExactFormattingUnits(double value)
{
long bits = BitConverter.DoubleToInt64Bits(value);
int exponent = (int)((bits >> 52) & 0x7ff);
BigInteger significand = bits & 0xfffffffffffffL;
if (exponent != 0)
{
significand = (significand + (BigInteger.One << 52)) << (exponent - 1);
}
return bits < 0 ? -significand : significand;
}
}