test-gap backlog fixes
.NET Test / .NET tests (push) Successful in 1m13s

This commit is contained in:
2026-09-13 23:34:43 +04:00
parent e51874b0c3
commit e05e1e902a
5 changed files with 681 additions and 0 deletions
@@ -7,6 +7,227 @@ namespace Just.PreciseMath.Tests;
public sealed class GenericConversionTests
{
[Theory]
[InlineData("byte")]
[InlineData("sbyte")]
[InlineData("short")]
[InlineData("ushort")]
[InlineData("int")]
[InlineData("uint")]
[InlineData("long")]
[InlineData("ulong")]
[InlineData("nint")]
[InlineData("nuint")]
[InlineData("Int128")]
[InlineData("UInt128")]
[InlineData("char")]
public void EveryBoundedIntegerCoversBothEndpointsAndAdjacentValues(string type)
{
switch (type)
{
case "byte": AssertIntegerBoundaries(byte.MinValue, byte.MaxValue); break;
case "sbyte": AssertIntegerBoundaries(sbyte.MinValue, sbyte.MaxValue); break;
case "short": AssertIntegerBoundaries(short.MinValue, short.MaxValue); break;
case "ushort": AssertIntegerBoundaries(ushort.MinValue, ushort.MaxValue); break;
case "int": AssertIntegerBoundaries(int.MinValue, int.MaxValue); break;
case "uint": AssertIntegerBoundaries(uint.MinValue, uint.MaxValue); break;
case "long": AssertIntegerBoundaries(long.MinValue, long.MaxValue); break;
case "ulong": AssertIntegerBoundaries(ulong.MinValue, ulong.MaxValue); break;
case "nint": AssertIntegerBoundaries(nint.MinValue, nint.MaxValue); break;
case "nuint": AssertIntegerBoundaries(nuint.MinValue, nuint.MaxValue); break;
case "Int128": AssertIntegerBoundaries(Int128.MinValue, Int128.MaxValue); break;
case "UInt128": AssertIntegerBoundaries(UInt128.MinValue, UInt128.MaxValue); break;
case "char": AssertIntegerBoundaries(char.MinValue, char.MaxValue); break;
default: throw new ArgumentOutOfRangeException(nameof(type));
}
}
private static void AssertIntegerBoundaries<T>(T minimum, T maximum) where T : struct, INumberBase<T>
{
BigInteger min = BigInteger.CreateChecked(minimum);
BigInteger max = BigInteger.CreateChecked(maximum);
BigInteger width = max - min + 1;
// Integer inputs near these power-of-two endpoints have exact two-component
// representations, even for Int128/UInt128. No round-trip is used as an oracle.
foreach (BigInteger integer in new[] { min, min + 1, max - 1, max })
{
T input = T.CreateChecked(integer);
foreach (int mode in new[] { 0, 1, 2 })
{
DoubleDouble direct = mode switch
{
0 => DoubleDouble.CreateChecked(input),
1 => DoubleDouble.CreateSaturating(input),
_ => DoubleDouble.CreateTruncating(input),
};
AssertExactQuarters(direct, integer * 4);
AssertExactQuarters(CreateInMode<DoubleDouble, T>(input, mode), integer * 4);
DoubleDouble hooked;
bool supported = mode switch
{
0 => ConversionProbe<DoubleDouble>.FromChecked(input, out hooked),
1 => ConversionProbe<DoubleDouble>.FromSaturating(input, out hooked),
_ => ConversionProbe<DoubleDouble>.FromTruncating(input, out hooked),
};
supported.ShouldBeTrue();
AssertExactQuarters(hooked, integer * 4);
}
}
foreach (BigInteger endpoint in new[] { min, max })
{
// Whole-unit neighbors test range transitions; quarter-unit neighbors
// prove truncation happens BEFORE range handling, on the exact sum.
// Including +/-1.25 crosses the negative endpoint's truncation boundary.
foreach (int offset in new[] { -5, -4, -3, -1, 0, 1, 3, 4, 5 })
{
BigInteger numerator = (endpoint * 4) + offset;
DoubleDouble input = FromExactQuarters(numerator);
BigInteger integer = numerator / 4;
BigInteger clamped = BigInteger.Min(max, BigInteger.Max(min, integer));
// Finite DD sources use BigInteger-style modular truncation, NOT
// binary64's floating-to-integer clamping. Derive modulo independently.
BigInteger wrapped = (((integer - min) % width) + width) % width + min;
foreach (int mode in new[] { 0, 1, 2 })
{
if (mode == 0 && (integer < min || integer > max))
{
Should.Throw<OverflowException>(() => CreateInMode<T, DoubleDouble>(input, mode));
Should.Throw<OverflowException>(() => ConvertToInMode<T>(input, mode));
}
else
{
BigInteger expected = mode == 0 ? integer : mode == 1 ? clamped : wrapped;
BigInteger.CreateChecked(CreateInMode<T, DoubleDouble>(input, mode)).ShouldBe(expected);
BigInteger.CreateChecked(ConvertToInMode<T>(input, mode)).ShouldBe(expected);
}
}
}
}
AssertNonfiniteIntegerPolicy<T>();
}
private static TTarget CreateInMode<TTarget, TSource>(TSource value, int mode)
where TTarget : INumberBase<TTarget>
where TSource : INumberBase<TSource>
{
return mode switch
{
0 => TTarget.CreateChecked(value),
1 => TTarget.CreateSaturating(value),
_ => TTarget.CreateTruncating(value),
};
}
private static T ConvertToInMode<T>(DoubleDouble value, int mode) where T : struct, INumberBase<T>
{
T result;
bool supported = mode switch
{
0 => ConversionProbe<DoubleDouble>.ToChecked(value, out result),
1 => ConversionProbe<DoubleDouble>.ToSaturating(value, out result),
_ => ConversionProbe<DoubleDouble>.ToTruncating(value, out result),
};
supported.ShouldBeTrue();
return result;
}
private static DoubleDouble FromExactQuarters(BigInteger numerator)
{
// Choose the nearest signed power of two using exact integer distances.
// A direct BigInteger-to-double cast can truncate rather than round, leaving
// an inexact large residual at 128-bit endpoints. Powers of two cast exactly.
BigInteger magnitude = BigInteger.Abs(numerator);
BigInteger integral = magnitude / 4;
BigInteger anchor = BigInteger.Zero;
if (!integral.IsZero)
{
anchor = BigInteger.One << checked((int)integral.GetBitLength() - 1);
if (BigInteger.Abs(magnitude - (anchor * 8)) < BigInteger.Abs(magnitude - (anchor * 4)))
{
anchor *= 2;
}
}
double high = (double)(anchor * numerator.Sign);
double low = (double)(numerator - (new BigInteger(high) * 4)) / 4.0;
DoubleDouble result = DoubleDouble.FromComponents(high, low);
AssertExactQuarters(result, numerator);
return result;
}
private static void AssertExactQuarters(DoubleDouble value, BigInteger numerator)
{
double high = value.High * 4.0;
double low = value.Low * 4.0;
double.IsFinite(high).ShouldBeTrue();
double.IsFinite(low).ShouldBeTrue();
Math.Truncate(high).ShouldBe(high);
Math.Truncate(low).ShouldBe(low);
(new BigInteger(high) + new BigInteger(low)).ShouldBe(numerator);
}
private static void AssertNonfiniteIntegerPolicy<T>() where T : struct, INumberBase<T>
{
// Unlike finite DD inputs, NaN/infinities explicitly inherit the destination's
// BCL binary64-source policy. Query that independent API, including exceptions;
// do not assume every signed/unsigned/native target maps NaN identically.
foreach (double special in new[] { double.NaN, double.NegativeInfinity, double.PositiveInfinity })
{
foreach (int mode in new[] { 0, 1, 2 })
{
T expected;
try
{
expected = CreateInMode<T, double>(special, mode);
}
catch (OverflowException)
{
Should.Throw<OverflowException>(() => CreateInMode<T, DoubleDouble>(new DoubleDouble(special), mode));
Should.Throw<OverflowException>(() => ConvertToInMode<T>(new DoubleDouble(special), mode));
continue;
}
CreateInMode<T, DoubleDouble>(new DoubleDouble(special), mode).ShouldBe(expected);
ConvertToInMode<T>(new DoubleDouble(special), mode).ShouldBe(expected);
}
}
}
[Fact]
public void UnboundedIntegerUsesFloatingInputOverflowButHasNoFiniteOutputEndpoints()
{
foreach (int sign in new[] { -1, 1 })
{
BigInteger huge = sign * (BigInteger.One << 2000);
foreach (int mode in new[] { 0, 1, 2 })
{
DoubleDouble created = CreateInMode<DoubleDouble, BigInteger>(huge, mode);
created.High.ShouldBe(sign < 0 ? double.NegativeInfinity : double.PositiveInfinity);
created.Low.ShouldBe(0.0);
DoubleDouble hooked;
bool supported = mode switch
{
0 => ConversionProbe<DoubleDouble>.FromChecked(huge, out hooked),
1 => ConversionProbe<DoubleDouble>.FromSaturating(huge, out hooked),
_ => ConversionProbe<DoubleDouble>.FromTruncating(huge, out hooked),
};
supported.ShouldBeTrue();
hooked.High.ShouldBe(created.High);
hooked.Low.ShouldBe(0.0);
// BigInteger has no endpoints: exact sparse integers beyond UInt128
// still truncate without clamping or wrapping in every output mode.
BigInteger integer = sign * ((BigInteger.One << 200) + 1);
DoubleDouble finite = FromExactQuarters((integer * 4) + sign);
CreateInMode<BigInteger, DoubleDouble>(finite, mode).ShouldBe(integer);
ConvertToInMode<BigInteger>(finite, mode).ShouldBe(integer);
AssertExactQuarters(CreateInMode<DoubleDouble, BigInteger>(integer, mode), integer * 4);
}
}
AssertNonfiniteIntegerPolicy<BigInteger>();
}
[Fact]
public void GenericCreationPreservesComponentsAndExactIntegers()
{