diff --git a/src/libraries/Common/src/System/Number.NumberBuffer.cs b/src/libraries/Common/src/System/Number.NumberBuffer.cs index 44c1d47d96dcab..325f752ec625c2 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 = 97 + 1 + 1; // 97 for the longest input + 1 for rounding + internal const int Decimal64NumberBufferLength = 385 + 1 + 1; // 385 for the longest input + 1 for rounding + internal const int Decimal128NumberBufferLength = 6145 + 1 + 1; // 6145 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 f511ebeb21810d..915b1795abd9fc 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -534,6 +534,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 56fdc095a74eed..1c3d93a2bfcbb0 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 @@ -436,6 +436,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..ae9e4202abb606 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -0,0 +1,441 @@ +// 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 IEEE 754 + /// decimal interchange format (BID), handles rounding/infinitive 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. + /// + /// + /// 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 (TValue.IsZero(significand)) + { + return signed ? TDecimal.NegativeZero : TDecimal.Zero; + } + + if (significand > TDecimal.MaxSignificand || exponent > TDecimal.MaxExponent || exponent < TDecimal.MinExponent) + { + return ConstructorToDecimalIeee754BitsRounding(signed, significand, exponent); + } + + return DecimalIeee754FiniteNumberBinaryEncoding(signed, significand, exponent); + + // This method adjusts the significand and exponent to ensure they fall within valid bounds. + // It handles underflow and overflow of the exponent by trimming or padding digits accordingly, + // and applies rounding when the number of digits exceeds the allowed precision. + static TValue ConstructorToDecimalIeee754BitsRounding(bool signed, TValue significand, int exponent) + { + int numberDigits = TDecimal.CountDigits(significand); + + if (exponent < TDecimal.MinExponent) + { + int numberDigitsRemove = (TDecimal.MinExponent - exponent); + + if (numberDigitsRemove >= numberDigits) + { + return TDecimal.Zero; + } + + significand = RemoveDigitsAndRound(significand, numberDigitsRemove); + exponent += numberDigitsRemove; + + if (significand > TDecimal.MaxSignificand) + { + return ConstructorToDecimalIeee754BitsRounding(signed, TDecimal.MaxSignificand + TValue.One, exponent); + } + } + else if (exponent > TDecimal.MaxExponent) + { + int numberZeroDigits = exponent - TDecimal.MaxExponent; + + if (numberDigits + numberZeroDigits <= TDecimal.Precision) + { + exponent -= numberZeroDigits; + significand *= TDecimal.Power10(numberZeroDigits); + } + else + { + return signed ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + } + else if (numberDigits > TDecimal.Precision) + { + int numberDigitsRemove = numberDigits - TDecimal.Precision; + + if (exponent + numberDigitsRemove >= TDecimal.MaxExponent) + { + return signed ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + significand = RemoveDigitsAndRound(significand, numberDigitsRemove); + exponent += numberDigitsRemove; + + if (significand > TDecimal.MaxSignificand) + { + return ConstructorToDecimalIeee754BitsRounding(signed, TDecimal.MaxSignificand + TValue.One, exponent); + } + } + + return DecimalIeee754FiniteNumberBinaryEncoding(signed, significand, exponent); + } + + static TValue RemoveDigitsAndRound(TValue significand, int numberDigitsRemove) + { + (significand, TValue remainder) = TDecimal.DivRemPow10(significand, numberDigitsRemove); + + if (remainder == TValue.Zero) + { + return significand; + } + + TValue half = TValue.CreateTruncating(5) * TDecimal.Power10(numberDigitsRemove - 1); + + if (remainder > half || (remainder == half && TValue.IsOddInteger(significand))) + { + significand += TValue.One; + } + + return significand; + } + } + + 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); + } + + 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; + } + } + + bool isCurrentNegative = (currentDecimalBits & TDecimal.SignMask) != TValue.Zero; + bool isOtherNegative = (otherDecimalBits & TDecimal.SignMask) != TValue.Zero; + DecodedDecimalIeee754 current; + DecodedDecimalIeee754 other; + + if (isCurrentNegative) + { + if (!isOtherNegative) + { + return currentDecimalBits == TDecimal.NegativeZero && otherDecimalBits == TDecimal.Zero ? 0 : -1; + } + current = UnpackDecimalIeee754(otherDecimalBits); + other = UnpackDecimalIeee754(currentDecimalBits); + } + else if (isOtherNegative) + { + return currentDecimalBits == TDecimal.Zero && otherDecimalBits == TDecimal.NegativeZero ? 0 : 1; + } + else + { + current = UnpackDecimalIeee754(currentDecimalBits); + other = UnpackDecimalIeee754(otherDecimalBits); + } + + return InternalUnsignedCompare(current, other); + + // 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.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; + } + } + + private static TValue NumberToDecimalIeee754Bits(ref NumberBuffer number) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + Debug.Assert(number.Digits[0] != '0'); + Debug.Assert(number.DigitsCount != 0); + + if (number.DigitsCount > TDecimal.Precision) + { + return DecimalIeee754Rounding(ref number, TDecimal.Precision); + } + + 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.MinExponent) + { + int numberDigitsRemove = (TDecimal.MinExponent - exponent); + if (numberDigitsRemove < number.DigitsCount) + { + int numberDigitsRemain = number.DigitsCount - numberDigitsRemove; + return DecimalIeee754Rounding(ref number, numberDigitsRemain); + } + else + { + return number.IsNegative ? TDecimal.NegativeZero : TDecimal.Zero; + } + } + + TValue significand = TDecimal.NumberToSignificand(ref number, number.DigitsCount); + + return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, significand, exponent); + } + + /// + /// Encodes the given IEEE 754 decimal components into their finite number binary IEEE 754 + /// decimal interchange format (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 + { + exponent += TDecimal.ExponentBias; + + TValue value = TValue.Zero; + TValue exponentVal = TValue.CreateTruncating(exponent); + bool msbSignificand = (significand & TDecimal.MostSignificantBitOfSignificandMask) != TValue.Zero; + + if (signed) + { + value = TDecimal.SignMask; + } + + if (msbSignificand) + { + value |= TDecimal.G0G1Mask; + exponentVal <<= TDecimal.NumberBitsEncoding - TDecimal.NumberBitsExponent - 3; + value |= exponentVal; + significand ^= TDecimal.MostSignificantBitOfSignificandMask; + value |= significand; + } + else + { + exponentVal <<= TDecimal.NumberBitsEncoding - TDecimal.NumberBitsExponent - 1; + value |= exponentVal; + 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); + + TValue significand = TDecimal.NumberToSignificand(ref number, digits); + + 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 increaseOne = false; + int midPointValue = number.Digits[digits]; + + if (midPointValue > '5') + { + increaseOne = true; + } + 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') + { + increaseOne = true; + tiedToEvenRounding = false; + break; + } + ++index; + c = number.Digits[index]; + } + + if (tiedToEvenRounding && !int.IsEvenInteger(number.Digits[digits - 1] - '0')) + { + increaseOne = true; + } + } + + if (increaseOne) + { + if (significand == TDecimal.MaxSignificand) + { + significand = TDecimal.Power10(TDecimal.Precision - 1); + exponent += 1; + } + else + { + significand += TValue.One; + } + } + + if (exponent > TDecimal.MaxExponent) + { + return number.IsNegative ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + 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 9b2028fa0f48ff..6ba16446778077 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; @@ -329,6 +330,36 @@ private static ref byte GetTwoDigitsBytesRef(bool useChars) => ref MemoryMarshal.GetReference(useChars ? TwoDigitsCharsAsBytes : TwoDigitsBytes); #endif + public static unsafe string FormatDecimalIeee754(TDecimal value, ReadOnlySpan format, NumberFormatInfo info) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + char fmt = ParseFormatSpecifier(format, out int digits); + byte[] buffer = ArrayPool.Shared.Rent(TDecimal.BufferLength); + + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, buffer); + + DecimalIeee754ToNumber(value, ref number); + char* stackPtr = stackalloc char[CharStackBufferSize]; + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); + + if (fmt != 0) + { + NumberToString(ref vlb, ref number, fmt, digits, info); + } + else + { + NumberToStringFormat(ref vlb, ref number, format, info); + } + + string result = vlb.AsSpan().ToString(); + vlb.Dispose(); + if (buffer != null) + { + ArrayPool.Shared.Return(buffer); + } + return result; + } public static unsafe string FormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info) { @@ -384,6 +415,46 @@ public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan(TDecimal value, ref NumberBuffer number) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + DecodedDecimalIeee754 unpackDecimal = value.Unpack(); + number.IsNegative = unpackDecimal.Signed; + + if (unpackDecimal.Significand == TValue.Zero) + { + return; + } + + string significand = TDecimal.ToDecStr(unpackDecimal.Significand); + + for (int i = 0; i < significand.Length; i++) + { + number.Digits[i] = (byte)significand[i]; + } + + number.Scale = TValue.IsZero(unpackDecimal.Significand) ? 0 : significand.Length + unpackDecimal.UnbiasedExponent; + + if (unpackDecimal.UnbiasedExponent > 0) + { + number.DigitsCount = significand.Length + unpackDecimal.UnbiasedExponent; + + for (int i = 0; i < unpackDecimal.UnbiasedExponent; i++) + { + number.Digits[significand.Length + i] = (byte)'0'; + } + } + else + { + number.DigitsCount = significand.Length; + } + + number.Digits[number.DigitsCount] = (byte)'\0'; + + number.CheckConsistency(); + } + internal static unsafe void DecimalToNumber(scoped ref decimal d, ref NumberBuffer number) { byte* buffer = number.DigitsPtr; @@ -1590,7 +1661,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; @@ -2055,7 +2126,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; @@ -2467,7 +2538,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 92535633232725..dda55d21a01baa 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs @@ -696,7 +696,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); @@ -890,7 +890,7 @@ private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger } // get 32-bit integer from at most 9 digits - private static uint DigitsToUInt32(byte* p, int count) + internal static uint DigitsToUInt32(byte* p, int count) { Debug.Assert((1 <= count) && (count <= 9)); @@ -914,7 +914,7 @@ 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) + 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 3748792c1784d3..69eb2836d66786 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,44 @@ 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 MaxScale { get; } + static abstract int MinScale { get; } + static abstract int BufferLength { get; } + static abstract int MaxExponent { get; } + static abstract int MinExponent { get; } + 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); + Number.DecodedDecimalIeee754 Unpack(); + static abstract TSelf Construct(TValue value); + static abstract int CountDigits(TValue significand); + static abstract int NumberBitsEncoding { get; } + static abstract int NumberBitsExponent { 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); + } + internal static partial class Number { private const int Int32Precision = 10; @@ -767,6 +805,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 +971,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.OK; + } + + 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 { @@ -1135,5 +1269,27 @@ 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; + + if ((number.DigitsCount == 0) || (number.Scale < TDecimal.MinScale)) + { + value = number.IsNegative ? TDecimal.NegativeZero : TDecimal.Zero; + } + else if (number.Scale > TDecimal.MaxScale) + { + value = number.IsNegative ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + else + { + 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..63170c22c2b189 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -0,0 +1,380 @@ +// 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 = 6111; + private const int MinExponent = -6176; + private const int Precision = 34; + private const int ExponentBias = 6176; + private const int NumberBitsExponent = 14; + private static UInt128 PositiveInfinityValue => new UInt128(upper: 0x7800_0000_0000_0000, lower: 0); + private static UInt128 NegativeInfinityValue => new UInt128(upper: 0xf800_0000_0000_0000, lower: 0); + private static UInt128 ZeroValue => new UInt128(0, 0); + private static UInt128 NegativeZeroValue => new UInt128(0x8000_0000_0000_0000, 0); + private static UInt128 QuietNaNValue => new UInt128(0x7C00_0000_0000_0000, 0); + private static UInt128 MaxInternalValue = new UInt128(upper: 0x5FFF_ED09_BEAD_87C0, lower: 0x378D_8E63_FFFF_FFFF); + private static UInt128 MinInternalValue = new UInt128(upper: 0xDFFF_ED09_BEAD_87C0, lower: 0x378D_8E63_FFFF_FFFF); + + private const ulong NaNMaskUpper = 0x7C00_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) + { + UInt128 value = Number.ConstructorToDecimalIeee754Bits(significand < 0, (UInt128)(significand < 0 ? -significand : significand), 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.Number, 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.Number, 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.Number, 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.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(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.Number, 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.Number, provider: null, out result); + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) => TryParse(s, NumberStyles.Number, 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.Number, 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.ValidateParseStyleFloatingPoint(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.ValidateParseStyleFloatingPoint(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 && Equals((Decimal128)obj); + } + + /// + /// Serves as the default hash function. + /// + public override int GetHashCode() + { + return new UInt128(_upper, _lower).GetHashCode(); + } + + /// + /// Returns a string representation of the current value. + /// + public override string ToString() + { + return Number.FormatDecimalIeee754(this, 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(this, format, NumberFormatInfo.CurrentInfo); + } + + /// + /// Returns a string representation of the current value with the specified . + /// + public string ToString(IFormatProvider? provider) + { + return Number.FormatDecimalIeee754(this, 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(this, format, NumberFormatInfo.GetInstance(provider)); + } + + private static 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 int IDecimalIeee754ParseAndFormatInfo.MaxScale => 6145; + + static int IDecimalIeee754ParseAndFormatInfo.MinScale => -6175; + + static string IDecimalIeee754ParseAndFormatInfo.ToDecStr(UInt128 significand) + { + return Number.UInt128ToDecStr(significand); + } + + Number.DecodedDecimalIeee754 IDecimalIeee754ParseAndFormatInfo.Unpack() + { + return Number.UnpackDecimalIeee754(new UInt128(_upper, _lower)); + } + + 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 int IDecimalIeee754ParseAndFormatInfo.NumberBitsExponent => NumberBitsExponent; + + static UInt128 IDecimalIeee754ParseAndFormatInfo.SignMask => new UInt128(0x8000_0000_0000_0000, 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; + } + } +} 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..376147b01c22c1 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -0,0 +1,341 @@ +// 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 = 90; + private const int MinExponent = -101; + private const int Precision = 7; + private const int ExponentBias = 101; + private const int NumberBitsExponent = 8; + 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 = 0x7C00_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 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) + { + _value = Number.ConstructorToDecimalIeee754Bits(significand < 0, (uint)Math.Abs(significand), 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.Number, 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.Number, 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.Number, 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.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(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.Number, 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.Number, provider: null, out result); + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) => TryParse(s, NumberStyles.Number, 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.Number, 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.ValidateParseStyleFloatingPoint(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.ValidateParseStyleFloatingPoint(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 && Equals((Decimal32)obj); + } + + /// + /// Serves as the default hash function. + /// + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + /// + /// Returns a string representation of the current value. + /// + public override string ToString() + { + return Number.FormatDecimalIeee754(this, 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(this, format, NumberFormatInfo.CurrentInfo); + } + + /// + /// Returns a string representation of the current value with the specified . + /// + public string ToString(IFormatProvider? provider) + { + return Number.FormatDecimalIeee754(this, 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(this, 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); + } + + Number.DecodedDecimalIeee754 IDecimalIeee754ParseAndFormatInfo.Unpack() + { + return Number.UnpackDecimalIeee754(_value); + } + + 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.MaxScale => 97; + + static int IDecimalIeee754ParseAndFormatInfo.MinScale => -100; + + 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 int IDecimalIeee754ParseAndFormatInfo.NumberBitsExponent => NumberBitsExponent; + + 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; + } + } +} 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..d343206b237e4f --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -0,0 +1,344 @@ +// 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 = 369; + private const int MinExponent = -398; + private const int Precision = 16; + private const int ExponentBias = 398; + private const int NumberBitsExponent = 10; + 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 = 0x7C00_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 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) + { + _value = Number.ConstructorToDecimalIeee754Bits(significand < 0, (ulong)Math.Abs(significand), 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.Number, 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.Number, 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.Number, 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.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(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.Number, 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.Number, provider: null, out result); + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) => TryParse(s, NumberStyles.Number, 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.Number, 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.ValidateParseStyleFloatingPoint(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.ValidateParseStyleFloatingPoint(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 && Equals((Decimal64)obj); + } + + /// + /// Serves as the default hash function. + /// + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + /// + /// Returns a string representation of the current value. + /// + public override string ToString() + { + return Number.FormatDecimalIeee754(this, 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(this, format, NumberFormatInfo.CurrentInfo); + } + + /// + /// Returns a string representation of the current value with the specified . + /// + public string ToString(IFormatProvider? provider) + { + return Number.FormatDecimalIeee754(this, 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(this, 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); + } + + Number.DecodedDecimalIeee754 IDecimalIeee754ParseAndFormatInfo.Unpack() + { + return Number.UnpackDecimalIeee754(_value); + } + + 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.CountDigits(ulong significand) => FormattingHelpers.CountDigits(significand); + + static int IDecimalIeee754ParseAndFormatInfo.MaxScale => 385; + + static int IDecimalIeee754ParseAndFormatInfo.MinScale => -397; + + static int IDecimalIeee754ParseAndFormatInfo.MaxExponent => MaxExponent; + + static int IDecimalIeee754ParseAndFormatInfo.MinExponent => MinExponent; + + static ulong IDecimalIeee754ParseAndFormatInfo.PositiveInfinity => PositiveInfinityValue; + + static ulong IDecimalIeee754ParseAndFormatInfo.NegativeInfinity => NegativeInfinityValue; + + static ulong IDecimalIeee754ParseAndFormatInfo.Zero => ZeroValue; + + static ulong IDecimalIeee754ParseAndFormatInfo.NegativeZero => NegativeZeroValue; + + static ulong IDecimalIeee754ParseAndFormatInfo.NaN => QuietNaNValue; + + static ulong IDecimalIeee754ParseAndFormatInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsEncoding => 64; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsExponent => NumberBitsExponent; + + static ulong IDecimalIeee754ParseAndFormatInfo.SignMask => SignMask; + + static ulong IDecimalIeee754ParseAndFormatInfo.G0G1Mask => G0G1Mask; + + static int IDecimalIeee754ParseAndFormatInfo.ExponentBias => ExponentBias; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsSignificand => 50; + + static ulong IDecimalIeee754ParseAndFormatInfo.G0ToGwPlus1ExponentMask => 0x7FE0_0000_0000_0000; + + static ulong IDecimalIeee754ParseAndFormatInfo.G2ToGwPlus3ExponentMask => 0x1FF8_0000_0000_0000; + + static ulong IDecimalIeee754ParseAndFormatInfo.GwPlus2ToGwPlus4SignificandMask => 0x001F_FFFF_FFFF_FFFF; + + static ulong IDecimalIeee754ParseAndFormatInfo.GwPlus4SignificandMask => 0x0007_FFFF_FFFF_FFFF; + + static ulong IDecimalIeee754ParseAndFormatInfo.MaxSignificand => MaxSignificand; + + static bool IDecimalIeee754ParseAndFormatInfo.IsNaN(ulong decimalBits) + { + return (decimalBits & NaNMask) == NaNMask; + } + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index ffa9bf58bdcecf..5ceb6963ed1722 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11253,6 +11253,129 @@ public static void HtmlEncode(string? value, System.IO.TextWriter output) { } } namespace System.Numerics { + public readonly struct Decimal32 + : System.IComparable, + System.IComparable, + System.IEquatable, + System.ISpanParsable, + System.Numerics.IMinMaxValue + { + public Decimal32(int significand, int exponent) { throw null; } + + public int CompareTo(object? value) { throw null; } + public int CompareTo(Decimal32 other) { throw null; } + public bool Equals(Decimal32 other) { throw null; } + + public static Decimal32 Parse(string s) { throw null; } + public static Decimal32 Parse(string s, System.Globalization.NumberStyles style) { throw null; } + public static Decimal32 Parse(ReadOnlySpan s, IFormatProvider? provider) { throw null; } + public static Decimal32 Parse(string s, IFormatProvider? provider) { throw null; } + public static Decimal32 Parse(ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Number, IFormatProvider? provider = null) { throw null; } + public static Decimal32 Parse(string s, System.Globalization.NumberStyles style, IFormatProvider? provider) { throw null; } + + public static Decimal32 NaN { get { throw null; } } + public static Decimal32 NegativeInfinity { get { throw null; } } + public static Decimal32 PositiveInfinity { get { throw null; } } + public static Decimal32 NegativeZero { get { throw null; } } + public static Decimal32 Zero { get { throw null; } } + public static Decimal32 MaxValue { get { throw null; } } + public static Decimal32 MinValue { get { throw null; } } + + public override string ToString() { throw null; } + public string ToString(System.IFormatProvider? provider) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)] string? format) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)] string? format, System.IFormatProvider? provider) { throw null; } + + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, out Decimal32 result) { throw null; } + public static bool TryParse(ReadOnlySpan s, out Decimal32 result) { throw null; } + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal32 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal32 result) { throw null; } + public static bool TryParse(ReadOnlySpan s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal32 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal32 result) { throw null; } + } + + public readonly struct Decimal64 + : System.IComparable, + System.IComparable, + System.IEquatable, + System.ISpanParsable, + System.Numerics.IMinMaxValue + { + public Decimal64(long significand, int exponent) { throw null; } + + public int CompareTo(object? value) { throw null; } + public int CompareTo(Decimal64 other) { throw null; } + public bool Equals(Decimal64 other) { throw null; } + + public static Decimal64 Parse(string s) { throw null; } + public static Decimal64 Parse(string s, System.Globalization.NumberStyles style) { throw null; } + public static Decimal64 Parse(ReadOnlySpan s, IFormatProvider? provider) { throw null; } + public static Decimal64 Parse(string s, IFormatProvider? provider) { throw null; } + public static Decimal64 Parse(ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Number, IFormatProvider? provider = null) { throw null; } + public static Decimal64 Parse(string s, System.Globalization.NumberStyles style, IFormatProvider? provider) { throw null; } + + public static Decimal64 NaN { get { throw null; } } + public static Decimal64 NegativeInfinity { get { throw null; } } + public static Decimal64 PositiveInfinity { get { throw null; } } + public static Decimal64 NegativeZero { get { throw null; } } + public static Decimal64 Zero { get { throw null; } } + public static Decimal64 MaxValue { get { throw null; } } + public static Decimal64 MinValue { get { throw null; } } + + public override string ToString() { throw null; } + public string ToString(System.IFormatProvider? provider) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)] string? format) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)] string? format, System.IFormatProvider? provider) { throw null; } + + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, out Decimal64 result) { throw null; } + public static bool TryParse(ReadOnlySpan s, out Decimal64 result) { throw null; } + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal64 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal64 result) { throw null; } + public static bool TryParse(ReadOnlySpan s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal64 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal64 result) { throw null; } + } + + public readonly struct Decimal128 + : System.IComparable, + System.IComparable, + System.IEquatable, + System.ISpanParsable, + System.Numerics.IMinMaxValue + { + public Decimal128(Int128 significand, int exponent) { throw null; } + + public int CompareTo(object? value) { throw null; } + public int CompareTo(Decimal128 other) { throw null; } + public bool Equals(Decimal128 other) { throw null; } + + public static Decimal128 Parse(string s) { throw null; } + public static Decimal128 Parse(string s, System.Globalization.NumberStyles style) { throw null; } + public static Decimal128 Parse(ReadOnlySpan s, IFormatProvider? provider) { throw null; } + public static Decimal128 Parse(string s, IFormatProvider? provider) { throw null; } + public static Decimal128 Parse(ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.Number, IFormatProvider? provider = null) { throw null; } + public static Decimal128 Parse(string s, System.Globalization.NumberStyles style, IFormatProvider? provider) { throw null; } + + public static Decimal128 NaN { get { throw null; } } + public static Decimal128 NegativeInfinity { get { throw null; } } + public static Decimal128 PositiveInfinity { get { throw null; } } + public static Decimal128 NegativeZero { get { throw null; } } + public static Decimal128 Zero { get { throw null; } } + public static Decimal128 MaxValue { get { throw null; } } + public static Decimal128 MinValue { get { throw null; } } + + public override string ToString() { throw null; } + public string ToString(System.IFormatProvider? provider) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)] string? format) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute(System.Diagnostics.CodeAnalysis.StringSyntaxAttribute.NumericFormat)] string? format, System.IFormatProvider? provider) { throw null; } + + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, out Decimal128 result) { throw null; } + public static bool TryParse(ReadOnlySpan s, out Decimal128 result) { throw null; } + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal128 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal128 result) { throw null; } + public static bool TryParse(ReadOnlySpan s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal128 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] string? s, System.Globalization.NumberStyles style, IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhen(false)] out Decimal128 result) { throw null; } + } + public readonly partial struct BFloat16 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; @@ -11492,6 +11615,7 @@ namespace System.Numerics public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, out System.Numerics.BFloat16 result) { throw null; } public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.Numerics.BFloat16 result) { throw null; } } + public static partial class BitOperations { [System.CLSCompliantAttribute(false)] diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System.Runtime.Tests.csproj b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System.Runtime.Tests.csproj index ce7a42a7f69699..1c4975070e3bc7 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System.Runtime.Tests.csproj +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System.Runtime.Tests.csproj @@ -79,6 +79,9 @@ + + + diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs new file mode 100644 index 00000000000000..5ebfb6c475562e --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -0,0 +1,383 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using Xunit; + +namespace System.Tests +{ + public class Decimal128Tests + { + public static IEnumerable Parse_Valid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Number; + NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; + + NumberFormatInfo emptyFormat = NumberFormatInfo.CurrentInfo; + + var customFormat1 = new NumberFormatInfo(); + customFormat1.CurrencySymbol = "$"; + customFormat1.CurrencyGroupSeparator = ","; + + var customFormat2 = new NumberFormatInfo(); + customFormat2.NumberDecimalSeparator = "."; + + yield return new object[] { "-123", defaultStyle, null, new Decimal128(-123, 0) }; + yield return new object[] { "0", defaultStyle, null, new Decimal128(0, 0) }; + yield return new object[] { "123", defaultStyle, null, new Decimal128(123, 0) }; + yield return new object[] { " 123 ", defaultStyle, null, new Decimal128(123, 0) }; + yield return new object[] { (567.89).ToString(), defaultStyle, null, new Decimal128(56789, -2) }; + yield return new object[] { (-567.89).ToString(), defaultStyle, null, new Decimal128(-56789, -2) }; + yield return new object[] { "0.666666666666666666666666666666666650000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, new Decimal128(Int128.Parse(new string('6', 34)), -34) }; + + yield return new object[] { "0." + new string('0', 6176) + "1", defaultStyle, invariantFormat, new Decimal128(0, 0) }; + yield return new object[] { "-0." + new string('0', 6176) + "1", defaultStyle, invariantFormat, new Decimal128(0, 0) }; + yield return new object[] { "0." + new string('0', 6175) + "1", defaultStyle, invariantFormat, new Decimal128(1, -6176) }; + yield return new object[] { "-0." + new string('0', 6175) + "1", defaultStyle, invariantFormat, new Decimal128(-1, -6176) }; + + yield return new object[] { "0." + new string('0', 6174) + "12345", defaultStyle, invariantFormat, new Decimal128(12, -6176) }; + yield return new object[] { "-0." + new string('0', 6174) + "12345", defaultStyle, invariantFormat, new Decimal128(-12, -6176) }; + yield return new object[] { "0." + new string('0', 6174) + "12562", defaultStyle, invariantFormat, new Decimal128(13, -6176) }; + yield return new object[] { "-0." + new string('0', 6174) + "12562", defaultStyle, invariantFormat, new Decimal128(-13, -6176) }; + + yield return new object[] { emptyFormat.NumberDecimalSeparator + "234", defaultStyle, null, new Decimal128(234, -3) }; + yield return new object[] { "234" + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal128(234, 0) }; + yield return new object[] { "7" + new string('0', 6144) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal128(7, 6144) }; + yield return new object[] { "07" + new string('0', 6144) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal128(7, 6144) }; + + yield return new object[] { (123.1).ToString(), NumberStyles.AllowDecimalPoint, null, new Decimal128(1231, -1) }; + yield return new object[] { 1000.ToString("N0"), NumberStyles.AllowThousands, null, new Decimal128(1000, 0) }; + + yield return new object[] { "123", NumberStyles.Any, emptyFormat, new Decimal128(123, 0) }; + yield return new object[] { (123.567).ToString(), NumberStyles.Any, emptyFormat, new Decimal128(123567, -3) }; + yield return new object[] { "123", NumberStyles.Float, emptyFormat, new Decimal128(123, 0) }; + yield return new object[] { "$1000", NumberStyles.Currency, customFormat1, new Decimal128(1, 3) }; + yield return new object[] { "123.123", NumberStyles.Float, customFormat2, new Decimal128(123123, -3) }; + yield return new object[] { "(123)", NumberStyles.AllowParentheses, customFormat2, new Decimal128(-123, 0) }; + + yield return new object[] { "NaN", NumberStyles.Any, invariantFormat, Decimal128.NaN }; + yield return new object[] { "+NaN", NumberStyles.Any, invariantFormat, Decimal128.NaN }; + yield return new object[] { "Infinity", NumberStyles.Any, invariantFormat, Decimal128.PositiveInfinity }; + yield return new object[] { "+Infinity", NumberStyles.Any, invariantFormat, Decimal128.PositiveInfinity }; + yield return new object[] { "-Infinity", NumberStyles.Any, invariantFormat, Decimal128.NegativeInfinity }; + } + + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse(string value, NumberStyles style, IFormatProvider provider, Decimal128 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal128 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal128.TryParse(value, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal128.Parse(value)); + } + + Assert.Equal(expected, Decimal128.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal128.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal128.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal128.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal128.Parse(value, style)); + Assert.Equal(expected, Decimal128.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_Invalid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Number; + + var customFormat = new NumberFormatInfo(); + customFormat.CurrencySymbol = "$"; + customFormat.NumberDecimalSeparator = "."; + + yield return new object[] { null, defaultStyle, null, typeof(ArgumentNullException) }; + yield return new object[] { "", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { " ", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { "Garbage", defaultStyle, null, typeof(FormatException) }; + + yield return new object[] { "ab", defaultStyle, null, typeof(FormatException) }; // Hex value + yield return new object[] { "(123)", defaultStyle, null, typeof(FormatException) }; // Parentheses + yield return new object[] { 100.ToString("C0"), defaultStyle, null, typeof(FormatException) }; // Currency + + yield return new object[] { (123.456m).ToString(), NumberStyles.Integer, null, typeof(FormatException) }; // Decimal + yield return new object[] { " " + (123.456m).ToString(), NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { (123.456m).ToString() + " ", NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { "1E23", NumberStyles.None, null, typeof(FormatException) }; // Exponent + + yield return new object[] { "ab", NumberStyles.None, null, typeof(FormatException) }; // Hex value + yield return new object[] { " 123 ", NumberStyles.None, null, typeof(FormatException) }; // Trailing and leading whitespace + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal128 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite)) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.False(Decimal128.TryParse(value, out result)); + Assert.Equal(default(Decimal128), result); + + Assert.Throws(exceptionType, () => Decimal128.Parse(value)); + } + + Assert.Throws(exceptionType, () => Decimal128.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal128.TryParse(value, style, provider, out result)); + Assert.Equal(default(Decimal128), result); + + Assert.Throws(exceptionType, () => Decimal128.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal128.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(default(Decimal128), result); + + Assert.Throws(exceptionType, () => Decimal128.Parse(value, style)); + Assert.Throws(exceptionType, () => Decimal128.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_ValidWithOffsetCount_TestData() + { + foreach (object[] inputs in Parse_Valid_TestData()) + { + yield return new object[] { inputs[0], 0, ((string)inputs[0]).Length, inputs[1], inputs[2], inputs[3] }; + } + + yield return new object[] { "-123", 1, 3, NumberStyles.Number, null, new Decimal128(123, 0) }; + yield return new object[] { "-123", 0, 3, NumberStyles.Number, null, new Decimal128(-12, 0) }; + yield return new object[] { 1000.ToString("N0"), 0, 4, NumberStyles.AllowThousands, null, new Decimal128(100, 0) }; + yield return new object[] { 1000.ToString("N0"), 2, 3, NumberStyles.AllowThousands, null, new Decimal128(0, 0) }; + yield return new object[] { "(123)", 1, 3, NumberStyles.AllowParentheses, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal128(123, 0) }; + yield return new object[] { "1234567890123456789012345.678456", 1, 4, NumberStyles.Number, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal128(2345, 0) }; + } + + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, Decimal128 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal128 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal128.TryParse(value.AsSpan(offset, count), out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal128.Parse(value.AsSpan(offset, count))); + } + + Assert.Equal(expected, Decimal128.Parse(value.AsSpan(offset, count), provider: provider)); + } + + Assert.Equal(expected, Decimal128.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(Decimal128.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + Assert.Throws(exceptionType, () => Decimal128.Parse(value.AsSpan(), style, provider)); + + Assert.False(Decimal128.TryParse(value.AsSpan(), style, provider, out Decimal128 result)); + Assert.Equal(default, result); + } + } + + [Fact] + public static void Midpoint_Rounding() + { + var number = new Decimal128(Int128.Parse("12345688888888881234568888888888885"), 0); + Assert.Equal(new Decimal128(Int128.Parse("1234568888888888123456888888888888"), 1), number); + } + + [Fact] + public static void Rounding() + { + var number = new Decimal128(Int128.Parse("12345677777777771234567777777777778"), 0); + Assert.Equal(new Decimal128(Int128.Parse("1234567777777777123456777777777778"), 1), number); + + number = new Decimal128(Int128.Parse("12345677777777771234567777777777771"), 0); + Assert.Equal(new Decimal128(Int128.Parse("1234567777777777123456777777777777"), 1), number); + + number = new Decimal128(Int128.Parse("12345677777777771234567777777777771"), -6177); + Assert.Equal(new Decimal128(Int128.Parse("1234567777777777123456777777777777"), -6176), number); + } + + [Fact] + public static void MaxValue_Rounding() + { + Assert.Equal(Decimal128.MaxValue, Decimal128.Parse(new string('9', 34) + '4' + new string('0', 6110))); + Assert.Equal(Decimal128.PositiveInfinity, Decimal128.Parse(new string('9', 34) + '5' + new string('0', 6110))); + Assert.Equal(Decimal128.PositiveInfinity, Decimal128.Parse(new string('9', 34) + '5' + new string('0', 6109) + '1')); + } + + [Theory] + [MemberData(nameof(CompareTo_Other_ReturnsExpected_TestData))] + public static void CompareTo_Other_ReturnsExpected(Decimal128 d1, Decimal128 d2, int expected) + { + Assert.Equal(expected, d1.CompareTo(d2)); + if (expected == 0) + { + Assert.Equal(d1, d2); + Assert.Equal(d2, d1); + } + else + { + Assert.Equal(-expected, d2.CompareTo(d1)); + Assert.NotEqual(d1, d2); + Assert.NotEqual(d2, d1); + } + } + + public static IEnumerable CompareTo_Other_ReturnsExpected_TestData() + { + yield return new object[] { new Decimal128(-1, 1), new Decimal128(-10, 0), 0 }; + yield return new object[] { new Decimal128(1, 6144), new Decimal128(10, 6143), 0 }; + yield return new object[] { new Decimal128(Int128.Parse(new string('9', 33)), 6111), new Decimal128(Int128.Parse(new string('9', 33) + "0"), 6110), 0 }; + yield return new object[] { Decimal128.Parse(new string('9', 33) + new string('0', 6111)), new Decimal128(Int128.Parse(new string('9', 32) + "8"), 6110), 1 }; + yield return new object[] { Decimal128.Parse(new string('9', 25) + new string('0', 6111)), new Decimal128(Int128.Parse(new string('9', 24) + "8"), 6110), 1 }; + yield return new object[] { new Decimal128(1, 1), new Decimal128(-1, 0), 1 }; + yield return new object[] { new Decimal128(10, 0), new Decimal128(-1, 1), 1 }; + yield return new object[] { new Decimal128(10, 0), Decimal128.NaN, 1 }; + yield return new object[] { new Decimal128(10, 0), Decimal128.NegativeInfinity, 1 }; + yield return new object[] { new Decimal128(10, 0), Decimal128.NegativeZero, 1 }; + yield return new object[] { Decimal128.PositiveInfinity, new Decimal128(10, 20), 1 }; + yield return new object[] { Decimal128.PositiveInfinity, new Decimal128(10, 7500), 0 }; + yield return new object[] { Decimal128.PositiveInfinity, Decimal128.NegativeInfinity, 1 }; + yield return new object[] { Decimal128.PositiveInfinity, Decimal128.PositiveInfinity, 0 }; + yield return new object[] { Decimal128.PositiveInfinity, Decimal128.NegativeZero, 1 }; + yield return new object[] { Decimal128.NegativeInfinity, Decimal128.NegativeInfinity, 0 }; + yield return new object[] { Decimal128.NaN, Decimal128.NaN, 0 }; + yield return new object[] { Decimal128.NegativeZero, Decimal128.NegativeInfinity, 1 }; + yield return new object[] { Decimal128.NegativeZero, new Decimal128(0, 20), 0 }; + yield return new object[] { Decimal128.NegativeZero, Decimal128.NaN, 1 }; + for (int i = 1; i < 30; i++) + { + var d1 = new Decimal128(1, i); + var d2 = new Decimal128(Int128.Parse("1" + new string('0', i)), 0); + yield return new object[] { d1, d2, 0 }; + } + } + + [Fact] + public static void CompareToTest() + { + var d1 = new Decimal128(-1, 1); + var d2 = new Decimal128(-10, 0); + Assert.Equal(0, d1.CompareTo(d2)); + + d1 = new Decimal128(1, 1); + d2 = new Decimal128(-1, 0); + Assert.Equal(1, d1.CompareTo(d2)); + Assert.Equal(-1, d2.CompareTo(d1)); + } + + [Fact] + public static void GetHashCodeTest() + { + var d = new Decimal128(10, 20); + Assert.Equal(d.GetHashCode(), d.GetHashCode()); + } + + [Fact] + public static void CompareToZero() + { + var zero = new Decimal128(0, 1); + Assert.Equal(zero, new Decimal128(0, 20)); + Assert.Equal(zero, new Decimal128(1, -6177)); + Assert.Equal(zero, new Decimal128(234, -10000)); + Assert.Equal(zero, new Decimal128(-1, -6177)); + Assert.Equal(zero, new Decimal128(-234, -10000)); + } + public static IEnumerable ToString_TestData() + { + foreach (NumberFormatInfo defaultFormat in new[] { null, NumberFormatInfo.CurrentInfo }) + { + yield return new object[] { new Decimal128(3, 6144), "G", defaultFormat, "3" + new string('0', 6144) }; + yield return new object[] { new Decimal128(-3, 6144), "G", defaultFormat, "-3" + new string('0', 6144) }; + yield return new object[] { new Decimal128(-4567, 0), "G", defaultFormat, "-4567" }; + yield return new object[] { new Decimal128(-4567891, -3), "G", defaultFormat, "-4567.891" }; + yield return new object[] { new Decimal128(0, 0), "G", defaultFormat, "0" }; + yield return new object[] { new Decimal128(4567, 0), "G", defaultFormat, "4567" }; + yield return new object[] { new Decimal128(4567891, -3), "G", defaultFormat, "4567.891" }; + + yield return new object[] { new Decimal128(2468, 0), "N", defaultFormat, "2,468.00" }; + + yield return new object[] { new Decimal128(2467, 0), "[#-##-#]", defaultFormat, "[2-46-7]" }; + + } + } + + [Fact] + public static void Test_ToString() + { + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + foreach (object[] testdata in ToString_TestData()) + { + ToString((Decimal128)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); + } + } + } + + private static void ToString(Decimal128 f, string format, IFormatProvider provider, string expected) + { + bool isDefaultProvider = provider == null; + if (string.IsNullOrEmpty(format) || format.ToUpperInvariant() == "G") + { + if (isDefaultProvider) + { + Assert.Equal(expected, f.ToString()); + Assert.Equal(expected, f.ToString((IFormatProvider)null)); + } + Assert.Equal(expected, f.ToString(provider)); + } + if (isDefaultProvider) + { + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant())); // If format is upper case, then exponents are printed in upper case + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant())); // If format is lower case, then exponents are printed in lower case + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), null)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), null)); + } + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), provider)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), provider)); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs new file mode 100644 index 00000000000000..2239acab8f5fa3 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -0,0 +1,368 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using Xunit; + +namespace System.Tests +{ + public class Decimal32Tests + { + public static IEnumerable Parse_Valid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Number; + NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; + + NumberFormatInfo emptyFormat = NumberFormatInfo.CurrentInfo; + + var customFormat1 = new NumberFormatInfo(); + customFormat1.CurrencySymbol = "$"; + customFormat1.CurrencyGroupSeparator = ","; + + var customFormat2 = new NumberFormatInfo(); + customFormat2.NumberDecimalSeparator = "."; + + yield return new object[] { "-123", defaultStyle, null, new Decimal32(-123, 0) }; + yield return new object[] { "0", defaultStyle, null, new Decimal32(0, 0) }; + yield return new object[] { "123", defaultStyle, null, new Decimal32(123, 0) }; + yield return new object[] { " 123 ", defaultStyle, null, new Decimal32(123, 0) }; + yield return new object[] { (567.89).ToString(), defaultStyle, null, new Decimal32(56789, -2) }; + yield return new object[] { (-567.89).ToString(), defaultStyle, null, new Decimal32(-56789, -2) }; + yield return new object[] { "0.6666666500000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, new Decimal32(6666666, -7) }; + + yield return new object[] { "0." + new string('0', 101) + "1", defaultStyle, invariantFormat, new Decimal32(0, 0) }; + yield return new object[] { "-0." + new string('0', 101) + "1", defaultStyle, invariantFormat, new Decimal32(0, 0) }; + yield return new object[] { "0." + new string('0', 100) + "1", defaultStyle, invariantFormat, new Decimal32(1, -101) }; + yield return new object[] { "-0." + new string('0', 100) + "1", defaultStyle, invariantFormat, new Decimal32(-1, -101) }; + + yield return new object[] { "0." + new string('0', 99) + "12345", defaultStyle, invariantFormat, new Decimal32(12, -101) }; + yield return new object[] { "-0." + new string('0', 99) + "12345", defaultStyle, invariantFormat, new Decimal32(-12, -101) }; + yield return new object[] { "0." + new string('0', 99) + "12562", defaultStyle, invariantFormat, new Decimal32(13, -101) }; + yield return new object[] { "-0." + new string('0', 99) + "12562", defaultStyle, invariantFormat, new Decimal32(-13, -101) }; + + yield return new object[] { emptyFormat.NumberDecimalSeparator + "234", defaultStyle, null, new Decimal32(234, -3) }; + yield return new object[] { "234" + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal32(234, 0) }; + yield return new object[] { "7" + new string('0', 96) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal32(7, 96) }; + yield return new object[] { "07" + new string('0', 96) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal32(7, 96) }; + + yield return new object[] { (123.1).ToString(), NumberStyles.AllowDecimalPoint, null, new Decimal32(1231, -1) }; + yield return new object[] { 1000.ToString("N0"), NumberStyles.AllowThousands, null, new Decimal32(1000, 0) }; + + yield return new object[] { "123", NumberStyles.Any, emptyFormat, new Decimal32(123, 0) }; + yield return new object[] { (123.567).ToString(), NumberStyles.Any, emptyFormat, new Decimal32(123567, -3) }; + yield return new object[] { "123", NumberStyles.Float, emptyFormat, new Decimal32(123, 0) }; + yield return new object[] { "$1000", NumberStyles.Currency, customFormat1, new Decimal32(1, 3) }; + yield return new object[] { "123.123", NumberStyles.Float, customFormat2, new Decimal32(123123, -3) }; + yield return new object[] { "(123)", NumberStyles.AllowParentheses, customFormat2, new Decimal32(-123, 0) }; + + yield return new object[] { "NaN", NumberStyles.Any, invariantFormat, Decimal32.NaN }; + yield return new object[] { "+NaN", NumberStyles.Any, invariantFormat, Decimal32.NaN }; + yield return new object[] { "Infinity", NumberStyles.Any, invariantFormat, Decimal32.PositiveInfinity }; + yield return new object[] { "+Infinity", NumberStyles.Any, invariantFormat, Decimal32.PositiveInfinity }; + yield return new object[] { "-Infinity", NumberStyles.Any, invariantFormat, Decimal32.NegativeInfinity }; + } + + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse(string value, NumberStyles style, IFormatProvider provider, Decimal32 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal32 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal32.TryParse(value, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal32.Parse(value)); + } + + Assert.Equal(expected, Decimal32.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal32.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal32.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal32.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal32.Parse(value, style)); + Assert.Equal(expected, Decimal32.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_Invalid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Number; + + var customFormat = new NumberFormatInfo(); + customFormat.CurrencySymbol = "$"; + customFormat.NumberDecimalSeparator = "."; + + yield return new object[] { null, defaultStyle, null, typeof(ArgumentNullException) }; + yield return new object[] { "", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { " ", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { "Garbage", defaultStyle, null, typeof(FormatException) }; + + yield return new object[] { "ab", defaultStyle, null, typeof(FormatException) }; // Hex value + yield return new object[] { "(123)", defaultStyle, null, typeof(FormatException) }; // Parentheses + yield return new object[] { 100.ToString("C0"), defaultStyle, null, typeof(FormatException) }; // Currency + + yield return new object[] { (123.456m).ToString(), NumberStyles.Integer, null, typeof(FormatException) }; // Decimal + yield return new object[] { " " + (123.456m).ToString(), NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { (123.456m).ToString() + " ", NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { "1E23", NumberStyles.None, null, typeof(FormatException) }; // Exponent + + yield return new object[] { "ab", NumberStyles.None, null, typeof(FormatException) }; // Hex value + yield return new object[] { " 123 ", NumberStyles.None, null, typeof(FormatException) }; // Trailing and leading whitespace + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal32 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite)) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.False(Decimal32.TryParse(value, out result)); + Assert.Equal(default(Decimal32), result); + + Assert.Throws(exceptionType, () => Decimal32.Parse(value)); + } + + Assert.Throws(exceptionType, () => Decimal32.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal32.TryParse(value, style, provider, out result)); + Assert.Equal(default(Decimal32), result); + + Assert.Throws(exceptionType, () => Decimal32.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal32.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(default(Decimal32), result); + + Assert.Throws(exceptionType, () => Decimal32.Parse(value, style)); + Assert.Throws(exceptionType, () => Decimal32.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_ValidWithOffsetCount_TestData() + { + foreach (object[] inputs in Parse_Valid_TestData()) + { + yield return new object[] { inputs[0], 0, ((string)inputs[0]).Length, inputs[1], inputs[2], inputs[3] }; + } + + yield return new object[] { "-123", 1, 3, NumberStyles.Number, null, new Decimal32(123, 0) }; + yield return new object[] { "-123", 0, 3, NumberStyles.Number, null, new Decimal32(-12, 0) }; + yield return new object[] { 1000.ToString("N0"), 0, 4, NumberStyles.AllowThousands, null, new Decimal32(100, 0) }; + yield return new object[] { 1000.ToString("N0"), 2, 3, NumberStyles.AllowThousands, null, new Decimal32(0, 0) }; + yield return new object[] { "(123)", 1, 3, NumberStyles.AllowParentheses, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal32(123, 0) }; + yield return new object[] { "1234567890123456789012345.678456", 1, 4, NumberStyles.Number, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal32(2345, 0) }; + } + + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, Decimal32 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal32 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal32.TryParse(value.AsSpan(offset, count), out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal32.Parse(value.AsSpan(offset, count))); + } + + Assert.Equal(expected, Decimal32.Parse(value.AsSpan(offset, count), provider: provider)); + } + + Assert.Equal(expected, Decimal32.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(Decimal32.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + Assert.Throws(exceptionType, () => Decimal32.Parse(value.AsSpan(), style, provider)); + + Assert.False(Decimal32.TryParse(value.AsSpan(), style, provider, out Decimal32 result)); + Assert.Equal(default, result); + } + } + + [Fact] + public static void Midpoint_Rounding() + { + var number = new Decimal32(12345685, 0); + Assert.Equal(new Decimal32(1234568, 1), number); + } + + [Fact] + public static void Rounding() + { + var number = new Decimal32(12345678, 0); + Assert.Equal(new Decimal32(1234568, 1), number); + + number = new Decimal32(12345671, 0); + Assert.Equal(new Decimal32(1234567, 1), number); + + number = new Decimal32(12345650, -103); + Assert.Equal(new Decimal32(123456, -101), number); + } + + [Fact] + public static void MaxValue_Rounding() + { + Assert.Equal(Decimal32.MaxValue, Decimal32.Parse(new string('9', 7) + '4' + new string('9', 89))); + Assert.Equal(Decimal32.PositiveInfinity, Decimal32.Parse(new string('9', 7) + '5' + new string('0', 89))); + Assert.Equal(Decimal32.PositiveInfinity, Decimal32.Parse(new string('9', 7) + '5' + new string('0', 88) + '1')); + } + + [Theory] + [MemberData(nameof(CompareTo_Other_ReturnsExpected_TestData))] + public static void CompareTo_Other_ReturnsExpected(Decimal32 d1, Decimal32 d2, int expected) + { + Assert.Equal(expected, d1.CompareTo(d2)); + if (expected == 0) + { + Assert.Equal(d1, d2); + Assert.Equal(d2, d1); + } + else + { + Assert.Equal(-expected, d2.CompareTo(d1)); + Assert.NotEqual(d1, d2); + Assert.NotEqual(d2, d1); + } + } + + public static IEnumerable CompareTo_Other_ReturnsExpected_TestData() + { + yield return new object[] { new Decimal32(-1, 1), new Decimal32(-10, 0), 0 }; + yield return new object[] { new Decimal32(1, 90), new Decimal32(10, 89), 0 }; + yield return new object[] { new Decimal32(999999, 90), new Decimal32(9999990, 89), 0 }; + yield return new object[] { new Decimal32(1, 1), new Decimal32(-1, 0), 1 }; + yield return new object[] { new Decimal32(10, 0), new Decimal32(-1, 1), 1 }; + yield return new object[] { new Decimal32(10, 0), Decimal32.NaN, 1 }; + yield return new object[] { new Decimal32(10, 0), Decimal32.NegativeInfinity, 1 }; + yield return new object[] { new Decimal32(10, 0), Decimal32.NegativeZero, 1 }; + yield return new object[] { Decimal32.PositiveInfinity, new Decimal32(10, 20), 1 }; + yield return new object[] { Decimal32.PositiveInfinity, new Decimal32(10, 150), 0 }; + yield return new object[] { Decimal32.PositiveInfinity, Decimal32.NegativeInfinity, 1 }; + yield return new object[] { Decimal32.PositiveInfinity, Decimal32.PositiveInfinity, 0 }; + yield return new object[] { Decimal32.PositiveInfinity, Decimal32.NegativeZero, 1 }; + yield return new object[] { Decimal32.NegativeInfinity, Decimal32.NegativeInfinity, 0 }; + yield return new object[] { Decimal32.NaN, Decimal32.NaN, 0 }; + yield return new object[] { Decimal32.NegativeZero, Decimal32.NegativeInfinity, 1 }; + yield return new object[] { Decimal32.NegativeZero, new Decimal32(0, 20), 0 }; + yield return new object[] { Decimal32.NegativeZero, Decimal32.NaN, 1 }; + for (int i = 1; i < 7; i++) + { + var d1 = new Decimal32(1, i); + var d2 = new Decimal32(int.Parse("1" + new string('0', i)), 0); + yield return new object[] { d1, d2, 0 }; + } + } + + [Fact] + public static void GetHashCodeTest() + { + var d = new Decimal32(10, 20); + Assert.Equal(d.GetHashCode(), d.GetHashCode()); + } + + [Fact] + public static void CompareToZero() + { + var zero = new Decimal32(0, 1); + Assert.Equal(zero, new Decimal32(0, 20)); + Assert.Equal(zero, new Decimal32(1, -102)); + Assert.Equal(zero, new Decimal32(234, -1000)); + Assert.Equal(zero, new Decimal32(-1, -102)); + Assert.Equal(zero, new Decimal32(-234, -1000)); + } + + public static IEnumerable ToString_TestData() + { + foreach (NumberFormatInfo defaultFormat in new[] { null, NumberFormatInfo.CurrentInfo }) + { + yield return new object[] { new Decimal32(3, 96), "G", defaultFormat, "3" + new string('0', 96) }; + yield return new object[] { new Decimal32(-3, 96), "G", defaultFormat, "-3" + new string('0', 96) }; + yield return new object[] { new Decimal32(-4567, 0), "G", defaultFormat, "-4567" }; + yield return new object[] { new Decimal32(-4567891, -3), "G", defaultFormat, "-4567.891" }; + yield return new object[] { new Decimal32(0, 0), "G", defaultFormat, "0" }; + yield return new object[] { new Decimal32(4567, 0), "G", defaultFormat, "4567" }; + yield return new object[] { new Decimal32(4567891, -3), "G", defaultFormat, "4567.891" }; + + yield return new object[] { new Decimal32(2468, 0), "N", defaultFormat, "2,468.00" }; + + yield return new object[] { new Decimal32(2467, 0), "[#-##-#]", defaultFormat, "[2-46-7]" }; + + } + } + + [Fact] + public static void Test_ToString() + { + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + foreach (object[] testdata in ToString_TestData()) + { + ToString((Decimal32)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); + } + } + } + + private static void ToString(Decimal32 f, string format, IFormatProvider provider, string expected) + { + bool isDefaultProvider = provider == null; + if (string.IsNullOrEmpty(format) || format.ToUpperInvariant() == "G") + { + if (isDefaultProvider) + { + Assert.Equal(expected, f.ToString()); + Assert.Equal(expected, f.ToString((IFormatProvider)null)); + } + Assert.Equal(expected, f.ToString(provider)); + } + if (isDefaultProvider) + { + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant())); // If format is upper case, then exponents are printed in upper case + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant())); // If format is lower case, then exponents are printed in lower case + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), null)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), null)); + } + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), provider)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), provider)); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs new file mode 100644 index 00000000000000..a1fbeb25645749 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -0,0 +1,370 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using System.Numerics; +using Xunit; + +namespace System.Tests +{ + public class Decimal64Tests + { + public static IEnumerable Parse_Valid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Number; + NumberFormatInfo invariantFormat = NumberFormatInfo.InvariantInfo; + + NumberFormatInfo emptyFormat = NumberFormatInfo.CurrentInfo; + + var customFormat1 = new NumberFormatInfo(); + customFormat1.CurrencySymbol = "$"; + customFormat1.CurrencyGroupSeparator = ","; + + var customFormat2 = new NumberFormatInfo(); + customFormat2.NumberDecimalSeparator = "."; + + yield return new object[] { "-123", defaultStyle, null, new Decimal64(-123, 0) }; + yield return new object[] { "0", defaultStyle, null, new Decimal64(0, 0) }; + yield return new object[] { "123", defaultStyle, null, new Decimal64(123, 0) }; + yield return new object[] { " 123 ", defaultStyle, null, new Decimal64(123, 0) }; + yield return new object[] { (567.89).ToString(), defaultStyle, null, new Decimal64(56789, -2) }; + yield return new object[] { (-567.89).ToString(), defaultStyle, null, new Decimal64(-56789, -2) }; + yield return new object[] { "0.6666666666666666500000000000000000000000000000000000000000000000000000000000000", defaultStyle, invariantFormat, new Decimal64(6666666666666666, -16) }; + yield return new object[] { new string('9', 17), defaultStyle, invariantFormat, new Decimal64(1, 17) }; + + yield return new object[] { "0." + new string('0', 398) + "1", defaultStyle, invariantFormat, new Decimal64(0, 0) }; + yield return new object[] { "-0." + new string('0', 398) + "1", defaultStyle, invariantFormat, new Decimal64(0, 0) }; + yield return new object[] { "0." + new string('0', 397) + "1", defaultStyle, invariantFormat, new Decimal64(1, -398) }; + yield return new object[] { "-0." + new string('0', 397) + "1", defaultStyle, invariantFormat, new Decimal64(-1, -398) }; + + yield return new object[] { "0." + new string('0', 396) + "12345", defaultStyle, invariantFormat, new Decimal64(12, -398) }; + yield return new object[] { "-0." + new string('0', 396) + "12345", defaultStyle, invariantFormat, new Decimal64(-12, -398) }; + yield return new object[] { "0." + new string('0', 396) + "12562", defaultStyle, invariantFormat, new Decimal64(13, -398) }; + yield return new object[] { "-0." + new string('0', 396) + "12562", defaultStyle, invariantFormat, new Decimal64(-13, -398) }; + + yield return new object[] { emptyFormat.NumberDecimalSeparator + "234", defaultStyle, null, new Decimal64(234, -3) }; + yield return new object[] { "234" + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal64(234, 0) }; + yield return new object[] { "7" + new string('0', 384) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal64(7, 384) }; + yield return new object[] { "07" + new string('0', 384) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal64(7, 384) }; + + yield return new object[] { (123.1).ToString(), NumberStyles.AllowDecimalPoint, null, new Decimal64(1231, -1) }; + yield return new object[] { 1000.ToString("N0"), NumberStyles.AllowThousands, null, new Decimal64(1000, 0) }; + + yield return new object[] { "123", NumberStyles.Any, emptyFormat, new Decimal64(123, 0) }; + yield return new object[] { (123.567).ToString(), NumberStyles.Any, emptyFormat, new Decimal64(123567, -3) }; + yield return new object[] { "123", NumberStyles.Float, emptyFormat, new Decimal64(123, 0) }; + yield return new object[] { "$1000", NumberStyles.Currency, customFormat1, new Decimal64(1, 3) }; + yield return new object[] { "123.123", NumberStyles.Float, customFormat2, new Decimal64(123123, -3) }; + yield return new object[] { "(123)", NumberStyles.AllowParentheses, customFormat2, new Decimal64(-123, 0) }; + + yield return new object[] { "NaN", NumberStyles.Any, invariantFormat, Decimal64.NaN }; + yield return new object[] { "+NaN", NumberStyles.Any, invariantFormat, Decimal64.NaN }; + yield return new object[] { "Infinity", NumberStyles.Any, invariantFormat, Decimal64.PositiveInfinity }; + yield return new object[] { "+Infinity", NumberStyles.Any, invariantFormat, Decimal64.PositiveInfinity }; + yield return new object[] { "-Infinity", NumberStyles.Any, invariantFormat, Decimal64.NegativeInfinity }; + } + + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse(string value, NumberStyles style, IFormatProvider provider, Decimal64 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal64 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal64.TryParse(value, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal64.Parse(value)); + } + + Assert.Equal(expected, Decimal64.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal64.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal64.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal64.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal64.Parse(value, style)); + Assert.Equal(expected, Decimal64.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_Invalid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Number; + + var customFormat = new NumberFormatInfo(); + customFormat.CurrencySymbol = "$"; + customFormat.NumberDecimalSeparator = "."; + + yield return new object[] { null, defaultStyle, null, typeof(ArgumentNullException) }; + yield return new object[] { "", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { " ", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { "Garbage", defaultStyle, null, typeof(FormatException) }; + + yield return new object[] { "ab", defaultStyle, null, typeof(FormatException) }; // Hex value + yield return new object[] { "(123)", defaultStyle, null, typeof(FormatException) }; // Parentheses + yield return new object[] { 100.ToString("C0"), defaultStyle, null, typeof(FormatException) }; // Currency + + yield return new object[] { (123.456m).ToString(), NumberStyles.Integer, null, typeof(FormatException) }; // Decimal + yield return new object[] { " " + (123.456m).ToString(), NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { (123.456m).ToString() + " ", NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { "1E23", NumberStyles.None, null, typeof(FormatException) }; // Exponent + + yield return new object[] { "ab", NumberStyles.None, null, typeof(FormatException) }; // Hex value + yield return new object[] { " 123 ", NumberStyles.None, null, typeof(FormatException) }; // Trailing and leading whitespace + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal64 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite)) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.False(Decimal64.TryParse(value, out result)); + Assert.Equal(default(Decimal64), result); + + Assert.Throws(exceptionType, () => Decimal64.Parse(value)); + } + + Assert.Throws(exceptionType, () => Decimal64.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal64.TryParse(value, style, provider, out result)); + Assert.Equal(default(Decimal64), result); + + Assert.Throws(exceptionType, () => Decimal64.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal64.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(default(Decimal64), result); + + Assert.Throws(exceptionType, () => Decimal64.Parse(value, style)); + Assert.Throws(exceptionType, () => Decimal64.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_ValidWithOffsetCount_TestData() + { + foreach (object[] inputs in Parse_Valid_TestData()) + { + yield return new object[] { inputs[0], 0, ((string)inputs[0]).Length, inputs[1], inputs[2], inputs[3] }; + } + + yield return new object[] { "-123", 1, 3, NumberStyles.Number, null, new Decimal64(123, 0) }; + yield return new object[] { "-123", 0, 3, NumberStyles.Number, null, new Decimal64(-12, 0) }; + yield return new object[] { 1000.ToString("N0"), 0, 4, NumberStyles.AllowThousands, null, new Decimal64(100, 0) }; + yield return new object[] { 1000.ToString("N0"), 2, 3, NumberStyles.AllowThousands, null, new Decimal64(0, 0) }; + yield return new object[] { "(123)", 1, 3, NumberStyles.AllowParentheses, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal64(123, 0) }; + yield return new object[] { "1234567890123456789012345.678456", 1, 4, NumberStyles.Number, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal64(2345, 0) }; + } + + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, Decimal64 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal64 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal64.TryParse(value.AsSpan(offset, count), out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal64.Parse(value.AsSpan(offset, count))); + } + + Assert.Equal(expected, Decimal64.Parse(value.AsSpan(offset, count), provider: provider)); + } + + Assert.Equal(expected, Decimal64.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(Decimal64.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + Assert.Throws(exceptionType, () => Decimal64.Parse(value.AsSpan(), style, provider)); + + Assert.False(Decimal64.TryParse(value.AsSpan(), style, provider, out Decimal64 result)); + Assert.Equal(default, result); + } + } + + [Fact] + public static void Midpoint_Rounding() + { + var number = new Decimal64(12345688888888885, 0); + Assert.Equal(new Decimal64(1234568888888888, 1), number); + } + + [Fact] + public static void Rounding() + { + var number = new Decimal64(12345677777777778, 0); + Assert.Equal(new Decimal64(1234567777777778, 1), number); + + number = new Decimal64(12345677777777771, 0); + Assert.Equal(new Decimal64(1234567777777777, 1), number); + + number = new Decimal64(12345677777777771, -399); + Assert.Equal(new Decimal64(1234567777777777, -398), number); + } + + [Fact] + public static void MaxValue_Rounding() + { + Assert.Equal(Decimal64.MaxValue, Decimal64.Parse(new string('9', 16) + '4' + new string('0', 368))); + Assert.Equal(Decimal64.PositiveInfinity, Decimal64.Parse(new string('9', 16) + '5' + new string('0', 368))); + Assert.Equal(Decimal64.PositiveInfinity, Decimal64.Parse(new string('9', 16) + '5' + new string('0', 367) + '1')); + } + + [Theory] + [MemberData(nameof(CompareTo_Other_ReturnsExpected_TestData))] + public static void CompareTo_Other_ReturnsExpected(Decimal64 d1, Decimal64 d2, int expected) + { + Assert.Equal(expected, d1.CompareTo(d2)); + if (expected == 0) + { + Assert.Equal(d1, d2); + Assert.Equal(d2, d1); + } + else + { + Assert.Equal(-expected, d2.CompareTo(d1)); + Assert.NotEqual(d1, d2); + Assert.NotEqual(d2, d1); + } + } + + public static IEnumerable CompareTo_Other_ReturnsExpected_TestData() + { + yield return new object[] { new Decimal64(-1, 1), new Decimal64(-10, 0), 0 }; + yield return new object[] { new Decimal64(1, 369), new Decimal64(10, 368), 0 }; + yield return new object[] { new Decimal64(long.Parse(new string('9', 15)), 369), new Decimal64(long.Parse(new string('9', 15) + "0"), 368), 0 }; + yield return new object[] { new Decimal64(1, 1), new Decimal64(-1, 0), 1 }; + yield return new object[] { new Decimal64(10, 0), new Decimal64(-1, 1), 1 }; + yield return new object[] { new Decimal64(10, 0), Decimal64.NaN, 1 }; + yield return new object[] { new Decimal64(10, 0), Decimal64.NegativeInfinity, 1 }; + yield return new object[] { new Decimal64(10, 0), Decimal64.NegativeZero, 1 }; + yield return new object[] { Decimal64.PositiveInfinity, new Decimal64(10, 20), 1 }; + yield return new object[] { Decimal64.PositiveInfinity, new Decimal64(10, 1500), 0 }; + yield return new object[] { Decimal64.PositiveInfinity, Decimal64.NegativeInfinity, 1 }; + yield return new object[] { Decimal64.PositiveInfinity, Decimal64.PositiveInfinity, 0 }; + yield return new object[] { Decimal64.PositiveInfinity, Decimal64.NegativeZero, 1 }; + yield return new object[] { Decimal64.NegativeInfinity, Decimal64.NegativeInfinity, 0 }; + yield return new object[] { Decimal64.NaN, Decimal64.NaN, 0 }; + yield return new object[] { Decimal64.NegativeZero, Decimal64.NegativeInfinity, 1 }; + yield return new object[] { Decimal64.NegativeZero, new Decimal64(0, 20), 0 }; + yield return new object[] { Decimal64.NegativeZero, Decimal64.NaN, 1 }; + for (int i = 1; i < 16; i++) + { + var d1 = new Decimal64(1, i); + var d2 = new Decimal64(long.Parse("1" + new string('0', i)), 0); + yield return new object[] { d1, d2, 0 }; + } + } + + [Fact] + public static void GetHashCodeTest() + { + var d = new Decimal64(10, 20); + Assert.Equal(d.GetHashCode(), d.GetHashCode()); + } + + [Fact] + public static void CompareToZero() + { + var zero = new Decimal64(0, 1); + Assert.Equal(zero, new Decimal64(0, 20)); + Assert.Equal(zero, new Decimal64(1, -399)); + Assert.Equal(zero, new Decimal64(234, -1000)); + Assert.Equal(zero, new Decimal64(-1, -399)); + Assert.Equal(zero, new Decimal64(-234, -1000)); + } + + public static IEnumerable ToString_TestData() + { + foreach (NumberFormatInfo defaultFormat in new[] { null, NumberFormatInfo.CurrentInfo }) + { + yield return new object[] { new Decimal64(3, 384), "G", defaultFormat, "3" + new string('0', 384) }; + yield return new object[] { new Decimal64(-3, 384), "G", defaultFormat, "-3" + new string('0', 384) }; + yield return new object[] { new Decimal64(-4567, 0), "G", defaultFormat, "-4567" }; + yield return new object[] { new Decimal64(-4567891, -3), "G", defaultFormat, "-4567.891" }; + yield return new object[] { new Decimal64(0, 0), "G", defaultFormat, "0" }; + yield return new object[] { new Decimal64(4567, 0), "G", defaultFormat, "4567" }; + yield return new object[] { new Decimal64(4567891, -3), "G", defaultFormat, "4567.891" }; + + yield return new object[] { new Decimal64(2468, 0), "N", defaultFormat, "2,468.00" }; + + yield return new object[] { new Decimal64(2467, 0), "[#-##-#]", defaultFormat, "[2-46-7]" }; + + } + } + + [Fact] + public static void Test_ToString() + { + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + foreach (object[] testdata in ToString_TestData()) + { + ToString((Decimal64)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); + } + } + } + + private static void ToString(Decimal64 f, string format, IFormatProvider provider, string expected) + { + bool isDefaultProvider = provider == null; + if (string.IsNullOrEmpty(format) || format.ToUpperInvariant() == "G") + { + if (isDefaultProvider) + { + Assert.Equal(expected, f.ToString()); + Assert.Equal(expected, f.ToString((IFormatProvider)null)); + } + Assert.Equal(expected, f.ToString(provider)); + } + if (isDefaultProvider) + { + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant())); // If format is upper case, then exponents are printed in upper case + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant())); // If format is lower case, then exponents are printed in lower case + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), null)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), null)); + } + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), provider)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), provider)); + } + } +}