152 lines
7.1 KiB
C#
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));
|
|
}
|
|
}
|