diff --git a/src/MissingValues/Int256.Conversions.cs b/src/MissingValues/Int256.Conversions.cs index f995ed7..5ace6c6 100644 --- a/src/MissingValues/Int256.Conversions.cs +++ b/src/MissingValues/Int256.Conversions.cs @@ -645,6 +645,34 @@ public static explicit operator decimal(in Int256 value) } return (decimal)(double)(UInt256)(value); } + +#if NET11_0_OR_GREATER + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal32(in Int256 value) + { + return BitHelper.ConvertToDecimalN(in value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal64(in Int256 value) + { + return BitHelper.ConvertToDecimalN(in value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal128(in Int256 value) + { + return BitHelper.ConvertToDecimalN(in value); + } +#endif + /// /// Explicitly converts a value to a . /// @@ -872,6 +900,63 @@ public static explicit operator checked Int256(BigInteger value) /// The value to convert. /// is outside the range of . public static explicit operator checked Int256(decimal value) => checked((Int256)(double)value); + +#if NET11_0_OR_GREATER + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Int256(Decimal32 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked Int256(Decimal32 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Int256(Decimal64 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked Int256(Decimal64 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Int256(Decimal128 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked Int256(Decimal128 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } +#endif + /// /// Explicitly converts a value to a . /// diff --git a/src/MissingValues/Int512.Conversions.cs b/src/MissingValues/Int512.Conversions.cs index e24c830..c5f829a 100644 --- a/src/MissingValues/Int512.Conversions.cs +++ b/src/MissingValues/Int512.Conversions.cs @@ -658,6 +658,34 @@ public static explicit operator decimal(in Int512 value) } return (decimal)(UInt512)(value); } + +#if NET11_0_OR_GREATER + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal32(in Int512 value) + { + return BitHelper.ConvertToDecimalN(in value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal64(in Int512 value) + { + return BitHelper.ConvertToDecimalN(in value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal128(in Int512 value) + { + return BitHelper.ConvertToDecimalN(in value); + } +#endif + /// /// Explicitly converts a value to a . /// @@ -894,6 +922,63 @@ public static explicit operator checked Int512(BigInteger value) /// The value to convert. /// is outside the range of . public static explicit operator checked Int512(decimal value) => checked((Int512)(double)value); + +#if NET11_0_OR_GREATER + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Int512(Decimal32 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked Int512(Decimal32 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Int512(Decimal64 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked Int512(Decimal64 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Int512(Decimal128 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked Int512(Decimal128 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } +#endif + /// /// Explicitly converts a value to a . /// diff --git a/src/MissingValues/Internals/BitHelper.Ieee.cs b/src/MissingValues/Internals/BitHelper.Ieee.cs index 5489ae4..c673c09 100644 --- a/src/MissingValues/Internals/BitHelper.Ieee.cs +++ b/src/MissingValues/Internals/BitHelper.Ieee.cs @@ -1,4 +1,5 @@ -using System.Diagnostics; +using System.Buffers.Binary; +using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; using MissingValues.Primitives; @@ -767,6 +768,141 @@ internal static void FastFloor(in Quad x, out Quad ai, out Quad ar) ar = x - ai; } +#if NET11_0_OR_GREATER + private static void GetExponentAndSignificand(T value, out int exponent, out UInt512 significand) + where T : IDecimalFloatingPointIeee754 + { + Span exponentBuffer = stackalloc byte[8]; + Span significandBuffer = stackalloc byte[64]; + + value.TryWriteExponentLittleEndian(exponentBuffer, out int written); + Debug.Assert(written <= 8); + value.TryWriteSignificandLittleEndian(significandBuffer, out written); + Debug.Assert(written <= 64); + + exponent = BinaryPrimitives.ReadInt32LittleEndian(exponentBuffer); + significand = BinaryOperations.ReadUInt512LittleEndian(significandBuffer); + } + + internal static TInteger ConvertFromDecimalN(TDecimal value, bool isChecked = false) + where TInteger : IBigInteger, IMinMaxValue + where TDecimal : IDecimalFloatingPointIeee754 + { + if (TDecimal.IsNaN(value)) + { + if (isChecked) + { + Thrower.IntegerOverflow(); + } + return TInteger.Zero; + } + + bool isNegative = TDecimal.IsNegative(value); + + if (TDecimal.IsInfinity(value)) + { + if (isChecked) + { + Thrower.IntegerOverflow(); + } + return isNegative ? TInteger.MinValue : TInteger.MaxValue; + } + + GetExponentAndSignificand(value, out var exponent, out var significand); + + if (significand == UInt512.Zero) + { + return TInteger.Zero; + } + + UInt512 magnitude; + bool exceedsUInt512 = false; + if (exponent >= 0) + { + // magnitude = significand * 10^exponent. UInt512 holds at most 155 digits, so any exponent that + // would push the product past that bound overflows every integer type and saturates/throws. + UInt512 maxValueBy10 = UInt512.MaxValue / 10; + for (int i = 0; i < exponent && !exceedsUInt512; i++) + { + if (significand > maxValueBy10) + { + exceedsUInt512 = true; + break; + } + significand *= 10; + } + + magnitude = exceedsUInt512 ? UInt512.Zero : significand; + } + else + { + // magnitude = significand / 10^(-exponent), truncated toward zero. Once the divisor has more + // digits than the significand the quotient is zero. + int drop = -exponent; + + for (int i = 0; i < drop && significand != UInt512.Zero; i++) + { + significand /= 10; + } + + magnitude = significand; + } + + bool isUnsigned = TInteger.IsZero(TInteger.MinValue); + UInt512 maxMagnitude = UInt512.CreateTruncating(TInteger.MaxValue); + + UInt512 minMagnitude = isUnsigned ? UInt512.Zero : maxMagnitude + UInt512.One; + + if (!isNegative) + { + if (exceedsUInt512 || (magnitude > maxMagnitude)) + { + if (isChecked) + { + Thrower.IntegerOverflow(); + } + return TInteger.MaxValue; + } + + return TInteger.CreateTruncating(magnitude); + } + + // Negative magnitude. + if (isUnsigned) + { + if (isChecked && (magnitude != UInt512.Zero)) + { + Thrower.IntegerOverflow(); + } + return TInteger.Zero; + } + + if (exceedsUInt512 || (magnitude > minMagnitude)) + { + if (isChecked) + { + Thrower.IntegerOverflow(); + } + return TInteger.MinValue; + } + + if (magnitude == minMagnitude) + { + return TInteger.MinValue; + } + + return TInteger.Zero - TInteger.CreateTruncating(magnitude); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static TDecimal ConvertToDecimalN(in TInteger value) + where TDecimal : IDecimalFloatingPointIeee754 + where TInteger : IBigInteger, IMinMaxValue + { + throw new NotImplementedException(); + } +#endif + [MethodImpl(MethodImplOptions.AggressiveInlining)] internal static UInt128 AddQuadBits(UInt128 uiA, UInt128 uiB, bool signZ) { diff --git a/src/MissingValues/UInt256.Conversions.cs b/src/MissingValues/UInt256.Conversions.cs index ab5eb10..fd5ef67 100644 --- a/src/MissingValues/UInt256.Conversions.cs +++ b/src/MissingValues/UInt256.Conversions.cs @@ -604,6 +604,34 @@ public static explicit operator decimal(in UInt256 value) return (decimal)value.Lower; } + +#if NET11_0_OR_GREATER + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal32(in UInt256 value) + { + return BitHelper.ConvertToDecimalN(in value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal64(in UInt256 value) + { + return BitHelper.ConvertToDecimalN(in value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal128(in UInt256 value) + { + return BitHelper.ConvertToDecimalN(in value); + } +#endif + /// /// Explicitly converts a value to a . /// @@ -781,6 +809,63 @@ public static explicit operator checked UInt256(double value) return ToUInt256(value); } + +#if NET11_0_OR_GREATER + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator UInt256(Decimal32 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked UInt256(Decimal32 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator UInt256(Decimal64 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked UInt256(Decimal64 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator UInt256(Decimal128 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked UInt256(Decimal128 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } +#endif + /// /// Explicitly converts a value to a . /// diff --git a/src/MissingValues/UInt512.Conversions.cs b/src/MissingValues/UInt512.Conversions.cs index e571c7f..ceb5816 100644 --- a/src/MissingValues/UInt512.Conversions.cs +++ b/src/MissingValues/UInt512.Conversions.cs @@ -618,6 +618,34 @@ public static explicit operator decimal(in UInt512 value) return (decimal)value.Lower; } + +#if NET11_0_OR_GREATER + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal32(in UInt512 value) + { + return BitHelper.ConvertToDecimalN(in value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal64(in UInt512 value) + { + return BitHelper.ConvertToDecimalN(in value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator Decimal128(in UInt512 value) + { + return BitHelper.ConvertToDecimalN(in value); + } +#endif + /// /// Explicitly converts a value to a . /// @@ -1104,6 +1132,63 @@ public static explicit operator checked UInt512(double value) return ToUInt512(value); } + +#if NET11_0_OR_GREATER + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator UInt512(Decimal32 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked UInt512(Decimal32 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator UInt512(Decimal64 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked UInt512(Decimal64 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } + + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + public static explicit operator UInt512(Decimal128 value) + { + return BitHelper.ConvertFromDecimalN(value); + } + /// + /// Explicitly converts a value to a . + /// + /// The value to convert. + /// is outside the range of . + public static explicit operator checked UInt512(Decimal128 value) + { + return BitHelper.ConvertFromDecimalN(value, true); + } +#endif + /// /// Explicitly converts a value to a . /// diff --git a/tests/MissingValues.Tests/Extensions/OctoExtensions.cs b/tests/MissingValues.Tests/Extensions/OctoExtensions.cs index fa7c327..2990d86 100644 --- a/tests/MissingValues.Tests/Extensions/OctoExtensions.cs +++ b/tests/MissingValues.Tests/Extensions/OctoExtensions.cs @@ -65,6 +65,13 @@ public static class OctoExtensions public static Octo NFloatMinValue => NFloat.Size == 8 ? Octo.DoubleMinValue : Octo.SingleMinValue; public static Octo QuadMaxValue => Values.CreateFloat(0x43FF_EFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF, 0xF000_0000_0000_0000, 0x0000_0000_0000_0000); public static Octo QuadMinValue => Values.CreateFloat(0xC3FF_EFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF, 0xF000_0000_0000_0000, 0x0000_0000_0000_0000); + + public static Octo Decimal32MaxValue => Values.CreateFloat(0x4017_65C0_1040_09E0, 0x12E0_0300_08A0_0380, 0x1AED_6792_CCAD_29A9, 0xD27D_9160_3B50_BF5D); + public static Octo Decimal32MinValue => Values.CreateFloat(0xC017_65C0_1040_09E0, 0x12E0_0300_08A0_0380, 0x1AED_6792_CCAD_29A9, 0xD27D_9160_3B50_BF5D); + public static Octo Decimal64MaxValue => Values.CreateFloat(0x404F_DEBE_EB7A_9B56, 0xD9B6_0E91_DC03_AB30, 0xAFC7_EA72_4341_EA33, 0x2994_DBDE_1B58_8CA4); + public static Octo Decimal64MinValue => Values.CreateFloat(0xC04F_DEBE_EB7A_9B56, 0xD9B6_0E91_DC03_AB30, 0xAFC7_EA72_4341_EA33, 0x2994_DBDE_1B58_8CA4); + public static Octo Decimal128MaxValue => Values.CreateFloat(0x44FF_4300_0080_3A00, 0x1200_2900_0880_6B80, 0x4490_2FF5_3A96_BC5, 0xC2D6A_4594_3117_1A7); + public static Octo Decimal128MinValue => Values.CreateFloat(0xC4FF_4300_0080_3A00, 0x1200_2900_0880_6B80, 0x4490_2FF5_3A96_BC5, 0xC2D6A_4594_3117_1A7); public static Octo Delta => Values.CreateFloat(0x400E_B000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000, 0x0000_0000_0000_0000); } diff --git a/tests/MissingValues.Tests/Extensions/QuadExtensions.cs b/tests/MissingValues.Tests/Extensions/QuadExtensions.cs index bc46840..9b0047f 100644 --- a/tests/MissingValues.Tests/Extensions/QuadExtensions.cs +++ b/tests/MissingValues.Tests/Extensions/QuadExtensions.cs @@ -64,6 +64,11 @@ public static class QuadExtensions public static Quad DoubleMinValue => Values.CreateFloat(0xC3FE_FFFF_FFFF_FFFF, 0xF000_0000_0000_0000); public static Quad NFloatMaxValue => NFloat.Size == 8 ? Quad.DoubleMaxValue : Quad.SingleMaxValue; public static Quad NFloatMinValue => NFloat.Size == 8 ? Quad.DoubleMinValue : Quad.SingleMinValue; + + public static Quad Decimal32MaxValue => Values.CreateFloat(0x4141_2BA0_93E5_C611, 0x4735_DACF_2599_5A53); + public static Quad Decimal32MinValue => Values.CreateFloat(0xC141_2BA0_93E5_C611, 0x4735_DACF_2599_5A53); + public static Quad Decimal64MaxValue => Values.CreateFloat(0x44FD_EBEE_B7A9_B56D, 0x9B60_E91D_C03A_B30B); + public static Quad Decimal64MinValue => Values.CreateFloat(0xC4FD_EBEE_B7A9_B56D, 0x9B60_E91D_C03A_B30B); public static Quad Delta => Values.CreateFloat(0x406F_0000_0000_0000, 0x0000_0000_0000_0000); }