diff --git a/src/libraries/Common/src/System/Number.NumberBuffer.cs b/src/libraries/Common/src/System/Number.NumberBuffer.cs index 44c1d47d96dca..325f752ec625c 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 e6246a6f98107..f4f5c3d30e95c 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. @@ -3203,6 +3212,15 @@ Value was either too large or too small for a Decimal. + + Value was either too large or too small for a Decimal32. + + + Value was either too large or too small for a Decimal64. + + + Value was either too large or too small for a Decimal128. + The duration cannot be returned for TimeSpan.MinValue because the absolute value of TimeSpan.MinValue exceeds the value of TimeSpan.MaxValue. 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 b8cb658322f89..67c9d3e3e8267 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 @@ -433,6 +433,10 @@ + + + + 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 0000000000000..790773743e85c --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -0,0 +1,255 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Numerics; + +namespace System +{ + internal interface IDecimalIeee754ConstructorInfo + where TSelf : unmanaged, IDecimalIeee754ConstructorInfo + where TSignificand : IBinaryInteger + where TValue : IBinaryInteger + { + static abstract TSignificand MaxSignificand { get; } + static abstract int MaxDecimalExponent { get; } + static abstract int MinDecimalExponent { get; } + static abstract int NumberDigitsPrecision { get; } + static abstract int Bias { get; } + static abstract int CountDigits(TSignificand number); + static abstract TSignificand Power10(int exponent); + static abstract int NumberBitsEncoding { get; } + static abstract TValue G0G1Mask { get; } + static abstract TValue MostSignificantBitOfSignificandMask { get; } + static abstract TValue SignMask { get; } + static abstract int NumberBitsCombinationField { get; } + static abstract int NumberBitsExponent { get; } + static abstract TValue PositiveInfinityBits { get; } + static abstract TValue NegativeInfinityBits { get; } + static abstract TValue Zero { get; } + static abstract string OverflowMessage { get; } + } + + internal interface IDecimalIeee754UnpackInfo + where TSelf : unmanaged, IDecimalIeee754UnpackInfo + where TSignificand : IBinaryInteger + where TValue : IBinaryInteger + { + 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 int NumberBitsSignificand { get; } + static abstract int NumberDigitsPrecision { get; } + static abstract int Bias { get; } + static abstract TValue MostSignificantBitOfSignificandMask { get; } + static abstract int ConvertToExponent(TValue value); + static abstract TSignificand ConvertToSignificand(TValue value); + static abstract TSignificand Power10(int exponent); + } + + internal static partial class Number + { + internal static TValue CalDecimalIeee754(TSignificand significand, int exponent) + where TDecimal : unmanaged, IDecimalIeee754ConstructorInfo + where TSignificand : IBinaryInteger + where TValue : IBinaryInteger + { + if (significand == TSignificand.Zero) + { + return TValue.Zero; + } + + TSignificand unsignedSignificand = significand > TSignificand.Zero ? significand : -significand; + + if ((unsignedSignificand > TDecimal.MaxSignificand && exponent >= TDecimal.MaxDecimalExponent) + || (unsignedSignificand == TDecimal.MaxSignificand && exponent > TDecimal.MaxDecimalExponent)) + { + return significand > TSignificand.Zero ? TDecimal.PositiveInfinityBits : TDecimal.NegativeInfinityBits; + } + + TSignificand ten = TSignificand.CreateTruncating(10); + if (exponent < TDecimal.MinDecimalExponent) + { + while (unsignedSignificand > TSignificand.Zero && exponent < TDecimal.MinDecimalExponent) + { + unsignedSignificand /= ten; + ++exponent; + } + if (unsignedSignificand == TSignificand.Zero) + { + return TDecimal.Zero; + } + } + + if (unsignedSignificand > TDecimal.MaxSignificand) + { + int numberDigitsRemoving = TDecimal.CountDigits(unsignedSignificand) - TDecimal.NumberDigitsPrecision; + + if (exponent + numberDigitsRemoving > TDecimal.MaxDecimalExponent) + { + throw new OverflowException(TDecimal.OverflowMessage); + } + + exponent += numberDigitsRemoving; + TSignificand divisor = TDecimal.Power10(numberDigitsRemoving); + TSignificand quotient = unsignedSignificand / divisor; + TSignificand remainder = unsignedSignificand % divisor; + TSignificand midPoint = divisor >> 1; + bool needRounding = remainder > midPoint || (remainder == midPoint && (quotient & TSignificand.One) == TSignificand.One); + + if (needRounding && quotient == TDecimal.MaxSignificand && exponent < TDecimal.MaxDecimalExponent) + { + unsignedSignificand = TDecimal.Power10(TDecimal.NumberDigitsPrecision - 1); + exponent++; + } + else if (needRounding && quotient < TDecimal.MaxSignificand) + { + unsignedSignificand = quotient + TSignificand.One; + } + else + { + unsignedSignificand = quotient; + } + } + else if (exponent > TDecimal.MaxDecimalExponent) + { + int numberZeroDigits = exponent - TDecimal.MaxDecimalExponent; + int numberSignificandDigits = TDecimal.CountDigits(unsignedSignificand); + + if (numberSignificandDigits + numberZeroDigits > TDecimal.NumberDigitsPrecision) + { + throw new OverflowException(TDecimal.OverflowMessage); + } + unsignedSignificand *= TDecimal.Power10(numberZeroDigits); + exponent -= numberZeroDigits; + } + + exponent += TDecimal.Bias; + + TValue value = TValue.Zero; + TValue exponentVal = TValue.CreateTruncating(exponent); + TValue significandVal = TValue.CreateTruncating(unsignedSignificand); + bool msbSignificand = (significandVal & TDecimal.MostSignificantBitOfSignificandMask) != TValue.Zero; + + if (significand < TSignificand.Zero) + { + value = TDecimal.SignMask; + } + + if (msbSignificand) + { + value |= TDecimal.G0G1Mask; + exponentVal <<= TDecimal.NumberBitsEncoding - TDecimal.NumberBitsExponent - 3; + value |= exponentVal; + significandVal ^= TDecimal.MostSignificantBitOfSignificandMask; + value |= significandVal; + } + else + { + exponentVal <<= TDecimal.NumberBitsEncoding - TDecimal.NumberBitsExponent - 1; + value |= exponentVal; + value |= significandVal; + } + + return value; + } + + internal struct DecimalIeee754 + where TSignificand : IBinaryInteger + { + public bool Signed { get; } + public int Exponent { get; } + public TSignificand Significand { get; } + + public DecimalIeee754(bool signed, int exponent, TSignificand significand) + { + Signed = signed; + Exponent = exponent; + Significand = significand; + } + } + + internal static DecimalIeee754 UnpackDecimalIeee754(TValue value) + where TDecimal : unmanaged, IDecimalIeee754UnpackInfo + where TSignificand : IBinaryInteger + where TValue : IBinaryInteger + { + bool signed = (value & TDecimal.SignMask) != TValue.Zero; + TSignificand significand; + int exponent; + + if ((value & TDecimal.G0G1Mask) == TDecimal.G0G1Mask) + { + exponent = TDecimal.ConvertToExponent((value & TDecimal.G2ToGwPlus3ExponentMask) >> (TDecimal.NumberBitsSignificand + 1)); + significand = TDecimal.ConvertToSignificand((value & TDecimal.GwPlus4SignificandMask) | TDecimal.MostSignificantBitOfSignificandMask); + } + else + { + exponent = TDecimal.ConvertToExponent((value & TDecimal.G0ToGwPlus1ExponentMask) >> (TDecimal.NumberBitsSignificand + 3)); + significand = TDecimal.ConvertToSignificand(value & TDecimal.GwPlus2ToGwPlus4SignificandMask); + } + + return new DecimalIeee754(signed, exponent - TDecimal.Bias, significand); + } + + internal static int CompareDecimalIeee754(TValue currentValue, TValue otherValue) + where TDecimal : unmanaged, IDecimalIeee754UnpackInfo + where TSignificand : IBinaryInteger + where TValue : IBinaryInteger + { + if (currentValue == otherValue) + { + return 0; + } + DecimalIeee754 current = UnpackDecimalIeee754(currentValue); + DecimalIeee754 other = UnpackDecimalIeee754(otherValue); + + if (current.Signed && !other.Signed) return -1; + + if (!current.Signed && other.Signed) return 1; + + if (current.Exponent > other.Exponent) + { + return current.Signed ? -InternalUnsignedCompare(current, other) : InternalUnsignedCompare(current, other); + } + + if (current.Exponent < other.Exponent) + { + return current.Signed ? InternalUnsignedCompare(other, current) : -InternalUnsignedCompare(current, other); + } + + if (current.Significand == other.Significand) return 0; + + if (current.Significand > other.Significand) + { + return current.Signed ? -1 : 1; + } + else + { + return current.Signed ? 1 : -1; + } + + static int InternalUnsignedCompare(DecimalIeee754 biggerExp, DecimalIeee754 smallerExp) + { + if (biggerExp.Significand >= smallerExp.Significand) return 1; + + int diffExponent = biggerExp.Exponent - smallerExp.Exponent; + if (diffExponent < TDecimal.NumberDigitsPrecision) + { + TSignificand factor = TDecimal.Power10(diffExponent); + TSignificand quotient = smallerExp.Significand / biggerExp.Significand; + TSignificand remainder = smallerExp.Significand % biggerExp.Significand; + + if (quotient < factor) return 1; + if (quotient > factor) return -1; + if (remainder > TSignificand.Zero) return -1; + return 0; + } + + return 1; + } + } + } +} 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 38a752c6810ff..55510ffe1efcd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -295,6 +295,84 @@ internal static partial class Number "80818283848586878889"u8 + "90919293949596979899"u8).ToArray(); + public static unsafe string FormatDecimal32(Decimal32 value, ReadOnlySpan format, NumberFormatInfo info) + { + char fmt = ParseFormatSpecifier(format, out int digits); + + byte* pDigits = stackalloc byte[Decimal32NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, Decimal32NumberBufferLength); + + Decimal32ToNumber(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(); + return result; + } + + public static unsafe string FormatDecimal64(Decimal64 value, ReadOnlySpan format, NumberFormatInfo info) + { + char fmt = ParseFormatSpecifier(format, out int digits); + + byte* pDigits = stackalloc byte[Decimal64NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, Decimal64NumberBufferLength); + + Decimal64ToNumber(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(); + return result; + } + + public static unsafe string FormatDecimal128(Decimal128 value, ReadOnlySpan format, NumberFormatInfo info) + { + char fmt = ParseFormatSpecifier(format, out int digits); + + byte* pDigits = stackalloc byte[Decimal128NumberBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, Decimal128NumberBufferLength); + + Decimal128ToNumber(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(); + return result; + } + public static unsafe string FormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info) { char fmt = ParseFormatSpecifier(format, out int digits); @@ -348,6 +426,129 @@ public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan decimal32 = UnpackDecimalIeee754(d._value); + number.IsNegative = decimal32.Signed; + + byte* p = buffer + Decimal32Precision; + p = UInt32ToDecChars(p, (uint)decimal32.Significand, 0); + int numberDigitsSignificand = (int)((buffer + Decimal32Precision) - p); + + byte* dst = number.DigitsPtr; + int i = numberDigitsSignificand; + while (--i >= 0) + { + *dst++ = *p++; + } + + number.Scale = decimal32.Significand != 0 ? numberDigitsSignificand + decimal32.Exponent : 0; + + if (decimal32.Exponent >= 0) + { + number.DigitsCount = numberDigitsSignificand + decimal32.Exponent; + + if (decimal32.Exponent > 0) + { + i = decimal32.Exponent; + while (--i >= 0) + { + *dst++ = (byte)'0'; + } + } + } + else + { + number.DigitsCount = numberDigitsSignificand; + } + + *dst = (byte)'\0'; + + number.CheckConsistency(); + } + internal static unsafe void Decimal64ToNumber(Decimal64 d, ref NumberBuffer number) + { + byte* buffer = number.DigitsPtr; + DecimalIeee754 decimal64 = UnpackDecimalIeee754(d._value); + number.IsNegative = decimal64.Signed; + byte* p = buffer + Decimal64Precision; + + p = UInt64ToDecChars(p, (ulong)decimal64.Significand, 0); + int numberDigitsSignificand = (int)((buffer + Decimal64Precision) - p); + + byte* dst = number.DigitsPtr; + int i = numberDigitsSignificand; + while (--i >= 0) + { + *dst++ = *p++; + } + + number.Scale = decimal64.Significand != 0 ? numberDigitsSignificand + decimal64.Exponent : 0; + + if (decimal64.Exponent >= 0) + { + number.DigitsCount = numberDigitsSignificand + decimal64.Exponent; + + if (decimal64.Exponent > 0) + { + i = decimal64.Exponent; + while (--i >= 0) + { + *dst++ = (byte)'0'; + } + } + } + else + { + number.DigitsCount = numberDigitsSignificand; + } + + *dst = (byte)'\0'; + + number.CheckConsistency(); + } + internal static unsafe void Decimal128ToNumber(Decimal128 d, ref NumberBuffer number) + { + byte* buffer = number.DigitsPtr; + DecimalIeee754 decimal128 = UnpackDecimalIeee754(new UInt128(d._upper, d._lower)); + number.IsNegative = decimal128.Signed; + byte* p = buffer + Decimal128Precision; + + p = UInt128ToDecChars(p, (UInt128)decimal128.Significand, 0); + int numberDigitsSignificand = (int)((buffer + Decimal128Precision) - p); + + byte* dst = number.DigitsPtr; + int i = numberDigitsSignificand; + while (--i >= 0) + { + *dst++ = *p++; + } + + number.Scale = decimal128.Significand != 0 ? numberDigitsSignificand + decimal128.Exponent : 0; + + if (decimal128.Exponent >= 0) + { + number.DigitsCount = numberDigitsSignificand + decimal128.Exponent; + + if (decimal128.Exponent > 0) + { + i = decimal128.Exponent; + while (--i >= 0) + { + *dst++ = (byte)'0'; + } + } + } + else + { + number.DigitsCount = numberDigitsSignificand; + } + + *dst = (byte)'\0'; + + number.CheckConsistency(); + } internal static unsafe void DecimalToNumber(scoped ref decimal d, ref NumberBuffer number) { 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 bb80ebd3ff67a..ea1a1d6898862 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs @@ -99,6 +99,22 @@ internal interface IBinaryFloatParseAndFormatInfo : IBinaryFloatingPointI static abstract int MaxPrecisionCustomFormat { get; } } + internal interface IDecimalIeee754ParseAndFormatInfo + where TSelf : unmanaged, IDecimalIeee754ParseAndFormatInfo + { + static abstract int NumberDigitsPrecision { get; } + static abstract int MaxScale { get; } + } + + internal interface IDecimalIeee754TryParseInfo + where TSelf : unmanaged, IDecimalIeee754TryParseInfo + where TSignificand : unmanaged, IBinaryInteger + { + static abstract int DecimalNumberBufferLength { get; } + static abstract bool TryNumberToDecimalIeee754(ref Number.NumberBuffer number, out TSignificand significand, out int exponent); + static abstract TSelf Construct(TSignificand significand, int exponent); + } + internal static partial class Number { private const int Int32Precision = 10; @@ -107,6 +123,9 @@ internal static partial class Number private const int UInt64Precision = 20; private const int Int128Precision = 39; private const int UInt128Precision = 39; + private const int Decimal32Precision = 7; + private const int Decimal64Precision = 16; + private const int Decimal128Precision = 34; private const int FloatingPointMaxExponent = 309; private const int FloatingPointMinExponent = -324; @@ -733,6 +752,54 @@ internal static decimal ParseDecimal(ReadOnlySpan value, NumberSty return result; } + internal static Decimal32 ParseDecimal32(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + ParsingStatus status = TryParseDecimalIeee754(value, styles, info, out Decimal32 result); + if (status != ParsingStatus.OK) + { + if (status == ParsingStatus.Failed) + { + ThrowFormatException(value); + } + ThrowOverflowException(SR.Overflow_Decimal32); + } + + return result; + } + + internal static Decimal64 ParseDecimal64(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + ParsingStatus status = TryParseDecimalIeee754(value, styles, info, out Decimal64 result); + if (status != ParsingStatus.OK) + { + if (status == ParsingStatus.Failed) + { + ThrowFormatException(value); + } + ThrowOverflowException(SR.Overflow_Decimal64); + } + + return result; + } + + internal static Decimal128 ParseDecimal128(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + { + ParsingStatus status = TryParseDecimalIeee754(value, styles, info, out Decimal128 result); + if (status != ParsingStatus.OK) + { + if (status == ParsingStatus.Failed) + { + ThrowFormatException(value); + } + ThrowOverflowException(SR.Overflow_Decimal128); + } + + return result; + } + internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decimal value) { number.CheckConsistency(); @@ -853,6 +920,70 @@ internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref deci return true; } + internal static unsafe bool TryNumberToDecimalIeee754(ref NumberBuffer number, out TSignificand significand, out int exponent) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TSignificand : unmanaged, IBinaryInteger + { + number.CheckConsistency(); + + byte* p = number.DigitsPtr; + int c = *p; + significand = TSignificand.Zero; + exponent = 0; + + if (c == 0) + { + return true; + } + + if (number.Scale > TDecimal.MaxScale) + { + return false; + } + + int digitIndex = 0; + + while (digitIndex < TDecimal.NumberDigitsPrecision && c != 0) + { + digitIndex++; + significand *= TSignificand.CreateTruncating(10); + significand += TSignificand.CreateTruncating(c - '0'); + c = *++p; + } + + exponent = number.Scale - digitIndex; + + if (digitIndex < number.DigitsCount) + { + if (c == '5') + { + int lastDigitSignificand = *(p - 1); + c = *++p; + bool tiedToEvenRounding = true; + while (digitIndex < number.DigitsCount && c != 0) + { + if (c != '0') + { + significand += TSignificand.One; + tiedToEvenRounding = false; + break; + } + c = *++p; + } + if (tiedToEvenRounding && lastDigitSignificand % 2 == 1) + { + significand += TSignificand.One; + } + } + else if (c > '5') + { + significand += TSignificand.One; + } + } + + return true; + } + internal static TFloat ParseFloat(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) where TChar : unmanaged, IUtfChar where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo @@ -884,6 +1015,29 @@ 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, IDecimalIeee754TryParseInfo + where TSignificand : unmanaged, IBinaryInteger + { + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[TDecimal.DecimalNumberBufferLength]); + result = default; + + if (!TryStringToNumber(value, styles, ref number, info)) + { + return ParsingStatus.Failed; + } + + if (!TDecimal.TryNumberToDecimalIeee754(ref number, out TSignificand significand, out int exponent)) + { + return ParsingStatus.Overflow; + } + + result = TDecimal.Construct(number.IsNegative ? -significand : significand, exponent); + + return ParsingStatus.OK; + } + internal static bool SpanStartsWith(ReadOnlySpan span, TChar c) where TChar : unmanaged, IUtfChar { 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 0000000000000..3c487c8291205 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -0,0 +1,268 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Buffers.Text; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; + +namespace System.Numerics +{ + public readonly struct Decimal128 + : IComparable, + IComparable, + IEquatable, + ISpanParsable, + IDecimalIeee754ParseAndFormatInfo, + IDecimalIeee754ConstructorInfo, + IDecimalIeee754UnpackInfo, + IDecimalIeee754TryParseInfo + { +#if BIGENDIAN + internal readonly ulong _upper; + internal readonly ulong _lower; +#else + internal readonly ulong _lower; + internal readonly ulong _upper; +#endif + + private const int MaxDecimalExponent = 6111; + private const int MinDecimalExponent = -6176; + private const int NumberDigitsPrecision = 34; + private const int Bias = 6176; + private const int NumberBitsExponent = 14; + private static readonly UInt128 PositiveInfinityValue = new UInt128(upper: 0x7800000000000000, lower: 0); + private static readonly UInt128 NegativeInfinityValue = new UInt128(upper: 0xf800000000000000, lower: 0); + private static readonly UInt128 ZeroValue = new UInt128(0, 0); + private static readonly Int128 MaxSignificand = new Int128(upper: 542101086242752, lower: 4003012203950112767); // 9_999_999_999_999_999_999_999_999_999_999_999; + private static readonly UInt128 SignMask = new UInt128(0x8000_0000_0000_0000, 0); + private static readonly UInt128 G0G1Mask = new UInt128(0x6000_0000_0000_0000, 0); + private static readonly UInt128 MostSignificantBitOfSignificandMask = new UInt128(0x0002_0000_0000_0000, 0); + + public Decimal128(Int128 significand, int exponent) + { + UInt128 value = Number.CalDecimalIeee754(significand, exponent); + _lower = value.Lower; + _upper = value.Upper; + } + public static Decimal128 Parse(string s) => Parse(s, NumberStyles.Number, provider: null); + + 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); + + public static Decimal128 Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); + + public static Decimal128 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.ParseDecimal128(s, style, NumberFormatInfo.GetInstance(provider)); + } + public static Decimal128 Parse(string s, NumberStyles style, IFormatProvider? provider) + { + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); + } + + public static bool TryParse([NotNullWhen(true)] string? s, out Decimal128 result) => TryParse(s, NumberStyles.Number, provider: null, out result); + 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); + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) => TryParse(s, NumberStyles.Number, provider, out result); + 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; + } + 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; + } + + // Compares this object to another object, returning an integer that + // indicates the relationship. + // Returns a value less than zero if this object + // null is considered to be less than any instance. + // If object is not of type Decimal128, this method throws an ArgumentException. + // + public int CompareTo(object? value) + { + if (value == null) + { + return 1; + } + + if (value is not Decimal128 i) + { + throw new ArgumentException(SR.Arg_MustBeDecimal128); + } + + var current = new UInt128(_upper, _lower); + var other = new UInt128(i._upper, i._lower); + + return Number.CompareDecimalIeee754(current, 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; + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Decimal128 && Equals((Decimal128)obj); + } + + public override int GetHashCode() + { + return new UInt128(_upper, _lower).GetHashCode(); + } + + public override string ToString() + { + return Number.FormatDecimal128(this, null, NumberFormatInfo.CurrentInfo); + } + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) + { + return Number.FormatDecimal128(this, format, NumberFormatInfo.CurrentInfo); + } + public string ToString(IFormatProvider? provider) + { + return Number.FormatDecimal128(this, null, NumberFormatInfo.GetInstance(provider)); + } + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) + { + return Number.FormatDecimal128(this, format, NumberFormatInfo.GetInstance(provider)); + } + + static int IDecimalIeee754ParseAndFormatInfo.NumberDigitsPrecision => NumberDigitsPrecision; + + static int IDecimalIeee754ParseAndFormatInfo.MaxScale => 6145; + + static int IDecimalIeee754ConstructorInfo.CountDigits(Int128 number) => FormattingHelpers.CountDigits((UInt128)number); + + static Int128 IDecimalIeee754ConstructorInfo.Power10(int exponent) => Int128Powers10[exponent]; + + static Int128 IDecimalIeee754ConstructorInfo.MaxSignificand => MaxSignificand; + + static int IDecimalIeee754ConstructorInfo.MaxDecimalExponent => MaxDecimalExponent; + + static int IDecimalIeee754ConstructorInfo.MinDecimalExponent => MinDecimalExponent; + + static int IDecimalIeee754ConstructorInfo.NumberDigitsPrecision => NumberDigitsPrecision; + + static int IDecimalIeee754ConstructorInfo.Bias => Bias; + + static int IDecimalIeee754ConstructorInfo.NumberBitsEncoding => 128; + + static int IDecimalIeee754ConstructorInfo.NumberBitsCombinationField => 17; + + static int IDecimalIeee754ConstructorInfo.NumberBitsExponent => NumberBitsExponent; + + static UInt128 IDecimalIeee754ConstructorInfo.PositiveInfinityBits => PositiveInfinityValue; + + static UInt128 IDecimalIeee754ConstructorInfo.NegativeInfinityBits => NegativeInfinityValue; + + static UInt128 IDecimalIeee754ConstructorInfo.Zero => ZeroValue; + + static UInt128 IDecimalIeee754ConstructorInfo.G0G1Mask => G0G1Mask; + + static UInt128 IDecimalIeee754ConstructorInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask; + + static UInt128 IDecimalIeee754ConstructorInfo.SignMask => SignMask; + + static string IDecimalIeee754ConstructorInfo.OverflowMessage => SR.Overflow_Decimal128; + + static int IDecimalIeee754UnpackInfo.ConvertToExponent(UInt128 value) => (int)value; + + static Int128 IDecimalIeee754UnpackInfo.ConvertToSignificand(UInt128 value) => (Int128)value; + + static Int128 IDecimalIeee754UnpackInfo.Power10(int exponent) => Int128Powers10[exponent]; + + static UInt128 IDecimalIeee754UnpackInfo.SignMask => SignMask; + + static int IDecimalIeee754UnpackInfo.Bias => Bias; + + static int IDecimalIeee754UnpackInfo.NumberDigitsPrecision => NumberDigitsPrecision; + + static UInt128 IDecimalIeee754UnpackInfo.G0G1Mask => G0G1Mask; + + static UInt128 IDecimalIeee754UnpackInfo.G0ToGwPlus1ExponentMask => new UInt128(0x7FFE_0000_0000_0000, 0); + + static UInt128 IDecimalIeee754UnpackInfo.G2ToGwPlus3ExponentMask => new UInt128(0x1FFF_8000_0000_0000, 0); + + static UInt128 IDecimalIeee754UnpackInfo.GwPlus2ToGwPlus4SignificandMask => new UInt128(0x0001_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); + + static UInt128 IDecimalIeee754UnpackInfo.GwPlus4SignificandMask => new UInt128(0x0000_7FFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); + + static UInt128 IDecimalIeee754UnpackInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask; + + static int IDecimalIeee754UnpackInfo.NumberBitsSignificand => 110; + + private static Int128[] Int128Powers10 => + [ + new Int128(0, 1), + new Int128(0, 10), + new Int128(0, 100), + new Int128(0, 1000), + new Int128(0, 10000), + new Int128(0, 100000), + new Int128(0, 1000000), + new Int128(0, 10000000), + new Int128(0, 100000000), + new Int128(0, 1000000000), + new Int128(0, 10000000000), + new Int128(0, 100000000000), + new Int128(0, 1000000000000), + new Int128(0, 10000000000000), + new Int128(0, 100000000000000), + new Int128(0, 1000000000000000), + new Int128(0, 10000000000000000), + new Int128(0, 100000000000000000), + new Int128(0, 1000000000000000000), + new Int128(0, 10000000000000000000), + new Int128(5, 7766279631452241920), + new Int128(54, 3875820019684212736), + new Int128(542, 1864712049423024128), + new Int128(5421, 200376420520689664), + new Int128(54210, 2003764205206896640), + new Int128(542101, 1590897978359414784), + new Int128(5421010, 15908979783594147840), + new Int128(54210108, 11515845246265065472), + new Int128(542101086, 4477988020393345024), + new Int128(5421010862, 7886392056514347008), + new Int128(54210108624, 5076944270305263616), + new Int128(542101086242, 13875954555633532928), + new Int128(5421010862427, 9632337040368467968), + new Int128(54210108624275, 4089650035136921600), + new Int128(542101086242752, 4003012203950112768), + ]; + + static bool IDecimalIeee754TryParseInfo.TryNumberToDecimalIeee754(ref Number.NumberBuffer number, out Int128 significand, out int exponent) + => Number.TryNumberToDecimalIeee754(ref number, out significand, out exponent); + + static Decimal128 IDecimalIeee754TryParseInfo.Construct(Int128 significand, int exponent) => new Decimal128(significand, exponent); + + static int IDecimalIeee754TryParseInfo.DecimalNumberBufferLength => Number.Decimal128NumberBufferLength; + } +} 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 0000000000000..551c61c3e85ea --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -0,0 +1,227 @@ +// 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, + IDecimalIeee754ParseAndFormatInfo, + IDecimalIeee754ConstructorInfo, + IDecimalIeee754UnpackInfo, + IDecimalIeee754TryParseInfo + { + internal readonly uint _value; + + private const int MaxDecimalExponent = 90; + private const int MinDecimalExponent = -101; + private const int NumberDigitsPrecision = 7; + private const int Bias = 101; + private const int NumberBitsExponent = 8; + private const uint PositiveInfinityValue = 0x7800_0000; + private const uint NegativeInfinityValue = 0xF800_0000; + private const uint ZeroValue = 0x00000000; + private const int MaxSignificand = 9_999_999; + private const uint G0G1Mask = 0x6000_0000; + private const uint SignMask = 0x8000_0000; + private const uint MostSignificantBitOfSignificandMask = 0x0080_0000; + + public Decimal32(int significand, int exponent) + { + _value = Number.CalDecimalIeee754(significand, exponent); + } + + public static Decimal32 Parse(string s) => Parse(s, NumberStyles.Number, provider: null); + + 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); + + public static Decimal32 Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); + + public static Decimal32 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.ParseDecimal32(s, style, NumberFormatInfo.GetInstance(provider)); + } + + public static Decimal32 Parse(string s, NumberStyles style, IFormatProvider? provider) + { + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); + } + public static bool TryParse([NotNullWhen(true)] string? s, out Decimal32 result) => TryParse(s, NumberStyles.Number, provider: null, out result); + 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); + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) => TryParse(s, NumberStyles.Number, provider, out result); + 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; + } + 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; + } + + private static ReadOnlySpan Int32Powers10 => + [ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + ]; + + // Compares this object to another object, returning an integer that + // indicates the relationship. + // Returns : + // 0 if the values are equal + // Negative number if _value is less than value + // Positive number if _value is more than value + // null is considered to be less than any instance, hence returns positive number + // If object is not of type Decimal32, this method throws an ArgumentException. + // + 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; + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Decimal32 && Equals((Decimal32)obj); + } + + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + public override string ToString() + { + return Number.FormatDecimal32(this, null, NumberFormatInfo.CurrentInfo); + } + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) + { + return Number.FormatDecimal32(this, format, NumberFormatInfo.CurrentInfo); + } + public string ToString(IFormatProvider? provider) + { + return Number.FormatDecimal32(this, null, NumberFormatInfo.GetInstance(provider)); + } + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) + { + return Number.FormatDecimal32(this, format, NumberFormatInfo.GetInstance(provider)); + } + + static int IDecimalIeee754ParseAndFormatInfo.NumberDigitsPrecision => NumberDigitsPrecision; + + static int IDecimalIeee754ParseAndFormatInfo.MaxScale => 97; + + static int IDecimalIeee754ConstructorInfo.CountDigits(int number) => FormattingHelpers.CountDigits((uint)number); + static int IDecimalIeee754ConstructorInfo.Power10(int exponent) => Int32Powers10[exponent]; + + static int IDecimalIeee754ConstructorInfo.MaxSignificand => MaxSignificand; + + static int IDecimalIeee754ConstructorInfo.MaxDecimalExponent => MaxDecimalExponent; + + static int IDecimalIeee754ConstructorInfo.MinDecimalExponent => MinDecimalExponent; + + static int IDecimalIeee754ConstructorInfo.NumberDigitsPrecision => NumberDigitsPrecision; + + static int IDecimalIeee754ConstructorInfo.Bias => Bias; + + static int IDecimalIeee754ConstructorInfo.NumberBitsEncoding => 32; + + static int IDecimalIeee754ConstructorInfo.NumberBitsCombinationField => 11; + + static uint IDecimalIeee754ConstructorInfo.PositiveInfinityBits => PositiveInfinityValue; + + static uint IDecimalIeee754ConstructorInfo.NegativeInfinityBits => NegativeInfinityValue; + + static int IDecimalIeee754ConstructorInfo.NumberBitsExponent => NumberBitsExponent; + + static uint IDecimalIeee754ConstructorInfo.Zero => ZeroValue; + + static uint IDecimalIeee754ConstructorInfo.G0G1Mask => G0G1Mask; + + static uint IDecimalIeee754ConstructorInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask; + + static uint IDecimalIeee754ConstructorInfo.SignMask => SignMask; + + static string IDecimalIeee754ConstructorInfo.OverflowMessage => SR.Overflow_Decimal32; + + static uint IDecimalIeee754UnpackInfo.SignMask => SignMask; + + static int IDecimalIeee754UnpackInfo.Bias => Bias; + + static int IDecimalIeee754UnpackInfo.NumberDigitsPrecision => NumberDigitsPrecision; + + static int IDecimalIeee754UnpackInfo.ConvertToExponent(uint value) => (int)value; + + static int IDecimalIeee754UnpackInfo.ConvertToSignificand(uint value) => (int)value; + + static int IDecimalIeee754UnpackInfo.Power10(int exponent) => Int32Powers10[exponent]; + + static uint IDecimalIeee754UnpackInfo.G0G1Mask => G0G1Mask; + + static uint IDecimalIeee754UnpackInfo.G0ToGwPlus1ExponentMask => 0x7F80_0000; + + static uint IDecimalIeee754UnpackInfo.G2ToGwPlus3ExponentMask => 0x1FE0_0000; + + static uint IDecimalIeee754UnpackInfo.GwPlus2ToGwPlus4SignificandMask => 0x007F_FFFF; + + static uint IDecimalIeee754UnpackInfo.GwPlus4SignificandMask => 0x001F_FFFF; + + static int IDecimalIeee754UnpackInfo.NumberBitsSignificand => 20; + + static uint IDecimalIeee754UnpackInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask; + + static int IDecimalIeee754TryParseInfo.DecimalNumberBufferLength => Number.Decimal32NumberBufferLength; + + static bool IDecimalIeee754TryParseInfo.TryNumberToDecimalIeee754(ref Number.NumberBuffer number, out int significand, out int exponent) + => Number.TryNumberToDecimalIeee754(ref number, out significand, out exponent); + + static Decimal32 IDecimalIeee754TryParseInfo.Construct(int significand, int exponent) => new Decimal32(significand, exponent); + } +} 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 0000000000000..79e7560e39272 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -0,0 +1,237 @@ +// 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, + IDecimalIeee754ParseAndFormatInfo, + IDecimalIeee754ConstructorInfo, + IDecimalIeee754UnpackInfo, + IDecimalIeee754TryParseInfo + { + internal readonly ulong _value; + + private const int MaxDecimalExponent = 369; + private const int MinDecimalExponent = -398; + private const int NumberDigitsPrecision = 16; + private const int Bias = 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 long MaxSignificand = 9_999_999_999_999_999; + private const ulong G0G1Mask = 0x6000_0000_0000_0000; + private const ulong SignMask = 0x8000_0000_0000_0000; + private const ulong MostSignificantBitOfSignificandMask = 0x0020_0000_0000_0000; + + public Decimal64(long significand, int exponent) + { + _value = Number.CalDecimalIeee754(significand, exponent); + } + + internal Decimal64(ulong value) + { + _value = value; + } + + public static Decimal64 Parse(string s) => Parse(s, NumberStyles.Number, provider: null); + + 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); + + public static Decimal64 Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); + + public static Decimal64 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.ParseDecimal64(s, style, NumberFormatInfo.GetInstance(provider)); + } + private static ReadOnlySpan Int64Powers10 => + [ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + ]; + + public static Decimal64 Parse(string s, NumberStyles style, IFormatProvider? provider) + { + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); + } + + public static bool TryParse([NotNullWhen(true)] string? s, out Decimal64 result) => TryParse(s, NumberStyles.Number, provider: null, out result); + 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); + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) => TryParse(s, NumberStyles.Number, provider, out result); + 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; + } + 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; + } + + // Compares this object to another object, returning an integer that + // indicates the relationship. + // Returns a value less than zero if this object + // null is considered to be less than any instance. + // If object is not of type Decimal64, this method throws an ArgumentException. + // + public int CompareTo(object? value) + { + if (value == null) + { + return 1; + } + + if (value is not Decimal64 i) + { + throw new ArgumentException(SR.Arg_MustBeDecimal64); + } + + return Number.CompareDecimalIeee754(_value, i._value); + } + + public int CompareTo(Decimal64 other) + { + return Number.CompareDecimalIeee754(_value, other._value); + } + + public bool Equals(Decimal64 other) + { + return Number.CompareDecimalIeee754(_value, other._value) == 0; + } + + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Decimal64 && Equals((Decimal64)obj); + } + + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + public override string ToString() + { + return Number.FormatDecimal64(this, null, NumberFormatInfo.CurrentInfo); + } + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) + { + return Number.FormatDecimal64(this, format, NumberFormatInfo.CurrentInfo); + } + public string ToString(IFormatProvider? provider) + { + return Number.FormatDecimal64(this, null, NumberFormatInfo.GetInstance(provider)); + } + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) + { + return Number.FormatDecimal64(this, format, NumberFormatInfo.GetInstance(provider)); + } + + static int IDecimalIeee754ParseAndFormatInfo.NumberDigitsPrecision => NumberDigitsPrecision; + + static int IDecimalIeee754ParseAndFormatInfo.MaxScale => 385; + + static int IDecimalIeee754ConstructorInfo.CountDigits(long number) => FormattingHelpers.CountDigits((ulong)number); + + static long IDecimalIeee754ConstructorInfo.Power10(int exponent) => Int64Powers10[exponent]; + + static long IDecimalIeee754ConstructorInfo.MaxSignificand => MaxSignificand; + + static int IDecimalIeee754ConstructorInfo.MaxDecimalExponent => MaxDecimalExponent; + + static int IDecimalIeee754ConstructorInfo.MinDecimalExponent => MinDecimalExponent; + + static int IDecimalIeee754ConstructorInfo.NumberDigitsPrecision => NumberDigitsPrecision; + + static int IDecimalIeee754ConstructorInfo.Bias => Bias; + + static int IDecimalIeee754ConstructorInfo.NumberBitsEncoding => 64; + + static int IDecimalIeee754ConstructorInfo.NumberBitsCombinationField => 13; + + static int IDecimalIeee754ConstructorInfo.NumberBitsExponent => NumberBitsExponent; + + static ulong IDecimalIeee754ConstructorInfo.PositiveInfinityBits => PositiveInfinityValue; + + static ulong IDecimalIeee754ConstructorInfo.NegativeInfinityBits => NegativeInfinityValue; + + static ulong IDecimalIeee754ConstructorInfo.Zero => ZeroValue; + + static ulong IDecimalIeee754ConstructorInfo.G0G1Mask => G0G1Mask; + + static ulong IDecimalIeee754ConstructorInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask; + + static ulong IDecimalIeee754ConstructorInfo.SignMask => SignMask; + + static string IDecimalIeee754ConstructorInfo.OverflowMessage => SR.Overflow_Decimal64; + + static int IDecimalIeee754UnpackInfo.ConvertToExponent(ulong value) => (int)value; + + static long IDecimalIeee754UnpackInfo.ConvertToSignificand(ulong value) => (long)value; + + static long IDecimalIeee754UnpackInfo.Power10(int exponent) => Int64Powers10[exponent]; + + static ulong IDecimalIeee754UnpackInfo.SignMask => SignMask; + + static int IDecimalIeee754UnpackInfo.Bias => Bias; + + static int IDecimalIeee754UnpackInfo.NumberDigitsPrecision => NumberDigitsPrecision; + + static ulong IDecimalIeee754UnpackInfo.G0G1Mask => G0G1Mask; + + static ulong IDecimalIeee754UnpackInfo.G0ToGwPlus1ExponentMask => 0x7FE0_0000_0000_0000; + + static ulong IDecimalIeee754UnpackInfo.G2ToGwPlus3ExponentMask => 0x1FF8_0000_0000_0000; + + static ulong IDecimalIeee754UnpackInfo.GwPlus2ToGwPlus4SignificandMask => 0x001F_FFFF_FFFF_FFFF; + + static ulong IDecimalIeee754UnpackInfo.GwPlus4SignificandMask => 0x0007_FFFF_FFFF_FFFF; + + static int IDecimalIeee754UnpackInfo.NumberBitsSignificand => 50; + + static ulong IDecimalIeee754UnpackInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask; + + static int IDecimalIeee754TryParseInfo.DecimalNumberBufferLength => Number.Decimal64NumberBufferLength; + + static bool IDecimalIeee754TryParseInfo.TryNumberToDecimalIeee754(ref Number.NumberBuffer number, out long significand, out int exponent) + => Number.TryNumberToDecimalIeee754(ref number, out significand, out exponent); + + static Decimal64 IDecimalIeee754TryParseInfo.Construct(long significand, int exponent) => new Decimal64(significand, exponent); + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index a08103e2de4a0..cbba9b7d037d8 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11096,6 +11096,103 @@ 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 + { + 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 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 + { + 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 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 + { + 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 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 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 32208e334d550..5e5f007203a4c 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 @@ -74,6 +74,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 0000000000000..5f0a764511c4c --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -0,0 +1,330 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +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 = "."; + + var customFormat3 = new NumberFormatInfo(); + customFormat3.NumberGroupSeparator = ","; + + var customFormat4 = new NumberFormatInfo(); + customFormat4.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[] { 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) }; + } + + + [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[] { "1" + new string('0', 6145), defaultStyle, null, typeof(OverflowException) }; + 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); + } + + [Fact] + public static void CompareTo_Other_ReturnsExpected() + { + 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); + Assert.Equal(d1, d2); + } + Assert.Equal(new Decimal128(-1, 1), new Decimal128(-10, 0)); + Assert.Equal(new Decimal128(1, 6144), new Decimal128(10, 6143)); + Assert.Equal(new Decimal128(Int128.Parse(new string('9', 33)), 6111), new Decimal128(Int128.Parse(new string('9', 33) + "0"), 6110)); + Assert.NotEqual(new Decimal128(1, 1), new Decimal128(-10, 0)); + Assert.NotEqual(new Decimal128(-1, 1), new Decimal128(10, 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()); + } + + 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 0000000000000..e3ad15422c4fd --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -0,0 +1,329 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +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 = "."; + + var customFormat3 = new NumberFormatInfo(); + customFormat3.NumberGroupSeparator = ","; + + var customFormat4 = new NumberFormatInfo(); + customFormat4.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[] { 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) }; + } + + + [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[] { "1" + new string('0', 97), defaultStyle, null, typeof(OverflowException) }; + 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); + } + + [Fact] + public static void CompareTo_Other_ReturnsExpected() + { + 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); + Assert.Equal(d1, d2); + } + Assert.Equal(new Decimal32(-1, 1), new Decimal32(-10, 0)); + Assert.Equal(new Decimal32(1, 90), new Decimal32(10, 89)); + Assert.Equal(new Decimal32(999999, 90), new Decimal32(9999990, 89)); + Assert.NotEqual(new Decimal32(1, 1), new Decimal32(-10, 0)); + Assert.NotEqual(new Decimal32(-1, 1), new Decimal32(10, 0)); + } + + [Fact] + public static void CompareToTest() + { + var d1 = new Decimal32(-1, 1); + var d2 = new Decimal32(-10, 0); + Assert.Equal(0, d1.CompareTo(d2)); + + d1 = new Decimal32(1, 1); + d2 = new Decimal32(-1, 0); + Assert.Equal(1, d1.CompareTo(d2)); + Assert.Equal(-1, d2.CompareTo(d1)); + } + + [Fact] + public static void GetHashCodeTest() + { + var d = new Decimal32(10, 20); + Assert.Equal(d.GetHashCode(), d.GetHashCode()); + } + + 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 0000000000000..d84e53352839c --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -0,0 +1,330 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Numerics; +using System.Text; +using System.Threading.Tasks; +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 = "."; + + var customFormat3 = new NumberFormatInfo(); + customFormat3.NumberGroupSeparator = ","; + + var customFormat4 = new NumberFormatInfo(); + customFormat4.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[] { "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[] { 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) }; + } + + + [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[] { "1" + new string('0', 385), defaultStyle, null, typeof(OverflowException) }; + 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); + } + + [Fact] + public static void CompareTo_Other_ReturnsExpected() + { + 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); + Assert.Equal(d1, d2); + } + Assert.Equal(new Decimal64(-1, 1), new Decimal64(-10, 0)); + Assert.Equal(new Decimal64(1, 369), new Decimal64(10, 368)); + Assert.Equal(new Decimal64(long.Parse(new string('9', 15)), 369), new Decimal64(long.Parse(new string('9', 15) + "0"), 368)); + Assert.NotEqual(new Decimal64(1, 1), new Decimal64(-10, 0)); + Assert.NotEqual(new Decimal64(-1, 1), new Decimal64(10, 0)); + } + + [Fact] + public static void CompareToTest() + { + var d1 = new Decimal64(-1, 1); + var d2 = new Decimal64(-10, 0); + Assert.Equal(0, d1.CompareTo(d2)); + + d1 = new Decimal64(1, 1); + d2 = new Decimal64(-1, 0); + Assert.Equal(1, d1.CompareTo(d2)); + Assert.Equal(-1, d2.CompareTo(d1)); + } + + [Fact] + public static void GetHashCodeTest() + { + var d = new Decimal64(10, 20); + Assert.Equal(d.GetHashCode(), d.GetHashCode()); + } + + 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)); + } + } +}