486 lines
23 KiB
C#
486 lines
23 KiB
C#
namespace Just.PreciseMath.Tests;
|
|
|
|
public class DoubleDoubleConversionTests
|
|
{
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void FloatOverflowMidpointUsesDecidingResidual(bool negative)
|
|
{
|
|
// Max = 2^128 - 2^104; midpoint to the next binade is
|
|
// 2^128 - 2^103. Its upper significand is even, hence the tie overflows.
|
|
double midpoint = Math.ScaleB(1.0, 128) - Math.ScaleB(1.0, 103);
|
|
foreach (int direction in new[] { -1, 0, 1 })
|
|
{
|
|
double sign = negative ? -1.0 : 1.0;
|
|
DoubleDouble value = DoubleDouble.FromComponents(sign * midpoint, sign * direction);
|
|
uint magnitude = direction < 0 ? 0x7f7fffffU : 0x7f800000U;
|
|
uint expected = magnitude | (negative ? 0x80000000U : 0U);
|
|
BitConverter.SingleToUInt32Bits((float)value).ShouldBe(expected);
|
|
BitConverter.SingleToUInt32Bits(((IConvertible)value).ToSingle(null)).ShouldBe(expected);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0, false)]
|
|
[InlineData(1, false)]
|
|
[InlineData(2, false)]
|
|
[InlineData(0, true)]
|
|
[InlineData(1, true)]
|
|
[InlineData(2, true)]
|
|
public void FloatSubnormalMidpointsRoundEvenWithBothSigns(int lower, bool negative)
|
|
{
|
|
// Subnormal bits are integer multiples of 2^-149. The exact midpoint
|
|
// (2*lower+1)*2^-150 chooses the even integer; +/-2^-1074 decides sides.
|
|
foreach (int direction in new[] { -1, 0, 1 })
|
|
{
|
|
double sign = negative ? -1.0 : 1.0;
|
|
DoubleDouble value = DoubleDouble.FromComponents(
|
|
sign * Math.ScaleB((2 * lower) + 1, -150), sign * direction * double.Epsilon);
|
|
int rounded = direction < 0 ? lower : direction > 0 ? lower + 1 : lower + (lower & 1);
|
|
uint expected = (uint)rounded | (negative ? 0x80000000U : 0U);
|
|
BitConverter.SingleToUInt32Bits((float)value).ShouldBe(expected);
|
|
BitConverter.SingleToUInt32Bits(((IConvertible)value).ToSingle(null)).ShouldBe(expected);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(32)]
|
|
[InlineData(64)]
|
|
public void ExplicitIntegerEndpointsTruncateBeforeCheckingRange(int bits)
|
|
{
|
|
BigInteger minimum = -(BigInteger.One << (bits - 1));
|
|
BigInteger maximum = -minimum - 1;
|
|
foreach (BigInteger endpoint in new[] { minimum, maximum })
|
|
{
|
|
// Probe the endpoint and each truncation transition with exact dyadics.
|
|
// Division of signed BigIntegers truncates toward zero independently.
|
|
foreach (int offset in new[] { -1, 0, 1 })
|
|
{
|
|
foreach (int side in new[] { -1, 0, 1 })
|
|
{
|
|
BigInteger denominator = BigInteger.One << 40;
|
|
BigInteger numerator = ((endpoint + offset) * denominator) + side;
|
|
BigInteger expected = numerator / denominator;
|
|
DoubleDouble value = ExactEndpointInput(numerator, denominator);
|
|
if (expected < minimum || expected > maximum)
|
|
{
|
|
Should.Throw<OverflowException>(() => ExplicitInteger(value, bits));
|
|
}
|
|
else
|
|
{
|
|
ExplicitInteger(value, bits).ShouldBe(expected);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(TypeCode.SByte, 8, true)]
|
|
[InlineData(TypeCode.Byte, 8, false)]
|
|
[InlineData(TypeCode.Int16, 16, true)]
|
|
[InlineData(TypeCode.UInt16, 16, false)]
|
|
[InlineData(TypeCode.Int32, 32, true)]
|
|
[InlineData(TypeCode.UInt32, 32, false)]
|
|
[InlineData(TypeCode.Int64, 64, true)]
|
|
[InlineData(TypeCode.UInt64, 64, false)]
|
|
public void ConvertibleIntegerEndpointsRoundBeforeCheckingRange(TypeCode type, int bits, bool isSigned)
|
|
{
|
|
BigInteger minimum = isSigned ? -(BigInteger.One << (bits - 1)) : BigInteger.Zero;
|
|
BigInteger maximum = (BigInteger.One << (isSigned ? bits - 1 : bits)) - 1;
|
|
BigInteger denominator = BigInteger.One << 40;
|
|
foreach (BigInteger endpoint in new[] { minimum, maximum })
|
|
{
|
|
foreach (int halfOffset in new[] { -2, -1, 0, 1, 2 })
|
|
{
|
|
foreach (int side in new[] { -1, 0, 1 })
|
|
{
|
|
BigInteger numerator = (endpoint * denominator) + (halfOffset * (denominator / 2)) + side;
|
|
// Choose the closest of floor(x) and floor(x)+1 by exact distances,
|
|
// selecting the even candidate at a tie. Includes unsigned -0.5.
|
|
BigInteger lower = numerator / denominator;
|
|
if (numerator < 0 && numerator % denominator != 0)
|
|
{
|
|
lower--;
|
|
}
|
|
BigInteger distanceBelow = numerator - (lower * denominator);
|
|
BigInteger distanceAbove = ((lower + 1) * denominator) - numerator;
|
|
BigInteger expected = distanceBelow < distanceAbove ||
|
|
(distanceBelow == distanceAbove && lower.IsEven) ? lower : lower + 1;
|
|
IConvertible value = ExactEndpointInput(numerator, denominator);
|
|
if (expected < minimum || expected > maximum)
|
|
{
|
|
Should.Throw<OverflowException>(() => ConvertibleInteger(value, type));
|
|
Should.Throw<OverflowException>(() => Convert.ChangeType(value, type, System.Globalization.CultureInfo.InvariantCulture));
|
|
}
|
|
else
|
|
{
|
|
ConvertibleInteger(value, type).ShouldBe(expected);
|
|
Convert.ChangeType(value, type, System.Globalization.CultureInfo.InvariantCulture)
|
|
.ShouldBe(Convert.ChangeType((decimal)expected, type, System.Globalization.CultureInfo.InvariantCulture));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private static DoubleDouble ExactEndpointInput(BigInteger numerator, BigInteger denominator)
|
|
{
|
|
// All inputs have denominator 2^40, magnitude <= 2^64+2, and a
|
|
// residual requiring <= 53 bits. Splitting the integer part is exact.
|
|
double high = (double)(numerator / denominator);
|
|
double low = (double)(numerator - (new BigInteger(high) * denominator)) / (double)denominator;
|
|
DoubleDouble value = DoubleDouble.FromComponents(high, low);
|
|
(new BigInteger(value.High * (double)denominator) +
|
|
new BigInteger(value.Low * (double)denominator)).ShouldBe(numerator);
|
|
return value;
|
|
}
|
|
|
|
private static BigInteger ExplicitInteger(DoubleDouble value, int bits)
|
|
{
|
|
return bits == 32 ? (int)value : (long)value;
|
|
}
|
|
|
|
private static BigInteger ConvertibleInteger(IConvertible value, TypeCode type)
|
|
{
|
|
return type switch
|
|
{
|
|
TypeCode.SByte => value.ToSByte(null),
|
|
TypeCode.Byte => value.ToByte(null),
|
|
TypeCode.Int16 => value.ToInt16(null),
|
|
TypeCode.UInt16 => value.ToUInt16(null),
|
|
TypeCode.Int32 => value.ToInt32(null),
|
|
TypeCode.UInt32 => value.ToUInt32(null),
|
|
TypeCode.Int64 => value.ToInt64(null),
|
|
TypeCode.UInt64 => value.ToUInt64(null),
|
|
_ => throw new ArgumentOutOfRangeException(nameof(type))
|
|
};
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void DecimalEndpointsCheckExactMagnitudeBeforeRounding(bool negative)
|
|
{
|
|
// M = 2^96-1; at scale zero M is odd. M-1/2 rounds to M-1,
|
|
// while M +/- 2^-40 would round to M but only the inside value is legal.
|
|
double step = Math.ScaleB(1.0, -40);
|
|
foreach (double offset in new[] { -1.0, -0.5 - step, -0.5, -0.5 + step, -step, 0.0, step, 0.5, 1.0 })
|
|
{
|
|
double sign = negative ? -1.0 : 1.0;
|
|
DoubleDouble value = DoubleDouble.FromComponents(sign * Math.ScaleB(1.0, 96), sign * (-1.0 + offset));
|
|
if (offset > 0)
|
|
{
|
|
Should.Throw<OverflowException>(() => (decimal)value);
|
|
Should.Throw<OverflowException>(() => ((IConvertible)value).ToDecimal(null));
|
|
}
|
|
else
|
|
{
|
|
decimal expected = decimal.MaxValue - (offset <= -0.5 ? 1m : 0m);
|
|
expected = negative ? -expected : expected;
|
|
((decimal)value).ShouldBe(expected);
|
|
((IConvertible)value).ToDecimal(null).ShouldBe(expected);
|
|
}
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(1)]
|
|
[InlineData(3)]
|
|
public void NegativeDecimalMidpointsRoundEvenAtMaximumScale(int multiplier)
|
|
{
|
|
// -m*2^-29 * 10^28 = -m*5^28/2. For m=1 the magnitude's
|
|
// lower coefficient is even; for m=3 it is odd. No decimal input oracle.
|
|
BigInteger twiceMagnitude = multiplier * BigInteger.Pow(5, 28);
|
|
BigInteger lowerMagnitude = twiceMagnitude / 2;
|
|
foreach (int direction in new[] { -1, 0, 1 })
|
|
{
|
|
DoubleDouble value = DoubleDouble.FromComponents(-Math.ScaleB(multiplier, -29), direction * double.Epsilon);
|
|
BigInteger coefficient = lowerMagnitude +
|
|
(direction < 0 || (direction == 0 && !lowerMagnitude.IsEven) ? 1 : 0);
|
|
decimal expected = new((int)(uint)(coefficient & uint.MaxValue),
|
|
(int)(uint)((coefficient >> 32) & uint.MaxValue), (int)(uint)(coefficient >> 64), true, 28);
|
|
((decimal)value).ShouldBe(expected);
|
|
((IConvertible)value).ToDecimal(null).ShouldBe(expected);
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(false)]
|
|
[InlineData(true)]
|
|
public void DecimalOutputPreservesZeroSignIncludingUnderflow(bool negative)
|
|
{
|
|
// 2^-95 < 1/(2*10^28), proved by 2*10^28 < 2^95.
|
|
// All nonzero magnitudes below therefore round to coefficient zero.
|
|
(2 * BigInteger.Pow(10, 28) < (BigInteger.One << 95)).ShouldBeTrue();
|
|
foreach (double magnitude in new[] { 0.0, double.Epsilon, Math.ScaleB(1.0, -95) })
|
|
{
|
|
DoubleDouble value = new(negative ? -magnitude : magnitude);
|
|
foreach (decimal result in new[] { (decimal)value, ((IConvertible)value).ToDecimal(null) })
|
|
{
|
|
int[] bits = decimal.GetBits(result);
|
|
bits.ShouldBe(new[] { 0, 0, 0, (28 << 16) | (negative ? int.MinValue : 0) });
|
|
}
|
|
}
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0.0, 0.0, false)]
|
|
[InlineData(1.0, -1.0, false)]
|
|
[InlineData(0.0, double.Epsilon, true)]
|
|
[InlineData(0.0, -double.Epsilon, true)]
|
|
[InlineData(double.NaN, 0.0, true)]
|
|
[InlineData(double.PositiveInfinity, 0.0, true)]
|
|
[InlineData(double.NegativeInfinity, 0.0, true)]
|
|
public void BooleanConversionUsesNormalizedZero(double high, double low, bool expected)
|
|
{
|
|
IConvertible value = DoubleDouble.FromComponents(high, low);
|
|
value.ToBoolean(null).ShouldBe(expected);
|
|
value.ToType(typeof(bool), null).ShouldBe(expected);
|
|
}
|
|
|
|
[Theory]
|
|
[InlineData(0.0)]
|
|
[InlineData(double.Epsilon)]
|
|
[InlineData(-double.Epsilon)]
|
|
[InlineData(double.MaxValue)]
|
|
[InlineData(-double.MaxValue)]
|
|
[InlineData(double.PositiveInfinity)]
|
|
[InlineData(double.NegativeInfinity)]
|
|
[InlineData(double.NaN)]
|
|
public void SingleComponentFloatConversionsMatchBinary64Casts(double high)
|
|
{
|
|
DoubleDouble value = new(high);
|
|
int expectedBits = BitConverter.SingleToInt32Bits((float)high);
|
|
BitConverter.SingleToInt32Bits((float)value).ShouldBe(expectedBits);
|
|
BitConverter.SingleToInt32Bits(((IConvertible)value).ToSingle(null)).ShouldBe(expectedBits);
|
|
}
|
|
|
|
[Fact]
|
|
public void SingleComponentFloatConversionDoesNotAllocate()
|
|
{
|
|
DoubleDouble value = new(1.25);
|
|
float result = 0.0f;
|
|
for (int i = 0; i < 100; ++i)
|
|
{
|
|
result = (float)value;
|
|
}
|
|
long before = GC.GetAllocatedBytesForCurrentThread();
|
|
for (int i = 0; i < 100; ++i)
|
|
{
|
|
result = (float)value;
|
|
}
|
|
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
|
result.ShouldBe(1.25f);
|
|
allocated.ShouldBe(0L);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecimalConstructionPreservesSignedScaledZero()
|
|
{
|
|
decimal zero = new(0, 0, 0, true, 28);
|
|
DoubleDouble value = new(zero);
|
|
BitConverter.DoubleToInt64Bits(value.High).ShouldBe(long.MinValue);
|
|
BitConverter.DoubleToInt64Bits(value.Low).ShouldBe(0L);
|
|
((IConvertible)value).ToBoolean(null).ShouldBeFalse();
|
|
}
|
|
|
|
[Fact]
|
|
public void IntegerInputsRemainExactAcrossBinary64RoundingBoundaries()
|
|
{
|
|
foreach (long input in new[] { 0L, 1L, -1L, long.MinValue, long.MinValue + 1, long.MaxValue - 1, long.MaxValue })
|
|
{
|
|
CheckIntegerInput(input);
|
|
}
|
|
for (int exponent = 53; exponent < 63; ++exponent)
|
|
{
|
|
long center = 1L << exponent;
|
|
long halfUlp = 1L << (exponent - 53);
|
|
foreach (long offset in new[] { -halfUlp - 1, -halfUlp, -halfUlp + 1, halfUlp - 1, halfUlp, halfUlp + 1 })
|
|
{
|
|
CheckIntegerInput(center + offset);
|
|
CheckIntegerInput(-center - offset);
|
|
}
|
|
}
|
|
Random random = new(1729);
|
|
for (int i = 0; i < 250; ++i)
|
|
{
|
|
CheckIntegerInput(random.NextInt64(long.MinValue, long.MaxValue));
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void IntegerConstructionDoesNotAllocate()
|
|
{
|
|
DoubleDouble value = default;
|
|
for (int i = 0; i < 100; ++i)
|
|
{
|
|
value = new DoubleDouble(long.MaxValue);
|
|
}
|
|
long before = GC.GetAllocatedBytesForCurrentThread();
|
|
for (int i = 0; i < 100; ++i)
|
|
{
|
|
value = new DoubleDouble(long.MaxValue);
|
|
}
|
|
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
|
value.High.ShouldBe(Math.ScaleB(1.0, 63));
|
|
value.Low.ShouldBe(-1.0);
|
|
allocated.ShouldBe(0L);
|
|
}
|
|
|
|
private static void CheckIntegerInput(long input)
|
|
{
|
|
foreach (DoubleDouble value in new[] { new DoubleDouble(input), (DoubleDouble)input })
|
|
{
|
|
// Both components of an integer input are integers. BigInteger
|
|
// recombines them exactly without rounding the sum to binary64.
|
|
(new BigInteger(value.High) + new BigInteger(value.Low)).ShouldBe(new BigInteger(input));
|
|
value.High.ShouldBe((double)input);
|
|
((long)value).ShouldBe(input);
|
|
}
|
|
}
|
|
|
|
[Fact]
|
|
public void IntegerInputsPreserveEveryBit()
|
|
{
|
|
DoubleDouble value = (DoubleDouble)9007199254740993L;
|
|
value.High.ShouldBe(9007199254740992.0);
|
|
value.Low.ShouldBe(1.0);
|
|
new DoubleDouble(long.MaxValue).Low.ShouldBe(-1.0);
|
|
new DoubleDouble(long.MinValue).Low.ShouldBe(0.0);
|
|
((DoubleDouble)int.MaxValue).High.ShouldBe(2147483647.0);
|
|
new DoubleDouble(1).High.ShouldBe(1.0);
|
|
}
|
|
|
|
[Fact]
|
|
public void BinaryConversionsRoundOnceUsingBothComponents()
|
|
{
|
|
double midpoint = 1.0 + Math.ScaleB(1.0, -24);
|
|
((float)DoubleDouble.FromComponents(midpoint, Math.ScaleB(1.0, -80))).ShouldBe(MathF.BitIncrement(1.0f));
|
|
((float)DoubleDouble.FromComponents(midpoint, -Math.ScaleB(1.0, -80))).ShouldBe(1.0f);
|
|
((float)new DoubleDouble(midpoint)).ShouldBe(1.0f);
|
|
((float)DoubleDouble.FromComponents(Math.ScaleB(1.0, -150), double.Epsilon)).ShouldBe(float.Epsilon);
|
|
((float)new DoubleDouble(Math.ScaleB(1.0, -150))).ShouldBe(0.0f);
|
|
((float)new DoubleDouble(double.MaxValue)).ShouldBe(float.PositiveInfinity);
|
|
float.IsNaN((float)DoubleDouble.NaN).ShouldBeTrue();
|
|
((double)new DoubleDouble(double.NegativeInfinity)).ShouldBe(double.NegativeInfinity);
|
|
((double)DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -54))).ShouldBe(1.0);
|
|
((DoubleDouble)double.Epsilon).High.ShouldBe(double.Epsilon);
|
|
((DoubleDouble)float.Epsilon).High.ShouldBe(Math.ScaleB(1.0, -149));
|
|
BitConverter.DoubleToInt64Bits((double)(DoubleDouble)(-0.0)).ShouldBe(long.MinValue);
|
|
BitConverter.SingleToInt32Bits((float)(DoubleDouble)(-0.0f)).ShouldBe(int.MinValue);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecimalInputComputesTheExactBinaryResidual()
|
|
{
|
|
DoubleDouble tenth = new(0.1m);
|
|
tenth.High.ShouldBe(0.1);
|
|
// 1/10 - binary64(0.1) = -1/(5 * 2^55), rounded to binary64.
|
|
tenth.Low.ShouldBe(-5.551115123125783e-18);
|
|
DoubleDouble maximum = (DoubleDouble)decimal.MaxValue;
|
|
maximum.High.ShouldBe(Math.ScaleB(1.0, 96));
|
|
maximum.Low.ShouldBe(-1.0);
|
|
((decimal)maximum).ShouldBe(decimal.MaxValue);
|
|
((decimal)new DoubleDouble(decimal.MinValue)).ShouldBe(decimal.MinValue);
|
|
((decimal)new DoubleDouble(0.0000000000000000000000000001m)).ShouldBe(0.0000000000000000000000000001m);
|
|
((decimal)tenth).ShouldBe(0.1m);
|
|
}
|
|
|
|
[Fact]
|
|
public void DecimalOutputRoundsExactSumToNearestEven()
|
|
{
|
|
((decimal)DoubleDouble.FromComponents(1.0, Math.ScaleB(1.0, -54))).ShouldBe(1.0000000000000000555111512313m);
|
|
((decimal)new DoubleDouble(Math.ScaleB(1.0, -29))).ShouldBe(0.0000000018626451492309570312m);
|
|
((decimal)DoubleDouble.FromComponents(Math.ScaleB(1.0, -29), double.Epsilon)).ShouldBe(0.0000000018626451492309570313m);
|
|
((decimal)new DoubleDouble(double.Epsilon)).ShouldBe(0m);
|
|
Should.Throw<OverflowException>(() => (decimal)new DoubleDouble(Math.ScaleB(1.0, 96)));
|
|
Should.Throw<OverflowException>(() => (decimal)DoubleDouble.NaN);
|
|
Should.Throw<OverflowException>(() => (decimal)new DoubleDouble(double.NegativeInfinity));
|
|
}
|
|
|
|
[Fact]
|
|
public void ConvertibleIntegersUseNearestEvenAndCheckAllTargetRanges()
|
|
{
|
|
IConvertible value = DoubleDouble.FromComponents(2.5, double.Epsilon);
|
|
value.ToByte(null).ShouldBe((byte)3);
|
|
value.ToSByte(null).ShouldBe((sbyte)3);
|
|
value.ToInt16(null).ShouldBe((short)3);
|
|
value.ToUInt16(null).ShouldBe((ushort)3);
|
|
value.ToInt32(null).ShouldBe(3);
|
|
value.ToUInt32(null).ShouldBe(3U);
|
|
value.ToInt64(null).ShouldBe(3L);
|
|
value.ToUInt64(null).ShouldBe(3UL);
|
|
((IConvertible)new DoubleDouble(2.5)).ToInt32(null).ShouldBe(2);
|
|
((IConvertible)new DoubleDouble(-2.5)).ToInt32(null).ShouldBe(-2);
|
|
((IConvertible)DoubleDouble.FromComponents(-2.5, -double.Epsilon)).ToInt32(null).ShouldBe(-3);
|
|
((IConvertible)DoubleDouble.FromComponents(Math.ScaleB(1.0, 64), -1.0)).ToUInt64(null).ShouldBe(ulong.MaxValue);
|
|
Should.Throw<OverflowException>(() => ((IConvertible)new DoubleDouble(255.5)).ToByte(null));
|
|
Should.Throw<OverflowException>(() => ((IConvertible)new DoubleDouble(127.5)).ToSByte(null));
|
|
Should.Throw<OverflowException>(() => ((IConvertible)new DoubleDouble(32767.5)).ToInt16(null));
|
|
Should.Throw<OverflowException>(() => ((IConvertible)new DoubleDouble(65535.5)).ToUInt16(null));
|
|
Should.Throw<OverflowException>(() => ((IConvertible)new DoubleDouble(2147483647.5)).ToInt32(null));
|
|
Should.Throw<OverflowException>(() => ((IConvertible)new DoubleDouble(4294967295.5)).ToUInt32(null));
|
|
Should.Throw<OverflowException>(() => ((IConvertible)new DoubleDouble(Math.ScaleB(1.0, 63))).ToInt64(null));
|
|
Should.Throw<OverflowException>(() => ((IConvertible)new DoubleDouble(Math.ScaleB(1.0, 64))).ToUInt64(null));
|
|
Should.Throw<OverflowException>(() => ((IConvertible)new DoubleDouble(-1.0)).ToUInt64(null));
|
|
Should.Throw<OverflowException>(() => ((IConvertible)DoubleDouble.NaN).ToInt32(null));
|
|
}
|
|
|
|
[Fact]
|
|
public void ConvertibleDispatchPreservesTypeAndUsesNumericPolicies()
|
|
{
|
|
IConvertible value = new DoubleDouble(1.25);
|
|
value.GetTypeCode().ShouldBe(TypeCode.Object);
|
|
value.ToBoolean(null).ShouldBeTrue();
|
|
((IConvertible)DoubleDouble.Zero).ToBoolean(null).ShouldBeFalse();
|
|
((IConvertible)DoubleDouble.NaN).ToBoolean(null).ShouldBeTrue();
|
|
value.ToDecimal(null).ShouldBe(1.25m);
|
|
value.ToDouble(null).ShouldBe(1.25);
|
|
value.ToSingle(null).ShouldBe(1.25f);
|
|
value.ToString(System.Globalization.CultureInfo.InvariantCulture).ShouldBe("1.25");
|
|
value.ToType(typeof(DoubleDouble), null).ShouldBe(new DoubleDouble(1.25));
|
|
value.ToType(typeof(object), null).ShouldBe(new DoubleDouble(1.25));
|
|
value.ToType(typeof(int), null).ShouldBe(1);
|
|
value.ToType(typeof(string), System.Globalization.CultureInfo.InvariantCulture).ShouldBe("1.25");
|
|
value.ToType(typeof(decimal), null).ShouldBe(1.25m);
|
|
value.ToType(typeof(double), null).ShouldBe(1.25);
|
|
value.ToType(typeof(float), null).ShouldBe(1.25f);
|
|
value.ToType(typeof(bool), null).ShouldBe(true);
|
|
Should.Throw<InvalidCastException>(() => value.ToChar(null));
|
|
Should.Throw<InvalidCastException>(() => value.ToDateTime(null));
|
|
Should.Throw<InvalidCastException>(() => value.ToType(typeof(Guid), null));
|
|
Should.Throw<InvalidCastException>(() => value.ToType(typeof(DayOfWeek), null));
|
|
Should.Throw<ArgumentNullException>(() => value.ToType(null!, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void ChangeTypeRecognizesDoubleDoubleAndDelegatesNumericTargets()
|
|
{
|
|
// Review-1 §10: Convert.ChangeType(One, typeof(DoubleDouble)) threw
|
|
// InvalidCastException because ToType did not recognize its own type.
|
|
System.Globalization.CultureInfo invariant = System.Globalization.CultureInfo.InvariantCulture;
|
|
Convert.ChangeType(DoubleDouble.One, typeof(DoubleDouble), invariant).ShouldBe(DoubleDouble.One);
|
|
Convert.ChangeType(new DoubleDouble(1.25), typeof(int), invariant).ShouldBe(1);
|
|
Convert.ChangeType(new DoubleDouble(1.25), typeof(double), invariant).ShouldBe(1.25);
|
|
Convert.ChangeType(new DoubleDouble(1.25), typeof(string), invariant).ShouldBe("1.25");
|
|
Should.Throw<InvalidCastException>(() => Convert.ChangeType(DoubleDouble.One, typeof(Guid), invariant));
|
|
}
|
|
|
|
[Fact]
|
|
public void ExplicitIntegersTruncateTheCompleteExpansion()
|
|
{
|
|
((int)DoubleDouble.FromComponents(1.0, -1e-30)).ShouldBe(0);
|
|
((int)DoubleDouble.FromComponents(-1.0, 1e-30)).ShouldBe(0);
|
|
((long)DoubleDouble.FromComponents(9007199254740992.0, 1.0)).ShouldBe(9007199254740993L);
|
|
((long)DoubleDouble.FromComponents(9223372036854775808.0, -1.0)).ShouldBe(long.MaxValue);
|
|
((long)new DoubleDouble(-9223372036854775808.0)).ShouldBe(long.MinValue);
|
|
((int)DoubleDouble.FromComponents(2147483648.0, -0.25)).ShouldBe(int.MaxValue);
|
|
Should.Throw<OverflowException>(() => (int)new DoubleDouble(2147483648.0));
|
|
Should.Throw<OverflowException>(() => (long)new DoubleDouble(9223372036854775808.0));
|
|
Should.Throw<OverflowException>(() => (int)DoubleDouble.NaN);
|
|
Should.Throw<OverflowException>(() => (long)new DoubleDouble(double.PositiveInfinity));
|
|
}
|
|
}
|