diff --git a/src/libraries/Common/src/System/Number.NumberBuffer.cs b/src/libraries/Common/src/System/Number.NumberBuffer.cs
index 9350f15677d5c0..90a60fc1e8f616 100644
--- a/src/libraries/Common/src/System/Number.NumberBuffer.cs
+++ b/src/libraries/Common/src/System/Number.NumberBuffer.cs
@@ -21,6 +21,9 @@ internal static partial class Number
internal const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295
internal const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615
internal const int UInt128NumberBufferLength = 39 + 1; // 39 for the longest input: 340,282,366,920,938,463,463,374,607,431,768,211,455
+ internal const int Decimal32NumberBufferLength = 7 + 1 + 1; // 7 for the longest input + 1 for rounding
+ internal const int Decimal64NumberBufferLength = 16 + 1 + 1; // 16 for the longest input + 1 for rounding
+ internal const int Decimal128NumberBufferLength = 34 + 1 + 1; // 34 for the longest input + 1 for rounding
internal unsafe ref struct NumberBuffer
{
diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
index ac51112ce4dbd1..8f5ccf8dd12c59 100644
--- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
+++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
@@ -537,6 +537,15 @@
Object must be of type Decimal.
+
+ Object must be of type Decimal32.
+
+
+ Object must be of type Decimal64.
+
+
+ Object must be of type Decimal128.
+
Type must derive from Delegate.
diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
index 1874075bdabba7..11034888ff9d6a 100644
--- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
+++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems
@@ -441,6 +441,10 @@
+
+
+
+
diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs b/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs
index 4c5fd32227de25..c1a031abfd5dd8 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs
@@ -1352,6 +1352,31 @@ public readonly ulong ToUInt64()
return 0;
}
+ public UInt128 ToUInt128()
+ {
+ if (_length > 3)
+ {
+ return new UInt128(((ulong)_blocks[3] << 32) + _blocks[2], ((ulong)(_blocks[1]) << 32) + _blocks[0]);
+ }
+
+ if (_length > 2)
+ {
+ return new UInt128((ulong)_blocks[2], ((ulong)_blocks[1] << 32) + _blocks[0]);
+ }
+
+ if (_length > 1)
+ {
+ return ((ulong)(_blocks[1]) << 32) + _blocks[0];
+ }
+
+ if (_length > 0)
+ {
+ return _blocks[0];
+ }
+
+ return 0;
+ }
+
private void Clear(int length) => ((Span)_blocks).Slice(0, length).Clear();
private static int DivRem32(int value, out int remainder)
diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs
new file mode 100644
index 00000000000000..a93f78c10d0003
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs
@@ -0,0 +1,593 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Diagnostics;
+using System.Numerics;
+
+namespace System
+{
+ internal static partial class Number
+ {
+ ///
+ /// Encodes the given IEEE 754 decimal components into their Binary Integer Decimal (BID),
+ /// handles rounding/infinite cases, producing the final bit pattern.
+ ///
+ ///
+ /// The sign of the value. true indicates a negative number; otherwise, false.
+ ///
+ ///
+ /// The fully decoded significand (coefficient):
+ /// - This is the complete integer coefficient with no packed BID encoding.
+ /// - It includes all significant digits (non-trailing).
+ /// - It has not been scaled by the exponent.
+ /// For example:
+ /// 123.45 → significand = 12345, exponent = -2
+ /// The value is interpreted as:
+ /// (-1)^sign × significand × 10^exponent
+ ///
+ ///
+ /// The unbiased exponent (actual exponent as defined by IEEE 754).
+ /// This value has already been adjusted by subtracting the format's exponent bias,
+ /// and will be re-biased internally when constructing the BID bit pattern.
+ ///
+ ///
+ /// The 32-bit or 64-bit or 128-bit IEEE 754 decimal BID encoding (depending on ),
+ /// containing the sign bit, combination field, biased exponent, and coefficient continuation bits.
+ ///
+ internal static TValue ConstructorToDecimalIeee754Bits(bool signed, TValue significand, int exponent)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ if (significand == TValue.Zero)
+ {
+ return DecimalIeee754FiniteNumberBinaryEncoding(signed, TValue.Zero, exponent);
+ }
+
+ if (exponent > TDecimal.MaxExponent)
+ {
+ return signed ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity;
+ }
+
+ if (exponent > TDecimal.MaxAdjustedExponent)
+ {
+ return ClampExponentOverflow(signed, significand, exponent);
+ }
+
+ if (exponent < TDecimal.MinAdjustedExponent)
+ {
+ return ClampExponentUnderflow(signed, significand, exponent);
+ }
+
+ if (significand > TDecimal.MaxSignificand)
+ {
+ int numberDigits = TDecimal.CountDigits(significand);
+ int numberDigitsRemove = numberDigits - TDecimal.Precision;
+ if (exponent + numberDigitsRemove > TDecimal.MaxAdjustedExponent)
+ {
+ return signed ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity;
+ }
+ return RemoveDigitsAndRoundHalfToEven(signed, significand, exponent, numberDigitsRemove);
+ }
+
+ return DecimalIeee754FiniteNumberBinaryEncoding(signed, significand, exponent);
+
+ static TValue ClampExponentOverflow(bool signed, TValue significand, int exponent)
+ {
+ Debug.Assert(exponent > TDecimal.MaxAdjustedExponent);
+
+ int numberDigits = TDecimal.CountDigits(significand);
+
+ int numberZeroDigits = exponent - TDecimal.MaxAdjustedExponent;
+
+ if (numberDigits + numberZeroDigits > TDecimal.Precision)
+ {
+ return signed ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity;
+ }
+ else
+ {
+ exponent -= numberZeroDigits;
+ significand *= TDecimal.Power10(numberZeroDigits);
+ }
+
+ return DecimalIeee754FiniteNumberBinaryEncoding(signed, significand, exponent);
+ }
+
+ static TValue ClampExponentUnderflow(bool signed, TValue significand, int exponent)
+ {
+ Debug.Assert(exponent < TDecimal.MinAdjustedExponent);
+
+ int numberDigits = TDecimal.CountDigits(significand);
+
+ int numberDigitsRemove = TDecimal.MinAdjustedExponent - exponent;
+
+ if (numberDigitsRemove >= numberDigits)
+ {
+ return DecimalIeee754FiniteNumberBinaryEncoding(signed, TValue.Zero, TDecimal.MinAdjustedExponent);
+ }
+
+ int numberDigitsRemain = numberDigits - numberDigitsRemove;
+
+ return numberDigitsRemain > TDecimal.Precision
+ ? RemoveDigitsAndRoundHalfToEven(signed, significand, exponent, numberDigitsRemove + (numberDigitsRemain - TDecimal.Precision))
+ : RemoveDigitsAndRoundHalfToEven(signed, significand, exponent, numberDigitsRemove);
+ }
+
+ static TValue RemoveDigitsAndRoundHalfToEven(bool signed, TValue significand, int exponent, int numberDigitsRemove)
+ {
+ exponent += numberDigitsRemove;
+ (significand, TValue remainder) = TDecimal.DivRemPow10(significand, numberDigitsRemove);
+
+ Debug.Assert(significand <= TDecimal.MaxSignificand);
+ if (remainder == TValue.Zero)
+ {
+ return DecimalIeee754FiniteNumberBinaryEncoding(signed, significand, exponent);
+ }
+
+ TValue half = TValue.CreateTruncating(5) * TDecimal.Power10(numberDigitsRemove - 1);
+
+ if (remainder < half || (remainder == half && TValue.IsEvenInteger(significand)))
+ {
+ // round down
+ return DecimalIeee754FiniteNumberBinaryEncoding(signed, significand, exponent);
+ }
+
+ if (significand == TDecimal.MaxSignificand)
+ {
+ exponent += 1;
+ if (exponent > TDecimal.MaxAdjustedExponent)
+ {
+ return signed ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity;
+ }
+ significand = TDecimal.Power10(TDecimal.Precision - 1);
+ return DecimalIeee754FiniteNumberBinaryEncoding(signed, significand, exponent);
+ }
+
+ significand += TValue.One;
+ return DecimalIeee754FiniteNumberBinaryEncoding(signed, significand, exponent);
+ }
+ }
+
+ internal static int GetDecimalIeee754HashCode(TValue decimalBits)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ if (TDecimal.IsNaN(decimalBits) || TDecimal.IsInfinity(decimalBits))
+ {
+ return decimalBits.GetHashCode();
+ }
+
+ DecodedDecimalIeee754 decoded = UnpackDecimalIeee754(decimalBits);
+ if (decoded.Significand == TValue.Zero)
+ {
+ return TDecimal.Zero.GetHashCode();
+ }
+
+ int digits = TDecimal.CountDigits(decoded.Significand);
+ if (digits < TDecimal.Precision)
+ {
+ int numberZeroDigits = TDecimal.Precision - digits;
+ TValue significand = decoded.Significand * TDecimal.Power10(numberZeroDigits);
+ int exponent = decoded.UnbiasedExponent - numberZeroDigits;
+ return HashCode.Combine(decoded.Signed, significand, exponent);
+ }
+ return HashCode.Combine(decoded.Signed, decoded.Significand, decoded.UnbiasedExponent);
+ }
+
+ internal struct DecodedDecimalIeee754
+ where TSignificand : IBinaryInteger
+ {
+ public bool Signed { get; }
+ public int UnbiasedExponent { get; }
+
+ ///
+ /// The decoded significand (coefficient) in integer form:
+ /// - Fully decoded from the BID encoding (no combination-field or DPD/BID packing).
+ /// - Represents the normalized coefficient; includes the implicit leading digit if applicable.
+ /// - Not scaled by the (unbiased) exponent.
+ ///
+ public TSignificand Significand { get; }
+
+ public DecodedDecimalIeee754(bool signed, int unbiasedExponent, TSignificand significand)
+ {
+ Signed = signed;
+ UnbiasedExponent = unbiasedExponent;
+ Significand = significand;
+ }
+ }
+
+ internal static DecodedDecimalIeee754 UnpackDecimalIeee754(TValue decimalBits)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ bool signed = (decimalBits & TDecimal.SignMask) != TValue.Zero;
+ TValue significand;
+ int biasedExponent;
+
+ if ((decimalBits & TDecimal.G0G1Mask) == TDecimal.G0G1Mask)
+ {
+ biasedExponent = TDecimal.ConvertToExponent((decimalBits & TDecimal.G2ToGwPlus3ExponentMask) >> (TDecimal.NumberBitsSignificand + 1));
+ significand = (decimalBits & TDecimal.GwPlus4SignificandMask) | TDecimal.MostSignificantBitOfSignificandMask;
+ }
+ else
+ {
+ biasedExponent = TDecimal.ConvertToExponent((decimalBits & TDecimal.G0ToGwPlus1ExponentMask) >> (TDecimal.NumberBitsSignificand + 3));
+ significand = decimalBits & TDecimal.GwPlus2ToGwPlus4SignificandMask;
+ }
+
+ return new DecodedDecimalIeee754(signed, biasedExponent - TDecimal.ExponentBias, significand);
+ }
+
+ ///
+ /// Compares two IEEE 754 decimal values represented by their raw bit patterns.
+ ///
+ ///
+ /// The implementation first handles special cases so the result stays consistent
+ /// with the .NET equality and ordering contract required by Equals,
+ /// GetHashCode, and CompareTo:
+ /// - identical bit patterns compare equal;
+ /// - NaN compares equal to NaN;
+ /// - infinities are handled explicitly;
+ /// - positive and negative zero compare equal.
+ /// After special-case handling, finite non-zero values are unpacked and compared
+ /// by sign first, then by unsigned magnitude.
+ /// Treating NaN as equal to NaN here is intentional so values that are considered
+ /// equal for comparison also behave consistently in hashing, collections, and
+ /// sorting scenarios.
+ ///
+ internal static int CompareDecimalIeee754(TValue currentDecimalBits, TValue otherDecimalBits)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ if (currentDecimalBits == otherDecimalBits)
+ {
+ return 0;
+ }
+
+ bool isCurrentNaN = TDecimal.IsNaN(currentDecimalBits);
+ bool isOtherNaN = TDecimal.IsNaN(otherDecimalBits);
+
+ if (isCurrentNaN || isOtherNaN)
+ {
+ if (isCurrentNaN && isOtherNaN)
+ {
+ return 0;
+ }
+ else
+ {
+ return isCurrentNaN ? -1 : 1;
+ }
+ }
+
+ if (TDecimal.IsInfinity(currentDecimalBits) || TDecimal.IsInfinity(otherDecimalBits))
+ {
+ return InternalInfinityCompare(currentDecimalBits, otherDecimalBits);
+ }
+
+ DecodedDecimalIeee754 current = UnpackDecimalIeee754(currentDecimalBits);
+ DecodedDecimalIeee754 other = UnpackDecimalIeee754(otherDecimalBits);
+
+ if (current.Significand == TValue.Zero && other.Significand == TValue.Zero)
+ {
+ return 0;
+ }
+
+ if (current.Signed)
+ {
+ if (!other.Signed)
+ {
+ return -1;
+ }
+ }
+ else if (other.Signed)
+ {
+ return 1;
+ }
+
+ int result = InternalUnsignedCompare(current, other);
+ return current.Signed ? -result : result;
+
+ // This method is needed to correctly compare decimals that represent the same numeric value
+ // but have different exponent/significand pairs. For example, 10e2 and 1e3 have different exponents,
+ // but represent the same number (1000). This function normalizes exponents and compares them accordingly,
+ // without considering sign.
+ static int InternalUnsignedCompare(DecodedDecimalIeee754 current, DecodedDecimalIeee754 other)
+ {
+ if (current.Significand == TValue.Zero && other.Significand == TValue.Zero)
+ {
+ return 0;
+ }
+
+ if (current.UnbiasedExponent == other.UnbiasedExponent && current.Significand == other.Significand)
+ {
+ return 0;
+ }
+
+ if (current.UnbiasedExponent < other.UnbiasedExponent)
+ {
+ return -InternalUnsignedCompare(other, current);
+ }
+
+ if (current.Significand >= other.Significand)
+ {
+ return 1;
+ }
+
+ int diffExponent = current.UnbiasedExponent - other.UnbiasedExponent;
+ if (diffExponent < TDecimal.Precision)
+ {
+ TValue factor = TDecimal.Power10(diffExponent);
+ (TValue quotient, TValue remainder) = TValue.DivRem(other.Significand, current.Significand);
+
+ if (quotient < factor)
+ {
+ return 1;
+ }
+ if (quotient > factor)
+ {
+ return -1;
+ }
+ if (remainder > TValue.Zero)
+ {
+ return -1;
+ }
+ return 0;
+ }
+
+ return 1;
+ }
+
+ static int InternalInfinityCompare(TValue current, TValue other)
+ {
+ if (current == TDecimal.PositiveInfinity)
+ {
+ return other == TDecimal.PositiveInfinity ? 0 : 1;
+ }
+ else if (current == TDecimal.NegativeInfinity)
+ {
+ return other == TDecimal.NegativeInfinity ? 0 : -1;
+ }
+
+ return other == TDecimal.PositiveInfinity ? -1 : 1;
+ }
+ }
+
+ private static TValue NumberToDecimalIeee754Bits(ref NumberBuffer number)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ if (number.DigitsCount == 0)
+ {
+ return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, TValue.Zero, number.Scale);
+ }
+
+ Debug.Assert(number.Digits[0] != '0');
+ Debug.Assert(number.DigitsCount != 0);
+
+ int positiveExponent = Math.Max(0, number.Scale);
+ int integerDigitsPresent = Math.Min(positiveExponent, number.DigitsCount);
+ int fractionalDigitsPresent = number.DigitsCount - integerDigitsPresent;
+ int exponent = number.Scale - integerDigitsPresent - fractionalDigitsPresent;
+
+ if (exponent > TDecimal.MaxExponent)
+ {
+ return number.IsNegative ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity;
+ }
+
+ if (exponent > TDecimal.MaxAdjustedExponent)
+ {
+ return ClampExponentOverflow(ref number, exponent);
+ }
+
+ if (exponent < TDecimal.MinAdjustedExponent)
+ {
+ return ClampExponentUnderflow(ref number, exponent);
+ }
+
+ if (number.DigitsCount > TDecimal.Precision)
+ {
+ int numberDigitsRemove = number.DigitsCount - TDecimal.Precision;
+ if (exponent + numberDigitsRemove > TDecimal.MaxAdjustedExponent)
+ {
+ return number.IsNegative ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity;
+ }
+ return DecimalIeee754Rounding(ref number, TDecimal.Precision);
+ }
+
+ TValue significand = TDecimal.NumberToSignificand(ref number, number.DigitsCount);
+
+ return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, significand, exponent);
+
+ static TValue ClampExponentOverflow(ref NumberBuffer number, int exponent)
+ {
+ Debug.Assert(exponent > TDecimal.MaxAdjustedExponent);
+
+ int numberDigits = number.DigitsCount;
+
+ int numberZeroDigits = exponent - TDecimal.MaxAdjustedExponent;
+
+ if (numberDigits + numberZeroDigits > TDecimal.Precision)
+ {
+ return number.IsNegative ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity;
+ }
+
+ for (int i = numberDigits; i < numberDigits + numberZeroDigits; i++)
+ {
+ number.Digits[i] = (byte)'0';
+ }
+ number.DigitsCount += numberZeroDigits;
+ number.Scale -= numberZeroDigits;
+
+ number.CheckConsistency();
+
+ TValue significand = TDecimal.NumberToSignificand(ref number, number.DigitsCount);
+
+ return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, significand, exponent);
+ }
+
+ static TValue ClampExponentUnderflow(ref NumberBuffer number, int exponent)
+ {
+ Debug.Assert(exponent < TDecimal.MinAdjustedExponent);
+ int numberDigitsRemove = TDecimal.MinAdjustedExponent - exponent;
+ if (numberDigitsRemove >= number.DigitsCount)
+ {
+ return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, TValue.Zero, TDecimal.MinAdjustedExponent);
+ }
+ int numberDigitsRemain = number.DigitsCount - numberDigitsRemove;
+ Debug.Assert(numberDigitsRemain <= TDecimal.Precision);
+ return DecimalIeee754Rounding(ref number, numberDigitsRemain);
+ }
+ }
+
+ ///
+ /// Encodes the given IEEE 754 decimal components into their Binary Integer Decimal (BID),
+ /// producing the final bit pattern.
+ ///
+ ///
+ /// The sign of the value. true indicates a negative number; otherwise, false.
+ ///
+ ///
+ /// The fully decoded significand (coefficient):
+ /// - This is the complete integer coefficient with no packed BID encoding.
+ /// - It includes all significant digits (non-trailing).
+ /// - It has not been scaled by the exponent.
+ ///
+ ///
+ /// The unbiased exponent (actual exponent as defined by IEEE 754).
+ /// This value has already been adjusted by subtracting the format's exponent bias,
+ /// and will be re-biased internally when constructing the BID bit pattern.
+ ///
+ ///
+ /// The 32-bit or 64-bit or 128-bit IEEE 754 decimal BID encoding (depending on ),
+ /// containing the sign bit, combination field, biased exponent, and coefficient continuation bits.
+ ///
+ private static TValue DecimalIeee754FiniteNumberBinaryEncoding(bool signed, TValue significand, int exponent)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ Debug.Assert(significand <= TDecimal.MaxSignificand);
+
+ if (TValue.IsZero(significand))
+ {
+ if (exponent < TDecimal.MinAdjustedExponent)
+ {
+ exponent = TDecimal.MinAdjustedExponent;
+ }
+ else if (exponent > TDecimal.MaxExponent)
+ {
+ exponent = TDecimal.MaxExponent;
+ }
+ }
+
+ uint biasedExponent = (uint)(exponent + TDecimal.ExponentBias);
+
+ TValue value = TValue.Zero;
+ bool msbSignificand = (significand & TDecimal.MostSignificantBitOfSignificandMask) != TValue.Zero;
+
+ if (signed)
+ {
+ value = TDecimal.SignMask;
+ }
+
+ if (msbSignificand)
+ {
+ value |= TDecimal.G0G1Mask;
+ value |= TDecimal.EncodeExponentToG2ThroughGwPlus3(biasedExponent);
+ significand ^= TDecimal.MostSignificantBitOfSignificandMask;
+ value |= significand;
+ }
+ else
+ {
+ value |= TDecimal.EncodeExponentToG0ThroughGwPlus1(biasedExponent);
+ value |= significand;
+ }
+
+ return value;
+ }
+
+ ///
+ /// Performs IEEE 754-compliant rounding on a decimal-like number before converting it
+ /// to an IEEE 754 decimal32/64/128 encoded value.
+ ///
+ /// ---------------------------------------------------------------
+ /// ROUNDING DECISION (implements round-to-nearest, ties-to-even)
+ ///
+ /// Unit In The Last Place (ULP) formula: ULP = 10^(unbiased exponent - number digits precision + 1)
+ /// The difference between the unrounded number and the rounded
+ /// representable value is effectively compared against ±ULP/2.
+ ///
+ /// If discarded part > 0.5 ULP → round up
+ /// If discarded part < 0.5 ULP → round down
+ /// If exactly 0.5 ULP → ties-to-even
+ ///
+ private static TValue DecimalIeee754Rounding(ref NumberBuffer number, int digits)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ Debug.Assert(digits < number.DigitsCount);
+ Debug.Assert(digits <= TDecimal.Precision);
+
+ TValue significand = TDecimal.NumberToSignificand(ref number, digits);
+
+ Debug.Assert(significand <= TDecimal.MaxSignificand);
+
+ int positiveExponent = (Math.Max(0, number.Scale));
+ int integerDigitsPresent = Math.Min(positiveExponent, number.DigitsCount);
+ int fractionalDigitsPresent = number.DigitsCount - integerDigitsPresent;
+ int exponent = number.Scale - integerDigitsPresent - fractionalDigitsPresent;
+ exponent += number.DigitsCount - digits;
+
+ bool roundDown = true;
+ int midPointValue = number.Digits[digits];
+
+ if (midPointValue > '5')
+ {
+ roundDown = false;
+ }
+ else if (midPointValue == '5')
+ {
+ int index = digits + 1;
+ int c = number.Digits[index];
+ bool tiedToEvenRounding = true;
+
+ while (index < number.DigitsCount && c != 0)
+ {
+ if (c != '0')
+ {
+ roundDown = false;
+ tiedToEvenRounding = false;
+ break;
+ }
+ ++index;
+ c = number.Digits[index];
+ }
+
+ if (tiedToEvenRounding && int.IsOddInteger(number.Digits[digits - 1] - '0'))
+ {
+ roundDown = false;
+ }
+ }
+
+ if (roundDown)
+ {
+ return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, significand, exponent);
+ }
+
+ if (significand == TDecimal.MaxSignificand)
+ {
+ significand = TDecimal.Power10(TDecimal.Precision - 1);
+ exponent += 1;
+
+ if (exponent > TDecimal.MaxAdjustedExponent)
+ {
+ return number.IsNegative ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity;
+ }
+
+ return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, significand, exponent);
+ }
+
+ significand += TValue.One;
+
+ return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, significand, exponent);
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs
index 141b946d8af147..3b4148cecc2a51 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs
@@ -1,6 +1,7 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
+using System.Buffers;
using System.Buffers.Text;
using System.Collections.Generic;
using System.Diagnostics;
@@ -330,6 +331,66 @@ private static ref byte GetTwoDigitsBytesRef(bool useChars) =>
ref MemoryMarshal.GetReference(useChars ? TwoDigitsCharsAsBytes : TwoDigitsBytes);
#endif
+ internal static string FormatDecimalIeee754(TValue value, string? format, NumberFormatInfo info)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ var vlb = new ValueListBuilder(stackalloc char[CharStackBufferSize]);
+ string result = FormatDecimalIeee754(ref vlb, value, format, info) ?? vlb.AsSpan().ToString();
+ vlb.Dispose();
+ return result;
+ }
+
+ private static unsafe string? FormatDecimalIeee754(ref ValueListBuilder vlb, TValue value, ReadOnlySpan format, NumberFormatInfo info)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ where TChar : unmanaged, IUtfChar
+ {
+ Debug.Assert(typeof(TChar) == typeof(char) || typeof(TChar) == typeof(byte));
+
+ if (!TDecimal.IsFinite(value))
+ {
+ if (TDecimal.IsNaN(value))
+ {
+ if (typeof(TChar) == typeof(char))
+ {
+ return info.NaNSymbol;
+ }
+ else
+ {
+ vlb.Append(info.NaNSymbolTChar());
+ return null;
+ }
+ }
+
+ if (typeof(TChar) == typeof(char))
+ {
+ return TDecimal.IsNegative(value) ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
+ }
+ else
+ {
+ vlb.Append(TDecimal.IsNegative(value) ? info.NegativeInfinitySymbolTChar() : info.PositiveInfinitySymbolTChar());
+ return null;
+ }
+ }
+ char fmt = ParseFormatSpecifier(format, out int digits);
+
+ byte* pDigits = stackalloc byte[TDecimal.BufferLength];
+ NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, TDecimal.BufferLength);
+
+ DecimalIeee754ToNumber(value, ref number);
+
+ if (fmt != 0)
+ {
+ NumberToString(ref vlb, ref number, fmt, digits, info);
+ }
+ else
+ {
+ NumberToStringFormat(ref vlb, ref number, format, info);
+ }
+
+ return null;
+ }
public static unsafe string FormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info)
{
@@ -385,6 +446,37 @@ public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan(TValue value, ref NumberBuffer number)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ DecodedDecimalIeee754 unpackDecimal = Number.UnpackDecimalIeee754(value);
+ number.IsNegative = unpackDecimal.Signed;
+
+ if (TValue.IsZero(unpackDecimal.Significand))
+ {
+ number.Scale = unpackDecimal.UnbiasedExponent < 0 ? unpackDecimal.UnbiasedExponent : 0;
+ number.DigitsCount = 0;
+ number.Digits[0] = (byte)'\0';
+ return;
+ }
+
+ string significand = TDecimal.ToDecStr(unpackDecimal.Significand);
+
+ Debug.Assert(significand.Length < TDecimal.BufferLength);
+
+ for (int i = 0; i < significand.Length; i++)
+ {
+ number.Digits[i] = (byte)significand[i];
+ }
+
+ number.Scale = significand.Length + unpackDecimal.UnbiasedExponent;
+ number.DigitsCount = significand.Length;
+ number.Digits[significand.Length] = (byte)'\0';
+
+ number.CheckConsistency();
+ }
+
internal static unsafe void DecimalToNumber(scoped ref decimal d, ref NumberBuffer number)
{
byte* buffer = number.DigitsPtr;
@@ -1802,7 +1894,7 @@ private static unsafe bool TryUInt32ToBinaryStr(uint value, int digits, S
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number)
+ internal static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number)
{
number.DigitsCount = UInt32Precision;
number.IsNegative = false;
@@ -2267,7 +2359,7 @@ private static unsafe bool TryUInt64ToBinaryStr(ulong value, int digits,
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number)
+ internal static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number)
{
number.DigitsCount = UInt64Precision;
number.IsNegative = false;
@@ -2679,7 +2771,7 @@ private static unsafe bool TryUInt128ToBinaryStr(Int128 value, int digits
}
}
- private static unsafe void UInt128ToNumber(UInt128 value, ref NumberBuffer number)
+ internal static unsafe void UInt128ToNumber(UInt128 value, ref NumberBuffer number)
{
number.DigitsCount = UInt128Precision;
number.IsNegative = false;
diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs
index 5bcc75e4dabf62..1e62f61e944fcb 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs
@@ -697,7 +697,7 @@ internal unsafe partial class Number
0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648
];
- private static void AccumulateDecimalDigitsIntoBigInteger(scoped ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result)
+ internal static void AccumulateDecimalDigitsIntoBigInteger(scoped ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result)
{
BigInteger.SetZero(out result);
@@ -891,7 +891,8 @@ private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger
}
// get 32-bit integer from at most 9 digits
- private static uint DigitsToUInt32(byte* p, int count)
+ [RequiresUnsafe]
+ internal static uint DigitsToUInt32(byte* p, int count)
{
Debug.Assert((1 <= count) && (count <= 9));
@@ -915,7 +916,8 @@ private static uint DigitsToUInt32(byte* p, int count)
}
// get 64-bit integer from at most 19 digits
- private static ulong DigitsToUInt64(byte* p, int count)
+ [RequiresUnsafe]
+ internal static ulong DigitsToUInt64(byte* p, int count)
{
Debug.Assert((1 <= count) && (count <= 19));
diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs
index 0088d4f20ae68c..523d58ced54b42 100644
--- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs
+++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs
@@ -133,6 +133,47 @@ internal interface IBinaryFloatParseAndFormatInfo : IBinaryFloatingPointI
static abstract int MaxPrecisionCustomFormat { get; }
}
+ internal interface IDecimalIeee754ParseAndFormatInfo
+ where TSelf : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ static abstract int Precision { get; }
+ static abstract int BufferLength { get; }
+ static abstract int MaxExponent { get; }
+ static abstract int MinExponent { get; }
+ static virtual int MaxAdjustedExponent => TSelf.MaxExponent - TSelf.Precision + 1;
+ static virtual int MinAdjustedExponent => TSelf.MinExponent - TSelf.Precision + 1;
+ static abstract int ExponentBias { get; }
+ static abstract TValue PositiveInfinity { get; }
+ static abstract TValue NegativeInfinity { get; }
+ static abstract TValue NaN { get; }
+ static abstract TValue Zero { get; }
+ static abstract TValue NegativeZero { get; }
+ static abstract TValue MaxSignificand { get; }
+ static abstract TValue NumberToSignificand(ref Number.NumberBuffer number, int digits);
+ static abstract string ToDecStr(TValue significand);
+ static abstract int ConvertToExponent(TValue value);
+ static abstract TValue Power10(int exponent);
+ static abstract (TValue Quotient, TValue Remainder) DivRemPow10(TValue value, int exponent);
+ static abstract TSelf Construct(TValue value);
+ static abstract int CountDigits(TValue significand);
+ static abstract int NumberBitsEncoding { get; }
+ static abstract int NumberBitsSignificand { get; }
+ static abstract TValue SignMask { get; }
+ static abstract TValue G0G1Mask { get; }
+ static abstract TValue G0ToGwPlus1ExponentMask { get; } //G0 to G(w+1)
+ static abstract TValue G2ToGwPlus3ExponentMask { get; } //G2 to G(w+3)
+ static abstract TValue GwPlus2ToGwPlus4SignificandMask { get; } //G(w+2) to G(w+4)
+ static abstract TValue GwPlus4SignificandMask { get; } //G(w+4)
+ static abstract TValue MostSignificantBitOfSignificandMask { get; }
+ static abstract bool IsNaN(TValue decimalBits);
+ static abstract bool IsFinite(TValue decimalBits);
+ static abstract bool IsInfinity(TValue decimalBits);
+ static abstract bool IsNegative(TValue decimalBits);
+ static abstract TValue EncodeExponentToG0ThroughGwPlus1(uint biasedExponent);
+ static abstract TValue EncodeExponentToG2ThroughGwPlus3(uint biasedExponent);
+ }
+
internal static partial class Number
{
private const int Int32Precision = 10;
@@ -767,6 +808,21 @@ internal static decimal ParseDecimal(ReadOnlySpan value, NumberSty
return result;
}
+ internal static TDecimal ParseDecimalIeee754(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info)
+ where TChar : unmanaged, IUtfChar
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ ParsingStatus status = TryParseDecimalIeee754(value, styles, info, out TDecimal result);
+
+ if (status == ParsingStatus.Failed)
+ {
+ ThrowFormatException(value);
+ }
+
+ return result;
+ }
+
internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decimal value)
{
number.CheckConsistency();
@@ -918,6 +974,87 @@ internal static ParsingStatus TryParseDecimal(ReadOnlySpan value,
return ParsingStatus.OK;
}
+ internal static ParsingStatus TryParseDecimalIeee754(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TDecimal result)
+ where TChar : unmanaged, IUtfChar
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[TDecimal.BufferLength]);
+ result = default;
+
+ if (!TryStringToNumber(value, styles, ref number, info))
+ {
+ ReadOnlySpan valueTrim = SpanTrim(value);
+ ReadOnlySpan positiveInfinitySymbol = info.PositiveInfinitySymbolTChar();
+
+ if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol))
+ {
+ result = TDecimal.Construct(TDecimal.PositiveInfinity);
+ return ParsingStatus.OK;
+ }
+
+ if (SpanEqualsOrdinalIgnoreCase(valueTrim, info.NegativeInfinitySymbolTChar()))
+ {
+ result = TDecimal.Construct(TDecimal.NegativeInfinity);
+ return ParsingStatus.OK;
+ }
+
+ ReadOnlySpan nanSymbol = info.NaNSymbolTChar();
+
+ if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol))
+ {
+ result = TDecimal.Construct(TDecimal.NaN);
+ return ParsingStatus.OK;
+ }
+
+
+ var positiveSign = info.PositiveSignTChar();
+
+ if (SpanStartsWith(valueTrim, positiveSign, StringComparison.OrdinalIgnoreCase))
+ {
+ valueTrim = valueTrim.Slice(positiveSign.Length);
+
+ if (SpanEqualsOrdinalIgnoreCase(valueTrim, positiveInfinitySymbol))
+ {
+ result = TDecimal.Construct(TDecimal.PositiveInfinity);
+ return ParsingStatus.OK;
+ }
+ else if (SpanEqualsOrdinalIgnoreCase(valueTrim, nanSymbol))
+ {
+ result = TDecimal.Construct(TDecimal.NaN);
+ return ParsingStatus.OK;
+ }
+
+ result = TDecimal.Construct(TDecimal.Zero);
+ return ParsingStatus.Failed;
+ }
+
+ ReadOnlySpan negativeSign = info.NegativeSignTChar();
+
+ if (SpanStartsWith(valueTrim, negativeSign, StringComparison.OrdinalIgnoreCase))
+ {
+ if (SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(negativeSign.Length), nanSymbol))
+ {
+ result = TDecimal.Construct(TDecimal.NaN);
+ return ParsingStatus.OK;
+ }
+
+ if (info.AllowHyphenDuringParsing() && SpanStartsWith(valueTrim, TChar.CastFrom('-')) && SpanEqualsOrdinalIgnoreCase(valueTrim.Slice(1), nanSymbol))
+ {
+ result = TDecimal.Construct(TDecimal.NaN);
+ return ParsingStatus.OK;
+ }
+ }
+
+ result = TDecimal.Construct(TDecimal.Zero);
+ return ParsingStatus.Failed;
+ }
+
+ result = NumberToDecimalIeee754(ref number);
+
+ return ParsingStatus.OK;
+ }
+
internal static bool SpanStartsWith(ReadOnlySpan span, TChar c)
where TChar : unmanaged, IUtfChar
{
@@ -1501,5 +1638,14 @@ internal static TFloat NumberToFloat(ref NumberBuffer number)
return number.IsNegative ? -result : result;
}
+
+ internal static TDecimal NumberToDecimalIeee754(ref NumberBuffer number)
+ where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo
+ where TValue : unmanaged, IBinaryInteger
+ {
+ number.CheckConsistency();
+ TValue value = NumberToDecimalIeee754Bits(ref number);
+ return TDecimal.Construct(value);
+ }
}
}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs
new file mode 100644
index 00000000000000..efb2baa98548e6
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs
@@ -0,0 +1,406 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers.Text;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+
+namespace System.Numerics
+{
+ public readonly struct Decimal128
+ : IComparable,
+ IComparable,
+ IEquatable,
+ ISpanParsable,
+ IMinMaxValue,
+ IDecimalIeee754ParseAndFormatInfo
+ {
+#if BIGENDIAN
+ internal readonly ulong _upper;
+ internal readonly ulong _lower;
+#else
+ internal readonly ulong _lower;
+ internal readonly ulong _upper;
+#endif
+
+ private const int MaxExponent = 6144;
+ private const int MinExponent = -6143;
+ private const int Precision = 34;
+ private const int ExponentBias = 6176;
+ private static readonly UInt128 PositiveInfinityValue = new UInt128(upper: 0x7800_0000_0000_0000, lower: 0);
+ private static readonly UInt128 NegativeInfinityValue = new UInt128(upper: 0xf800_0000_0000_0000, lower: 0);
+ private static readonly UInt128 ZeroValue = new UInt128(0, 0);
+ private static readonly UInt128 NegativeZeroValue = new UInt128(0x8000_0000_0000_0000, 0);
+ private static readonly UInt128 QuietNaNValue = new UInt128(0xFC00_0000_0000_0000, 0);
+ private static readonly UInt128 MaxInternalValue = new UInt128(upper: 0x5FFF_ED09_BEAD_87C0, lower: 0x378D_8E63_FFFF_FFFF);
+ private static readonly UInt128 MinInternalValue = new UInt128(upper: 0xDFFF_ED09_BEAD_87C0, lower: 0x378D_8E63_FFFF_FFFF);
+
+ private const ulong SignMaskUpper = 0x8000_0000_0000_0000;
+ private const ulong NaNMaskUpper = 0x7C00_0000_0000_0000;
+ private const ulong InfinityMaskUpper = 0x7800_0000_0000_0000;
+
+ public static Decimal128 PositiveInfinity => new Decimal128(PositiveInfinityValue);
+ public static Decimal128 NegativeInfinity => new Decimal128(NegativeInfinityValue);
+ public static Decimal128 NaN => new Decimal128(QuietNaNValue);
+ public static Decimal128 NegativeZero => new Decimal128(NegativeZeroValue);
+ public static Decimal128 Zero => new Decimal128(ZeroValue);
+ public static Decimal128 MinValue => new Decimal128(MinInternalValue);
+ public static Decimal128 MaxValue => new Decimal128(MaxInternalValue);
+
+ internal Decimal128(UInt128 value)
+ {
+ _upper = value.Upper;
+ _lower = value.Lower;
+ }
+
+ public Decimal128(Int128 significand, int exponent)
+ {
+ bool isNegative = significand < 0;
+ UInt128 magnitude;
+ if (isNegative)
+ {
+ magnitude = significand == Int128.MinValue ? (UInt128)Int128.MaxValue + 1 : (UInt128)(-significand);
+ }
+ else
+ {
+ magnitude = (UInt128)significand;
+ }
+ UInt128 value = Number.ConstructorToDecimalIeee754Bits(isNegative, magnitude, exponent);
+ _upper = value.Upper;
+ _lower = value.Lower;
+ }
+
+ ///
+ /// Parses a from a in the default parse style.
+ ///
+ /// The input to be parsed.
+ /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned.
+ public static Decimal128 Parse(string s) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null);
+
+ ///
+ /// Parses a from a in the given .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned.
+ public static Decimal128 Parse(string s, NumberStyles style) => Parse(s, style, provider: null);
+
+ ///
+ public static Decimal128 Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider);
+
+ ///
+ /// Parses a from a and .
+ ///
+ /// The input to be parsed.
+ /// A format provider.
+ /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned.
+ public static Decimal128 Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider);
+
+ ///
+ /// Parses a from a and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned.
+ public static Decimal128 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleDecimal(style);
+ return Number.ParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ ///
+ /// Parses a from a with the given and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned.
+ public static Decimal128 Parse(string s, NumberStyles style, IFormatProvider? provider)
+ {
+ if (s is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ }
+ return Parse(s.AsSpan(), style, provider);
+ }
+
+ ///
+ /// Tries to parse a from a in the default parse style.
+ ///
+ /// The input to be parsed.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse([NotNullWhen(true)] string? s, out Decimal128 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result);
+
+ ///
+ /// Tries to parse a from a in the default parse style.
+ ///
+ /// The input to be parsed.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse(ReadOnlySpan s, out Decimal128 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result);
+
+ ///
+ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
+
+ ///
+ /// Tries to parse a from a with the given .
+ ///
+ /// The input to be parsed.
+ /// A format provider.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
+
+ ///
+ /// Tries to parse a from a with the given and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result)
+ {
+ NumberFormatInfo.ValidateParseStyleDecimal(style);
+ return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
+ }
+
+ ///
+ /// Tries to parse a from a with the given and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result)
+ {
+ NumberFormatInfo.ValidateParseStyleDecimal(style);
+
+ if (s == null)
+ {
+ result = default;
+ return false;
+ }
+ return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
+ }
+
+ ///
+ public int CompareTo(object? value)
+ {
+ if (value is not Decimal128 other)
+ {
+ return (value is null) ? 1 : throw new ArgumentException(SR.Arg_MustBeDecimal128);
+ }
+ return CompareTo(other);
+ }
+
+ ///
+ public int CompareTo(Decimal128 other)
+ {
+ var current = new UInt128(_upper, _lower);
+ var another = new UInt128(other._upper, other._lower);
+ return Number.CompareDecimalIeee754(current, another);
+ }
+
+ ///
+ public bool Equals(Decimal128 other)
+ {
+ var current = new UInt128(_upper, _lower);
+ var another = new UInt128(other._upper, other._lower);
+ return Number.CompareDecimalIeee754(current, another) == 0;
+ }
+
+ ///
+ /// Returns a value that indicates whether this instance is equal to a specified .
+ ///
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ return (obj is Decimal128 other) && Equals(other);
+ }
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ public override int GetHashCode()
+ {
+ return Number.GetDecimalIeee754HashCode(new UInt128(_upper, _lower));
+ }
+
+ ///
+ /// Returns a string representation of the current value.
+ ///
+ public override string ToString()
+ {
+ return Number.FormatDecimalIeee754(new UInt128(_upper, _lower), null, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// Returns a string representation of the current value using the specified .
+ ///
+ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format)
+ {
+ return Number.FormatDecimalIeee754(new UInt128(_upper, _lower), format, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// Returns a string representation of the current value with the specified .
+ ///
+ public string ToString(IFormatProvider? provider)
+ {
+ return Number.FormatDecimalIeee754(new UInt128(_upper, _lower), null, NumberFormatInfo.GetInstance(provider));
+ }
+
+ ///
+ /// Returns a string representation of the current value using the specified and .
+ ///
+ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider)
+ {
+ return Number.FormatDecimalIeee754(new UInt128(_upper, _lower), format, NumberFormatInfo.GetInstance(provider));
+ }
+
+ private static readonly UInt128[] UInt128Powers10 =
+ [
+ new UInt128(0, 1),
+ new UInt128(0, 10),
+ new UInt128(0, 100),
+ new UInt128(0, 1000),
+ new UInt128(0, 10000),
+ new UInt128(0, 100000),
+ new UInt128(0, 1000000),
+ new UInt128(0, 10000000),
+ new UInt128(0, 100000000),
+ new UInt128(0, 1000000000),
+ new UInt128(0, 10000000000),
+ new UInt128(0, 100000000000),
+ new UInt128(0, 1000000000000),
+ new UInt128(0, 10000000000000),
+ new UInt128(0, 100000000000000),
+ new UInt128(0, 1000000000000000),
+ new UInt128(0, 10000000000000000),
+ new UInt128(0, 100000000000000000),
+ new UInt128(0, 1000000000000000000),
+ new UInt128(0, 10000000000000000000),
+ new UInt128(5, 7766279631452241920),
+ new UInt128(54, 3875820019684212736),
+ new UInt128(542, 1864712049423024128),
+ new UInt128(5421, 200376420520689664),
+ new UInt128(54210, 2003764205206896640),
+ new UInt128(542101, 1590897978359414784),
+ new UInt128(5421010, 15908979783594147840),
+ new UInt128(54210108, 11515845246265065472),
+ new UInt128(542101086, 4477988020393345024),
+ new UInt128(5421010862, 7886392056514347008),
+ new UInt128(54210108624, 5076944270305263616),
+ new UInt128(542101086242, 13875954555633532928),
+ new UInt128(5421010862427, 9632337040368467968),
+ new UInt128(54210108624275, 4089650035136921600),
+ new UInt128(542101086242752, 4003012203950112768),
+ ];
+
+ static string IDecimalIeee754ParseAndFormatInfo.ToDecStr(UInt128 significand)
+ {
+ return Number.UInt128ToDecStr(significand);
+ }
+
+ static unsafe UInt128 IDecimalIeee754ParseAndFormatInfo.NumberToSignificand(ref Number.NumberBuffer number, int digits)
+ {
+ if (digits <= 19)
+ {
+ return Number.DigitsToUInt64(number.DigitsPtr, digits);
+ }
+ else
+ {
+ Number.AccumulateDecimalDigitsIntoBigInteger(ref number, 0, (uint)digits, out Number.BigInteger result);
+ return result.ToUInt128();
+ }
+ }
+
+ static Decimal128 IDecimalIeee754ParseAndFormatInfo.Construct(UInt128 value) => new Decimal128(value);
+
+ static int IDecimalIeee754ParseAndFormatInfo.ConvertToExponent(UInt128 value) => (int)value;
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.Power10(int exponent) => UInt128Powers10[exponent];
+
+ static (UInt128 Quotient, UInt128 Remainder) IDecimalIeee754ParseAndFormatInfo.DivRemPow10(UInt128 value, int exponent)
+ {
+ UInt128 power = UInt128Powers10[exponent];
+ return UInt128.DivRem(value, power);
+ }
+
+ static int IDecimalIeee754ParseAndFormatInfo.CountDigits(UInt128 significand) => FormattingHelpers.CountDigits(significand);
+
+ static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision;
+
+ static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal128NumberBufferLength;
+
+ static int IDecimalIeee754ParseAndFormatInfo.MaxExponent => MaxExponent;
+
+ static int IDecimalIeee754ParseAndFormatInfo.MinExponent => MinExponent;
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.PositiveInfinity => PositiveInfinityValue;
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.NegativeInfinity => NegativeInfinityValue;
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.Zero => ZeroValue;
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.NegativeZero => NegativeZeroValue;
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.NaN => QuietNaNValue;
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.MostSignificantBitOfSignificandMask => new UInt128(0x0002_0000_0000_0000, 0);
+
+ static int IDecimalIeee754ParseAndFormatInfo.NumberBitsEncoding => 128;
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.SignMask => new UInt128(SignMaskUpper, 0);
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.G0G1Mask => new UInt128(0x6000_0000_0000_0000, 0);
+
+ static int IDecimalIeee754ParseAndFormatInfo.ExponentBias => ExponentBias;
+
+ static int IDecimalIeee754ParseAndFormatInfo.NumberBitsSignificand => 110;
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.G0ToGwPlus1ExponentMask => new UInt128(0x7FFE_0000_0000_0000, 0);
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.G2ToGwPlus3ExponentMask => new UInt128(0x1FFF_8000_0000_0000, 0);
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.GwPlus2ToGwPlus4SignificandMask => new UInt128(0x0001_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF);
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.GwPlus4SignificandMask => new UInt128(0x0000_7FFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF);
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.MaxSignificand => new UInt128(upper: 0x0001_ED09_BEAD_87C0, lower: 0x378D_8E63_FFFF_FFFF); // 9_999_999_999_999_999_999_999_999_999_999_999;
+
+ static bool IDecimalIeee754ParseAndFormatInfo.IsNaN(UInt128 decimalBits)
+ {
+ return (decimalBits.Upper & NaNMaskUpper) == NaNMaskUpper;
+ }
+
+ static bool IDecimalIeee754ParseAndFormatInfo.IsNegative(UInt128 decimalBits)
+ {
+ return (decimalBits.Upper & SignMaskUpper) != 0;
+ }
+
+ static bool IDecimalIeee754ParseAndFormatInfo.IsFinite(UInt128 decimalBits)
+ {
+ ulong comb = decimalBits.Upper & NaNMaskUpper;
+ return comb != NaNMaskUpper && comb != InfinityMaskUpper;
+ }
+
+ static bool IDecimalIeee754ParseAndFormatInfo.IsInfinity(UInt128 decimalBits)
+ {
+ return (decimalBits.Upper & NaNMaskUpper) == InfinityMaskUpper;
+ }
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.EncodeExponentToG0ThroughGwPlus1(uint biasedExponent)
+ {
+ return ((UInt128)biasedExponent) << 113;
+ }
+
+ static UInt128 IDecimalIeee754ParseAndFormatInfo.EncodeExponentToG2ThroughGwPlus3(uint biasedExponent)
+ {
+ return ((UInt128)biasedExponent) << 111;
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs
new file mode 100644
index 00000000000000..ad4d4cbbc9518b
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs
@@ -0,0 +1,358 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers.Text;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Runtime.InteropServices;
+
+namespace System.Numerics
+{
+ [StructLayout(LayoutKind.Sequential)]
+ public readonly struct Decimal32
+ : IComparable,
+ IComparable,
+ IEquatable,
+ ISpanParsable,
+ IMinMaxValue,
+ IDecimalIeee754ParseAndFormatInfo
+ {
+ internal readonly uint _value;
+
+ internal Decimal32(uint value)
+ {
+ _value = value;
+ }
+
+ private const int MaxExponent = 96;
+ private const int MinExponent = -95;
+ private const int Precision = 7;
+ private const int ExponentBias = 101;
+ private const uint PositiveInfinityValue = 0x7800_0000;
+ private const uint NegativeInfinityValue = 0xF800_0000;
+ private const uint ZeroValue = 0x0000_0000;
+ private const uint NegativeZeroValue = 0x8000_0000;
+ private const uint QuietNaNValue = 0xFC00_0000;
+ private const uint G0G1Mask = 0x6000_0000;
+ private const uint SignMask = 0x8000_0000;
+ private const uint MostSignificantBitOfSignificandMask = 0x0080_0000;
+ private const uint NaNMask = 0x7C00_0000;
+ private const uint InfinityMask = 0x7800_0000;
+ private const uint MaxSignificand = 9_999_999;
+ private const uint MaxInternalValue = 0x77F8_967F; // 9,999,999 x 10^90
+ private const uint MinInternalValue = 0xF7F8_967F; // -9,999,999 x 10^90
+
+ public static Decimal32 PositiveInfinity => new Decimal32(PositiveInfinityValue);
+ public static Decimal32 NegativeInfinity => new Decimal32(NegativeInfinityValue);
+ public static Decimal32 NaN => new Decimal32(QuietNaNValue);
+ public static Decimal32 NegativeZero => new Decimal32(NegativeZeroValue);
+ public static Decimal32 Zero => new Decimal32(ZeroValue);
+ public static Decimal32 MinValue => new Decimal32(MinInternalValue);
+ public static Decimal32 MaxValue => new Decimal32(MaxInternalValue);
+
+ private static ReadOnlySpan UInt32Powers10 =>
+ [
+ 1,
+ 10,
+ 100,
+ 1000,
+ 10000,
+ 100000,
+ 1000000,
+ ];
+
+ public Decimal32(int significand, int exponent)
+ {
+ bool isNegative = significand < 0;
+ uint unsignedSignificand = isNegative ? (uint)(-(long)significand) : (uint)significand;
+ _value = Number.ConstructorToDecimalIeee754Bits(isNegative, unsignedSignificand, exponent);
+ }
+
+ ///
+ /// Parses a from a in the default parse style.
+ ///
+ /// The input to be parsed.
+ /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned.
+ public static Decimal32 Parse(string s) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null);
+
+ ///
+ /// Parses a from a in the given .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned.
+ public static Decimal32 Parse(string s, NumberStyles style) => Parse(s, style, provider: null);
+
+ ///
+ public static Decimal32 Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider);
+
+ ///
+ /// Parses a from a and .
+ ///
+ /// The input to be parsed.
+ /// A format provider.
+ /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned.
+ public static Decimal32 Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider);
+
+ ///
+ /// Parses a from a and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned.
+ public static Decimal32 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleDecimal(style);
+ return Number.ParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ ///
+ /// Parses a from a with the given and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned.
+ public static Decimal32 Parse(string s, NumberStyles style, IFormatProvider? provider)
+ {
+ if (s is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ }
+ return Parse(s.AsSpan(), style, provider);
+ }
+
+ ///
+ /// Tries to parse a from a in the default parse style.
+ ///
+ /// The input to be parsed.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse([NotNullWhen(true)] string? s, out Decimal32 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result);
+
+ ///
+ /// Tries to parse a from a in the default parse style.
+ ///
+ /// The input to be parsed.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse(ReadOnlySpan s, out Decimal32 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result);
+
+ ///
+ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
+
+ ///
+ /// Tries to parse a from a with the given .
+ ///
+ /// The input to be parsed.
+ /// A format provider.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
+
+ ///
+ /// Tries to parse a from a with the given and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result)
+ {
+ NumberFormatInfo.ValidateParseStyleDecimal(style);
+ return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
+ }
+
+ ///
+ /// Tries to parse a from a with the given and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result)
+ {
+ NumberFormatInfo.ValidateParseStyleDecimal(style);
+
+ if (s == null)
+ {
+ result = default;
+ return false;
+ }
+ return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
+ }
+
+ ///
+ public int CompareTo(object? value)
+ {
+ if (value == null)
+ {
+ return 1;
+ }
+
+ if (value is not Decimal32 i)
+ {
+ throw new ArgumentException(SR.Arg_MustBeDecimal32);
+ }
+
+ return Number.CompareDecimalIeee754(_value, i._value);
+ }
+
+ ///
+ public int CompareTo(Decimal32 other)
+ {
+ return Number.CompareDecimalIeee754(_value, other._value);
+ }
+
+ ///
+ public bool Equals(Decimal32 other)
+ {
+ return Number.CompareDecimalIeee754(_value, other._value) == 0;
+ }
+
+ ///
+ /// Returns a value that indicates whether this instance is equal to a specified .
+ ///
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ return obj is Decimal32 other && Equals(other);
+ }
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ public override int GetHashCode()
+ {
+ return Number.GetDecimalIeee754HashCode(_value);
+ }
+
+ ///
+ /// Returns a string representation of the current value.
+ ///
+ public override string ToString()
+ {
+ return Number.FormatDecimalIeee754(_value, null, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// Returns a string representation of the current value using the specified .
+ ///
+ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format)
+ {
+ return Number.FormatDecimalIeee754(_value, format, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// Returns a string representation of the current value with the specified .
+ ///
+ public string ToString(IFormatProvider? provider)
+ {
+ return Number.FormatDecimalIeee754(_value, null, NumberFormatInfo.GetInstance(provider));
+ }
+
+ ///
+ /// Returns a string representation of the current value using the specified and .
+ ///
+ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider)
+ {
+ return Number.FormatDecimalIeee754(_value, format, NumberFormatInfo.GetInstance(provider));
+ }
+
+ static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision;
+
+ static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal32NumberBufferLength;
+
+ static string IDecimalIeee754ParseAndFormatInfo.ToDecStr(uint significand)
+ {
+ return Number.UInt32ToDecStr(significand);
+ }
+
+ static unsafe uint IDecimalIeee754ParseAndFormatInfo.NumberToSignificand(ref Number.NumberBuffer number, int digits)
+ {
+ return Number.DigitsToUInt32(number.DigitsPtr, digits);
+ }
+
+ static Decimal32 IDecimalIeee754ParseAndFormatInfo.Construct(uint value) => new Decimal32(value);
+
+ static int IDecimalIeee754ParseAndFormatInfo.ConvertToExponent(uint value) => (int)value;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.Power10(int exponent) => UInt32Powers10[exponent];
+
+ static (uint Quotient, uint Remainder) IDecimalIeee754ParseAndFormatInfo.DivRemPow10(uint value, int exponent)
+ {
+ uint power = UInt32Powers10[exponent];
+ return Math.DivRem(value, power);
+ }
+ static int IDecimalIeee754ParseAndFormatInfo.CountDigits(uint significand) => FormattingHelpers.CountDigits(significand);
+
+ static int IDecimalIeee754ParseAndFormatInfo.MaxExponent => MaxExponent;
+
+ static int IDecimalIeee754ParseAndFormatInfo.MinExponent => MinExponent;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.PositiveInfinity => PositiveInfinityValue;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.NegativeInfinity => NegativeInfinityValue;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.Zero => ZeroValue;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.NegativeZero => NegativeZeroValue;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.NaN => QuietNaNValue;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask;
+
+ static int IDecimalIeee754ParseAndFormatInfo.NumberBitsEncoding => 32;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.SignMask => SignMask;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.G0G1Mask => G0G1Mask;
+
+ static int IDecimalIeee754ParseAndFormatInfo.ExponentBias => ExponentBias;
+
+ static int IDecimalIeee754ParseAndFormatInfo.NumberBitsSignificand => 20;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.G0ToGwPlus1ExponentMask => 0x7F80_0000;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.G2ToGwPlus3ExponentMask => 0x1FE0_0000;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.GwPlus2ToGwPlus4SignificandMask => 0x007F_FFFF;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.GwPlus4SignificandMask => 0x001F_FFFF;
+
+ static uint IDecimalIeee754ParseAndFormatInfo.MaxSignificand => MaxSignificand;
+
+ static bool IDecimalIeee754ParseAndFormatInfo.IsNaN(uint decimalBits)
+ {
+ return (decimalBits & NaNMask) == NaNMask;
+ }
+
+ static bool IDecimalIeee754ParseAndFormatInfo.IsNegative(uint decimalBits)
+ {
+ return (decimalBits & SignMask) != 0;
+ }
+
+ static bool IDecimalIeee754ParseAndFormatInfo.IsFinite(uint decimalBits)
+ {
+ uint comb = decimalBits & NaNMask;
+ return comb != NaNMask && comb != InfinityMask;
+ }
+
+ static bool IDecimalIeee754ParseAndFormatInfo.IsInfinity(uint decimalBits)
+ {
+ return (decimalBits & NaNMask) == InfinityMask;
+ }
+
+ static uint IDecimalIeee754ParseAndFormatInfo.EncodeExponentToG0ThroughGwPlus1(uint biasedExponent)
+ {
+ return biasedExponent << 23;
+ }
+
+ static uint IDecimalIeee754ParseAndFormatInfo.EncodeExponentToG2ThroughGwPlus3(uint biasedExponent)
+ {
+ return biasedExponent << 21;
+ }
+ }
+}
diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs
new file mode 100644
index 00000000000000..e430659afb681b
--- /dev/null
+++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs
@@ -0,0 +1,361 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+
+using System.Buffers.Text;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+
+namespace System.Numerics
+{
+ public readonly struct Decimal64
+ : IComparable,
+ IComparable,
+ IEquatable,
+ ISpanParsable,
+ IMinMaxValue,
+ IDecimalIeee754ParseAndFormatInfo
+ {
+ internal readonly ulong _value;
+
+ private const int MaxExponent = 384;
+ private const int MinExponent = -383;
+ private const int Precision = 16;
+ private const int ExponentBias = 398;
+ private const ulong PositiveInfinityValue = 0x7800_0000_0000_0000;
+ private const ulong NegativeInfinityValue = 0xF800_0000_0000_0000;
+ private const ulong ZeroValue = 0x0000_0000_0000_0000;
+ private const ulong NegativeZeroValue = 0x8000_0000_0000_0000;
+ private const ulong QuietNaNValue = 0xFC00_0000_0000_0000;
+ private const ulong G0G1Mask = 0x6000_0000_0000_0000;
+ private const ulong SignMask = 0x8000_0000_0000_0000;
+ private const ulong MostSignificantBitOfSignificandMask = 0x0020_0000_0000_0000;
+ private const ulong NaNMask = 0x7C00_0000_0000_0000;
+ private const ulong InfinityMask = 0x7800_0000_0000_0000;
+ private const ulong MaxSignificand = 9_999_999_999_999_999;
+ private const ulong MaxInternalValue = 0x77FB_86F2_6FC0_FFFF; // 9_999_999_999_999_999 x 10^369
+ private const ulong MinInternalValue = 0xF7FB_86F2_6FC0_FFFF; // -9_999_999_999_999_999 x 10^369
+
+ public static Decimal64 PositiveInfinity => new Decimal64(PositiveInfinityValue);
+ public static Decimal64 NegativeInfinity => new Decimal64(NegativeInfinityValue);
+ public static Decimal64 NaN => new Decimal64(QuietNaNValue);
+ public static Decimal64 NegativeZero => new Decimal64(NegativeZeroValue);
+ public static Decimal64 Zero => new Decimal64(ZeroValue);
+ public static Decimal64 MinValue => new Decimal64(MinInternalValue);
+ public static Decimal64 MaxValue => new Decimal64(MaxInternalValue);
+
+
+ private static ReadOnlySpan UInt64Powers10 =>
+ [
+ 1,
+ 10,
+ 100,
+ 1000,
+ 10000,
+ 100000,
+ 1000000,
+ 10000000,
+ 100000000,
+ 1000000000,
+ 10000000000,
+ 100000000000,
+ 1000000000000,
+ 10000000000000,
+ 100000000000000,
+ 1000000000000000,
+ ];
+
+ public Decimal64(long significand, int exponent)
+ {
+ bool isNegative = significand < 0;
+ ulong magnitude = isNegative ? 0UL - (ulong)significand : (ulong)significand;
+ _value = Number.ConstructorToDecimalIeee754Bits(isNegative, magnitude, exponent);
+ }
+
+ internal Decimal64(ulong value)
+ {
+ _value = value;
+ }
+
+ ///
+ /// Parses a from a in the default parse style.
+ ///
+ /// The input to be parsed.
+ /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned.
+ public static Decimal64 Parse(string s) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null);
+
+ ///
+ /// Parses a from a in the given .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned.
+ public static Decimal64 Parse(string s, NumberStyles style) => Parse(s, style, provider: null);
+
+ ///
+ public static Decimal64 Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider);
+
+ ///
+ /// Parses a from a and .
+ ///
+ /// The input to be parsed.
+ /// A format provider.
+ /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned.
+ public static Decimal64 Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider);
+
+ ///
+ /// Parses a from a and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned.
+ public static Decimal64 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Float | NumberStyles.AllowThousands, IFormatProvider? provider = null)
+ {
+ NumberFormatInfo.ValidateParseStyleDecimal(style);
+ return Number.ParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider));
+ }
+
+ ///
+ /// Parses a from a with the given and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned.
+ public static Decimal64 Parse(string s, NumberStyles style, IFormatProvider? provider)
+ {
+ if (s is null)
+ {
+ ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s);
+ }
+ return Parse(s.AsSpan(), style, provider);
+ }
+
+ ///
+ /// Tries to parse a from a in the default parse style.
+ ///
+ /// The input to be parsed.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse([NotNullWhen(true)] string? s, out Decimal64 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result);
+
+ ///
+ /// Tries to parse a from a in the default parse style.
+ ///
+ /// The input to be parsed.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse(ReadOnlySpan s, out Decimal64 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider: null, out result);
+
+ ///
+ public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
+
+ ///
+ /// Tries to parse a from a with the given .
+ ///
+ /// The input to be parsed.
+ /// A format provider.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) => TryParse(s, NumberStyles.Float | NumberStyles.AllowThousands, provider, out result);
+
+ ///
+ /// Tries to parse a from a with the given and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result)
+ {
+ NumberFormatInfo.ValidateParseStyleDecimal(style);
+ return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
+ }
+
+ ///
+ /// Tries to parse a from a with the given and .
+ ///
+ /// The input to be parsed.
+ /// The used to parse the input.
+ /// A format provider.
+ /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned.
+ /// if the parse was successful, otherwise.
+ public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result)
+ {
+ NumberFormatInfo.ValidateParseStyleDecimal(style);
+
+ if (s == null)
+ {
+ result = default;
+ return false;
+ }
+ return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK;
+ }
+
+ ///
+ public int CompareTo(object? value)
+ {
+ if (value is not Decimal64 other)
+ {
+ return (value is null) ? 1 : throw new ArgumentException(SR.Arg_MustBeDecimal64);
+ }
+ return CompareTo(other);
+ }
+
+ ///
+ public int CompareTo(Decimal64 other)
+ {
+ return Number.CompareDecimalIeee754(_value, other._value);
+ }
+
+ ///
+ public bool Equals(Decimal64 other)
+ {
+ return Number.CompareDecimalIeee754(_value, other._value) == 0;
+ }
+
+ ///
+ /// Returns a value that indicates whether this instance is equal to a specified .
+ ///
+ public override bool Equals([NotNullWhen(true)] object? obj)
+ {
+ return obj is Decimal64 other && Equals(other);
+ }
+
+ ///
+ /// Serves as the default hash function.
+ ///
+ public override int GetHashCode()
+ {
+ return Number.GetDecimalIeee754HashCode(_value);
+ }
+
+ ///
+ /// Returns a string representation of the current value.
+ ///
+ public override string ToString()
+ {
+ return Number.FormatDecimalIeee754(_value, null, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// Returns a string representation of the current value using the specified .
+ ///
+ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format)
+ {
+ return Number.FormatDecimalIeee754(_value, format, NumberFormatInfo.CurrentInfo);
+ }
+
+ ///
+ /// Returns a string representation of the current value with the specified .
+ ///
+ public string ToString(IFormatProvider? provider)
+ {
+ return Number.FormatDecimalIeee754(_value, null, NumberFormatInfo.GetInstance(provider));
+ }
+
+ ///
+ /// Returns a string representation of the current value using the specified and .
+ ///
+ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider)
+ {
+ return Number.FormatDecimalIeee754(_value, format, NumberFormatInfo.GetInstance(provider));
+ }
+
+ static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision;
+
+ static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal64NumberBufferLength;
+
+ static string IDecimalIeee754ParseAndFormatInfo.ToDecStr(ulong significand)
+ {
+ return Number.UInt64ToDecStr(significand);
+ }
+
+ static unsafe ulong IDecimalIeee754ParseAndFormatInfo.NumberToSignificand(ref Number.NumberBuffer number, int digits)
+ {
+ return Number.DigitsToUInt64(number.DigitsPtr, digits);
+ }
+
+ static Decimal64 IDecimalIeee754ParseAndFormatInfo.Construct(ulong value) => new Decimal64(value);
+
+ static int IDecimalIeee754ParseAndFormatInfo.ConvertToExponent(ulong value) => (int)value;
+
+ static ulong IDecimalIeee754ParseAndFormatInfo.Power10(int exponent) => UInt64Powers10[exponent];
+
+ static (ulong Quotient, ulong Remainder) IDecimalIeee754ParseAndFormatInfo.DivRemPow10(ulong value, int exponent)
+ {
+ ulong power = UInt64Powers10[exponent];
+ return Math.DivRem(value, power);
+ }
+
+ static int IDecimalIeee754ParseAndFormatInfo