Files
Just.PreciseMath/1-tests/Just.PreciseMath.Tests/GenericConversionTests.cs
T
just e51874b0c3
.NET Test / .NET tests (push) Successful in 4m27s
implemented ISignedNumber
2026-09-13 23:08:27 +04:00

152 lines
7.1 KiB
C#

using System.Diagnostics.CodeAnalysis;
using System.Numerics;
using Shouldly;
using Xunit;
namespace Just.PreciseMath.Tests;
public sealed class GenericConversionTests
{
[Fact]
public void GenericCreationPreservesComponentsAndExactIntegers()
{
DoubleDouble value = DoubleDouble.FromComponents(1.0, double.Epsilon);
DoubleDouble.CreateChecked(value).Low.ShouldBe(double.Epsilon);
DoubleDouble.CreateSaturating(value).Low.ShouldBe(double.Epsilon);
DoubleDouble.CreateTruncating(value).Low.ShouldBe(double.Epsilon);
DoubleDouble.CreateChecked(ulong.MaxValue).High.ShouldBe(Math.ScaleB(1.0, 64));
DoubleDouble.CreateChecked(ulong.MaxValue).Low.ShouldBe(-1.0);
DoubleDouble.CreateChecked(decimal.MaxValue).Low.ShouldBe(-1.0);
BigInteger integer = (BigInteger.One << 100) + 1;
BigInteger.CreateChecked(DoubleDouble.CreateChecked(integer)).ShouldBe(integer);
BitConverter.DoubleToInt64Bits(DoubleDouble.CreateChecked(-0.0).High).ShouldBe(long.MinValue);
}
[Fact]
public void IntegerTargetsTruncateTheExactSumBeforeApplyingRangePolicy()
{
int.CreateChecked(DoubleDouble.FromComponents(1.0, -double.Epsilon)).ShouldBe(0);
int.CreateChecked(DoubleDouble.FromComponents(-1.0, double.Epsilon)).ShouldBe(0);
long.CreateChecked(DoubleDouble.FromComponents(Math.ScaleB(1.0, 63), -1.0)).ShouldBe(long.MaxValue);
ulong.CreateChecked(DoubleDouble.FromComponents(Math.ScaleB(1.0, 64), -1.0)).ShouldBe(ulong.MaxValue);
DoubleDouble overflow = new(256.75);
Should.Throw<OverflowException>(() => byte.CreateChecked(overflow));
byte.CreateSaturating(overflow).ShouldBe(byte.MaxValue);
byte.CreateTruncating(overflow).ShouldBe((byte)0);
byte.CreateSaturating(new DoubleDouble(-1.0)).ShouldBe((byte)0);
byte.CreateTruncating(new DoubleDouble(-1.0)).ShouldBe(byte.MaxValue);
}
[Fact]
public void HugeIntegerInputsUseFloatingOverflowAndExactRatioRounding()
{
BigInteger huge = BigInteger.One << 2000;
double.IsPositiveInfinity(DoubleDouble.CreateChecked(huge).High).ShouldBeTrue();
double.IsPositiveInfinity(DoubleDouble.CreateSaturating(huge).High).ShouldBeTrue();
double.IsNegativeInfinity(DoubleDouble.CreateTruncating(-huge).High).ShouldBeTrue();
// A sparse exact integer must retain its low component rather than pass through double.
DoubleDouble sparse = DoubleDouble.CreateChecked((BigInteger.One << 100) + 1);
sparse.High.ShouldBe(Math.ScaleB(1.0, 100));
sparse.Low.ShouldBe(1.0);
}
[Fact]
public void FloatingAndDecimalTargetsRetainResidualRounding()
{
DoubleDouble aboveFloatMidpoint = DoubleDouble.FromComponents(1.0 + Math.ScaleB(1.0, -24), double.Epsilon);
float.CreateChecked(aboveFloatMidpoint).ShouldBe(float.BitIncrement(1.0f));
DoubleDouble aboveHalfMidpoint = DoubleDouble.FromComponents(1.0 + Math.ScaleB(1.0, -11), double.Epsilon);
Half.CreateChecked(aboveHalfMidpoint).ShouldBe(Half.BitIncrement((Half)1));
decimal.CreateChecked(DoubleDouble.CreateChecked(0.1m)).ShouldBe(0.1m);
Should.Throw<OverflowException>(() => decimal.CreateChecked(new DoubleDouble(double.PositiveInfinity)));
decimal.CreateSaturating(new DoubleDouble(double.PositiveInfinity)).ShouldBe(decimal.MaxValue);
decimal.CreateTruncating(DoubleDouble.NaN).ShouldBe(0m);
int.CreateSaturating(DoubleDouble.NaN).ShouldBe(0);
int.CreateTruncating(new DoubleDouble(double.PositiveInfinity)).ShouldBe(int.MaxValue);
Should.Throw<OverflowException>(() => int.CreateChecked(DoubleDouble.NaN));
}
[Fact]
public void BuiltInNumericFamiliesRoundTripThroughAllCreationModes()
{
RoundTrip((byte)123);
RoundTrip((sbyte)-123);
RoundTrip((short)-12345);
RoundTrip((ushort)54321);
RoundTrip(int.MinValue);
RoundTrip(uint.MaxValue);
RoundTrip(long.MinValue);
RoundTrip(ulong.MaxValue);
RoundTrip((nint)(-12345));
RoundTrip((nuint)54321);
RoundTrip((Int128.One << 100) + 1);
RoundTrip((UInt128.One << 100) + 1);
RoundTrip('A');
RoundTrip((Half)1.25);
RoundTrip(1.25f);
RoundTrip(1.25);
RoundTrip(1.25m);
}
private static void RoundTrip<T>(T value) where T : INumberBase<T>
{
T.CreateChecked(DoubleDouble.CreateChecked(value)).ShouldBe(value);
T.CreateSaturating(DoubleDouble.CreateSaturating(value)).ShouldBe(value);
T.CreateTruncating(DoubleDouble.CreateTruncating(value)).ShouldBe(value);
}
[Fact]
public void UnsupportedHooksReturnFalseWithoutTwoSidedRecursion()
{
ConversionProbe<DoubleDouble>.FromChecked(Complex.One, out DoubleDouble from).ShouldBeFalse();
from.ShouldBe(DoubleDouble.Zero);
ConversionProbe<DoubleDouble>.FromSaturating(Complex.One, out _).ShouldBeFalse();
ConversionProbe<DoubleDouble>.FromTruncating(Complex.One, out _).ShouldBeFalse();
ConversionProbe<DoubleDouble>.ToChecked(DoubleDouble.One, out Complex to).ShouldBeFalse();
to.ShouldBe(default);
ConversionProbe<DoubleDouble>.ToSaturating(DoubleDouble.One, out Complex _).ShouldBeFalse();
ConversionProbe<DoubleDouble>.ToTruncating(DoubleDouble.One, out Complex _).ShouldBeFalse();
}
private interface ConversionProbe<T> : INumberBase<T> where T : INumberBase<T>
{
public static bool FromChecked<TOther>(TOther value, [MaybeNullWhen(false)] out T result) where TOther : INumberBase<TOther>
{
return T.TryConvertFromChecked(value, out result);
}
public static bool FromSaturating<TOther>(TOther value, [MaybeNullWhen(false)] out T result) where TOther : INumberBase<TOther>
{
return T.TryConvertFromSaturating(value, out result);
}
public static bool FromTruncating<TOther>(TOther value, [MaybeNullWhen(false)] out T result) where TOther : INumberBase<TOther>
{
return T.TryConvertFromTruncating(value, out result);
}
public static bool ToChecked<TOther>(T value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase<TOther>
{
return T.TryConvertToChecked(value, out result);
}
public static bool ToSaturating<TOther>(T value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase<TOther>
{
return T.TryConvertToSaturating(value, out result);
}
public static bool ToTruncating<TOther>(T value, [MaybeNullWhen(false)] out TOther result) where TOther : INumberBase<TOther>
{
return T.TryConvertToTruncating(value, out result);
}
}
[Fact]
public void UnsupportedComplexConversionTerminatesWithNotSupported()
{
Should.Throw<NotSupportedException>(() => DoubleDouble.CreateChecked(Complex.One));
Should.Throw<NotSupportedException>(() => DoubleDouble.CreateSaturating(Complex.One));
Should.Throw<NotSupportedException>(() => DoubleDouble.CreateTruncating(Complex.One));
}
}