diff --git a/src/libraries/Common/src/System/Number.NumberBuffer.cs b/src/libraries/Common/src/System/Number.NumberBuffer.cs index 44c1d47d96dcab..325f752ec625c2 100644 --- a/src/libraries/Common/src/System/Number.NumberBuffer.cs +++ b/src/libraries/Common/src/System/Number.NumberBuffer.cs @@ -21,6 +21,9 @@ internal static partial class Number internal const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295 internal const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615 internal const int UInt128NumberBufferLength = 39 + 1; // 39 for the longest input: 340,282,366,920,938,463,463,374,607,431,768,211,455 + internal const int Decimal32NumberBufferLength = 97 + 1 + 1; // 97 for the longest input + 1 for rounding + internal const int Decimal64NumberBufferLength = 385 + 1 + 1; // 385 for the longest input + 1 for rounding + internal const int Decimal128NumberBufferLength = 6145 + 1 + 1; // 6145 for the longest input + 1 for rounding internal unsafe ref struct NumberBuffer { diff --git a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx index 2ced3b884d6fa3..b8252764421382 100644 --- a/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx +++ b/src/libraries/System.Private.CoreLib/src/Resources/Strings.resx @@ -540,6 +540,15 @@ Object must be of type Decimal. + + Object must be of type Decimal32. + + + Object must be of type Decimal64. + + + Object must be of type Decimal128. + Type must derive from Delegate. diff --git a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems index a25bcdfc984f28..7bc265f531bcb8 100644 --- a/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems +++ b/src/libraries/System.Private.CoreLib/src/System.Private.CoreLib.Shared.projitems @@ -436,6 +436,10 @@ + + + + diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs b/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs index 733faa5320aab3..adf8cb452b94cb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs @@ -1324,6 +1324,31 @@ public ulong ToUInt64() return 0; } + public UInt128 ToUInt128() + { + if (_length > 3) + { + return new UInt128(((ulong)(_blocks[3]) << 96) + ((ulong)_blocks[2] << 64), ((ulong)(_blocks[1]) << 32) + _blocks[0]); + } + + if (_length > 2) + { + return new UInt128(((ulong)_blocks[2] << 64), ((ulong)(_blocks[1]) << 32) + _blocks[0]); + } + + if (_length > 1) + { + return ((ulong)(_blocks[1]) << 32) + _blocks[0]; + } + + if (_length > 0) + { + return _blocks[0]; + } + + return 0; + } + private void Clear(uint length) => NativeMemory.Clear( (byte*)Unsafe.AsPointer(ref _blocks[0]), // This is safe to do since we are a ref struct diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs new file mode 100644 index 00000000000000..9b801cb7380409 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -0,0 +1,352 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Diagnostics; +using System.Numerics; + +namespace System +{ + internal static partial class Number + { + internal static unsafe TValue ConstructorToDecimalIeee754Bits(bool signed, TValue significand, int exponent) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + if (TValue.IsZero(significand)) + { + return TDecimal.Zero; + } + + if (significand > TDecimal.MaxSignificand || exponent > TDecimal.MaxExponent || exponent < TDecimal.MinExponent) + { + return ConstructorToDecimalIeee754BitsRounding(signed, significand, exponent); + } + + return DecimalIeee754BinaryEncoding(signed, significand, exponent); + + // This method adjusts the significand and exponent to ensure they fall within valid bounds. + // It handles underflow and overflow of the exponent by trimming or padding digits accordingly, + // and applies rounding when the number of digits exceeds the allowed precision. + static TValue ConstructorToDecimalIeee754BitsRounding(bool signed, TValue significand, int exponent) + { + byte* pDigits = stackalloc byte[TDecimal.SignificandBufferLength]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.Integer, pDigits, TDecimal.SignificandBufferLength); + TDecimal.ToNumber(significand, ref number); + + if (exponent < TDecimal.MinExponent) + { + int numberDigitsRemove = (TDecimal.MinExponent - exponent); + + if (numberDigitsRemove < number.DigitsCount) + { + int numberDigitsRemain = number.DigitsCount - numberDigitsRemove; + DecimalIeee754Rounding(ref number, numberDigitsRemain, out significand, out int extraExponent); + exponent += extraExponent; + } + else + { + return TDecimal.Zero; + } + } + else if (exponent > TDecimal.MaxExponent) + { + int numberZeroDigits = exponent - TDecimal.MaxExponent; + + if (number.DigitsCount + numberZeroDigits <= TDecimal.Precision) + { + byte* p = number.DigitsPtr + number.DigitsCount; + + for (int i = 0; i < numberZeroDigits; i++) + { + *p = (byte)'0'; + p++; + } + + *p = (byte)'\0'; + number.DigitsCount += numberZeroDigits; + number.Scale += numberZeroDigits; + + exponent -= numberZeroDigits; + significand = TDecimal.NumberToSignificand(ref number, number.DigitsCount); + } + else + { + return signed ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + } + else if (number.DigitsCount > TDecimal.Precision) + { + DecimalIeee754Rounding(ref number, TDecimal.Precision, out significand, out int extraExponent); + exponent += extraExponent; + + if (exponent >= TDecimal.MaxExponent) + { + return signed ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + } + + return DecimalIeee754BinaryEncoding(signed, significand, exponent); + } + } + + 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, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + bool signed = (value & TDecimal.SignMask) != TValue.Zero; + TValue significand; + int exponent; + + if ((value & TDecimal.G0G1Mask) == TDecimal.G0G1Mask) + { + exponent = TDecimal.ConvertToExponent((value & TDecimal.G2ToGwPlus3ExponentMask) >> (TDecimal.NumberBitsSignificand + 1)); + significand =(value & TDecimal.GwPlus4SignificandMask) | TDecimal.MostSignificantBitOfSignificandMask; + } + else + { + exponent = TDecimal.ConvertToExponent((value & TDecimal.G0ToGwPlus1ExponentMask) >> (TDecimal.NumberBitsSignificand + 3)); + significand = value & TDecimal.GwPlus2ToGwPlus4SignificandMask; + } + + return new DecimalIeee754(signed, exponent - TDecimal.ExponentBias, significand); + } + + internal static int CompareDecimalIeee754(TValue currentValue, TValue otherValue) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + if (currentValue == otherValue) + { + return 0; + } + DecimalIeee754 current = UnpackDecimalIeee754(currentValue); + DecimalIeee754 other = UnpackDecimalIeee754(otherValue); + + if (current.Signed) + { + if (!other.Signed) + { + return -1; + } + } + else if (other.Signed) + { + return 1; + } + + if (current.Signed) + { + (current, other) = (other, current); + } + + // This method is needed to correctly compare decimals that represent the same numeric value + // but have different exponent/significand pairs. For example, 10e2 and 1e3 have different exponents, + // but represent the same number (1000). This function normalizes exponents and compares them accordingly, + // without considering sign. + return InternalUnsignedCompare(current, other); + + static int InternalUnsignedCompare(DecimalIeee754 current, DecimalIeee754 other) + { + if (current.Exponent == other.Exponent && current.Significand == other.Significand) + { + return 0; + } + + if (current.Exponent < other.Exponent) + { + return -InternalUnsignedCompare(other, current); + } + + if (current.Significand >= other.Significand) + { + return 1; + } + + int diffExponent = current.Exponent - other.Exponent; + if (diffExponent < TDecimal.Precision) + { + TValue factor = TDecimal.Power10(diffExponent); + (TValue quotient, TValue remainder) = TValue.DivRem(other.Significand, current.Significand); + + if (quotient < factor) + { + return 1; + } + if (quotient > factor) + { + return -1; + } + if (remainder > TValue.Zero) + { + return -1; + } + return 0; + } + + return 1; + } + } + + private static unsafe TValue NumberToDecimalIeee754Bits(ref NumberBuffer number) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + Debug.Assert(number.DigitsPtr[0] != '0'); + Debug.Assert(number.DigitsCount != 0); + + TValue significand; + int exponent; + + if (number.DigitsCount > TDecimal.Precision) + { + DecimalIeee754Rounding(ref number, TDecimal.Precision, out significand, out exponent); + + if (exponent > TDecimal.MaxExponent) + { + return number.IsNegative ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + } + else + { + int positiveExponent = (Math.Max(0, number.Scale)); + int integerDigitsPresent = Math.Min(positiveExponent, number.DigitsCount); + int fractionalDigitsPresent = number.DigitsCount - integerDigitsPresent; + exponent = number.Scale - integerDigitsPresent - fractionalDigitsPresent; + + if (exponent < TDecimal.MinExponent) + { + return SmallExponentRounding(ref number, exponent); + } + + significand = TDecimal.NumberToSignificand(ref number, number.DigitsCount); + return DecimalIeee754BinaryEncoding(number.IsNegative, significand, exponent); + } + return DecimalIeee754BinaryEncoding(number.IsNegative, significand, exponent); + + static TValue SmallExponentRounding(ref NumberBuffer number, int exponent) + { + Debug.Assert(exponent < TDecimal.MinExponent); + int numberDigitsRemove = (TDecimal.MinExponent - exponent); + if (numberDigitsRemove < number.DigitsCount) + { + int numberDigitsRemain = number.DigitsCount - numberDigitsRemove; + DecimalIeee754Rounding(ref number, numberDigitsRemain, out TValue significand, out exponent); + return DecimalIeee754BinaryEncoding(number.IsNegative, significand, exponent); + } + else + { + return TDecimal.Zero; + } + } + } + + private static unsafe TValue DecimalIeee754BinaryEncoding(bool signed, TValue significand, int exponent) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + exponent += TDecimal.ExponentBias; + + TValue value = TValue.Zero; + TValue exponentVal = TValue.CreateTruncating(exponent); + bool msbSignificand = (significand & TDecimal.MostSignificantBitOfSignificandMask) != TValue.Zero; + + if (signed) + { + value = TDecimal.SignMask; + } + + if (msbSignificand) + { + value |= TDecimal.G0G1Mask; + exponentVal <<= TDecimal.NumberBitsEncoding - TDecimal.NumberBitsExponent - 3; + value |= exponentVal; + significand ^= TDecimal.MostSignificantBitOfSignificandMask; + value |= significand; + } + else + { + exponentVal <<= TDecimal.NumberBitsEncoding - TDecimal.NumberBitsExponent - 1; + value |= exponentVal; + value |= significand; + } + + return value; + } + + private static unsafe void DecimalIeee754Rounding(ref NumberBuffer number, int digits, out TValue significand, out int exponent) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + Debug.Assert(digits < number.DigitsCount); + + significand = TDecimal.NumberToSignificand(ref number, digits); + + int positiveExponent = (Math.Max(0, number.Scale)); + int integerDigitsPresent = Math.Min(positiveExponent, number.DigitsCount); + int fractionalDigitsPresent = number.DigitsCount - integerDigitsPresent; + exponent = number.Scale - integerDigitsPresent - fractionalDigitsPresent; + + exponent += number.DigitsCount - digits; + + bool increaseOne = false; + int midPointValue = *(number.DigitsPtr + digits); + + if (midPointValue > '5') + { + increaseOne = true; + } + else if (midPointValue == '5') + { + int index = digits + 1; + byte* p = number.DigitsPtr + index; + int c = *p; + bool tiedToEvenRounding = true; + + while (index < number.DigitsCount && c != 0) + { + if (c != '0') + { + increaseOne = true; + tiedToEvenRounding = false; + break; + } + ++index; + c = *++p; + } + + if (tiedToEvenRounding && !int.IsEvenInteger(*(number.DigitsPtr + digits - 1) - '0')) + { + increaseOne = true; + } + } + + if (increaseOne) + { + if (significand == TDecimal.MaxSignificand) + { + significand = TDecimal.Power10(TDecimal.Precision - 1); + exponent += 1; + } + else + { + significand += TValue.One; + } + } + } + } +} 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 2062aa526cb386..d954a629b3d62f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -1,6 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers; using System.Buffers.Text; using System.Collections.Generic; using System.Diagnostics; @@ -329,6 +330,42 @@ private static ref byte GetTwoDigitsBytesRef(bool useChars) => ref MemoryMarshal.GetReference(useChars ? TwoDigitsCharsAsBytes : TwoDigitsBytes); #endif + public static unsafe string FormatDecimalIeee754(TDecimal value, ReadOnlySpan format, NumberFormatInfo info) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + char fmt = ParseFormatSpecifier(format, out int digits); + byte[] buffer = ArrayPool.Shared.Rent(TDecimal.BufferLength); + try + { + fixed (byte* pDigits = buffer) + { + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, pDigits, TDecimal.BufferLength); + + DecimalIeee754ToNumber(value, ref number); + + char* stackPtr = stackalloc char[CharStackBufferSize]; + var vlb = new ValueListBuilder(new Span(stackPtr, CharStackBufferSize)); + + if (fmt != 0) + { + NumberToString(ref vlb, ref number, fmt, digits, info); + } + else + { + NumberToStringFormat(ref vlb, ref number, format, info); + } + + string result = vlb.AsSpan().ToString(); + vlb.Dispose(); + return result; + } + } + finally + { + ArrayPool.Shared.Return(buffer); + } + } public static unsafe string FormatDecimal(decimal value, ReadOnlySpan format, NumberFormatInfo info) { @@ -384,6 +421,50 @@ public static unsafe bool TryFormatDecimal(decimal value, ReadOnlySpan(TDecimal value, ref NumberBuffer number) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + byte* buffer = number.DigitsPtr; + DecimalIeee754 unpackDecimal = value.Unpack(); + number.IsNegative = unpackDecimal.Signed; + + byte* p = buffer + TDecimal.Precision; + p = TDecimal.ToDecChars(p, unpackDecimal.Significand); + int numberDigitsSignificand = (int)((buffer + TDecimal.Precision) - p); + + byte* dst = number.DigitsPtr; + int i = numberDigitsSignificand; + while (--i >= 0) + { + *dst++ = *p++; + } + + number.Scale = TValue.IsZero(unpackDecimal.Significand) ? 0 : numberDigitsSignificand + unpackDecimal.Exponent; + + if (unpackDecimal.Exponent >= 0) + { + number.DigitsCount = numberDigitsSignificand + unpackDecimal.Exponent; + + if (unpackDecimal.Exponent > 0) + { + i = unpackDecimal.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) { byte* buffer = number.DigitsPtr; @@ -1576,7 +1657,7 @@ private static unsafe bool TryUInt32ToBinaryStr(uint value, int digits, S } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number) + internal static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number) { number.DigitsCount = UInt32Precision; number.IsNegative = false; @@ -2041,7 +2122,7 @@ private static unsafe bool TryUInt64ToBinaryStr(ulong value, int digits, } [MethodImpl(MethodImplOptions.AggressiveInlining)] - private static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number) + internal static unsafe void UInt64ToNumber(ulong value, ref NumberBuffer number) { number.DigitsCount = UInt64Precision; number.IsNegative = false; @@ -2453,7 +2534,7 @@ private static unsafe bool TryUInt128ToBinaryStr(Int128 value, int digits } } - private static unsafe void UInt128ToNumber(UInt128 value, ref NumberBuffer number) + internal static unsafe void UInt128ToNumber(UInt128 value, ref NumberBuffer number) { number.DigitsCount = UInt128Precision; number.IsNegative = false; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs index 303ae2f43c9222..50bca18dfe3dc0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs @@ -696,7 +696,7 @@ internal unsafe partial class Number 0x8e679c2f5e44ff8f, 0x570f09eaa7ea7648 ]; - private static void AccumulateDecimalDigitsIntoBigInteger(scoped ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result) + internal static void AccumulateDecimalDigitsIntoBigInteger(scoped ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result) { BigInteger.SetZero(out result); @@ -890,7 +890,7 @@ private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger } // get 32-bit integer from at most 9 digits - private static uint DigitsToUInt32(byte* p, int count) + internal static uint DigitsToUInt32(byte* p, int count) { Debug.Assert((1 <= count) && (count <= 9)); @@ -914,7 +914,7 @@ private static uint DigitsToUInt32(byte* p, int count) } // get 64-bit integer from at most 19 digits - private static ulong DigitsToUInt64(byte* p, int count) + internal static ulong DigitsToUInt64(byte* p, int count) { Debug.Assert((1 <= count) && (count <= 19)); diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Parsing.cs index bb80ebd3ff67a2..9b70e12ee1abae 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,41 @@ internal interface IBinaryFloatParseAndFormatInfo : IBinaryFloatingPointI static abstract int MaxPrecisionCustomFormat { get; } } + internal interface IDecimalIeee754ParseAndFormatInfo + where TSelf : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + static abstract int Precision { get; } + static abstract int MaxScale { get; } + static abstract int MinScale { get; } + static abstract int BufferLength { get; } + static abstract int MaxExponent { get; } + static abstract int MinExponent { get; } + static abstract int ExponentBias { get; } + static abstract TValue PositiveInfinity { get; } + static abstract TValue NegativeInfinity { get; } + static abstract TValue Zero { get; } + static abstract TValue MaxSignificand { get; } + static abstract TValue NumberToSignificand(ref Number.NumberBuffer number, int digits); + static abstract unsafe byte* ToDecChars(byte* p, TValue significand); + static abstract int ConvertToExponent(TValue value); + static abstract TValue Power10(int exponent); + Number.DecimalIeee754 Unpack(); + static abstract TSelf Construct(TValue value); + static abstract void ToNumber(TValue significand, ref Number.NumberBuffer number); + static abstract int NumberBitsEncoding { get; } + static abstract int NumberBitsExponent { get; } + static abstract int NumberBitsSignificand { get; } + static abstract TValue SignMask { get; } + static abstract TValue G0G1Mask { get; } + static abstract TValue G0ToGwPlus1ExponentMask { get; } //G0 to G(w+1) + static abstract TValue G2ToGwPlus3ExponentMask { get; } //G2 to G(w+3) + static abstract TValue GwPlus2ToGwPlus4SignificandMask { get; } //G(w+2) to G(w+4) + static abstract TValue GwPlus4SignificandMask { get; } //G(w+4) + static abstract TValue MostSignificantBitOfSignificandMask { get; } + static abstract int SignificandBufferLength { get; } + } + internal static partial class Number { private const int Int32Precision = 10; @@ -733,6 +768,21 @@ internal static decimal ParseDecimal(ReadOnlySpan value, NumberSty return result; } + internal static TDecimal ParseDecimalIeee754(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info) + where TChar : unmanaged, IUtfChar + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + ParsingStatus status = TryParseDecimalIeee754(value, styles, info, out TDecimal result); + + if (status == ParsingStatus.Failed) + { + ThrowFormatException(value); + } + + return result; + } + internal static unsafe bool TryNumberToDecimal(ref NumberBuffer number, ref decimal value) { number.CheckConsistency(); @@ -884,6 +934,24 @@ internal static ParsingStatus TryParseDecimal(ReadOnlySpan value, return ParsingStatus.OK; } + internal static ParsingStatus TryParseDecimalIeee754(ReadOnlySpan value, NumberStyles styles, NumberFormatInfo info, out TDecimal result) + where TChar : unmanaged, IUtfChar + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + NumberBuffer number = new NumberBuffer(NumberBufferKind.Decimal, stackalloc byte[TDecimal.BufferLength]); + result = default; + + if (!TryStringToNumber(value, styles, ref number, info)) + { + return ParsingStatus.Failed; + } + + result = NumberToDecimalIeee754(ref number); + + return ParsingStatus.OK; + } + internal static bool SpanStartsWith(ReadOnlySpan span, TChar c) where TChar : unmanaged, IUtfChar { @@ -1101,5 +1169,27 @@ internal static TFloat NumberToFloat(ref NumberBuffer number) return number.IsNegative ? -result : result; } + + internal static TDecimal NumberToDecimalIeee754(ref NumberBuffer number) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + number.CheckConsistency(); + TValue value; + + if ((number.DigitsCount == 0) || (number.Scale < TDecimal.MinScale)) + { + value = TDecimal.Zero; + } + else if (number.Scale > TDecimal.MaxScale) + { + value = number.IsNegative ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + else + { + value = NumberToDecimalIeee754Bits(ref number); + } + return TDecimal.Construct(value); + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs new file mode 100644 index 00000000000000..0017bdbf07c5fd --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -0,0 +1,358 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +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 + { +#if BIGENDIAN + internal readonly ulong _upper; + internal readonly ulong _lower; +#else + internal readonly ulong _lower; + internal readonly ulong _upper; +#endif + + private const int MaxExponent = 6111; + private const int MinExponent = -6176; + private const int Precision = 34; + private const int ExponentBias = 6176; + private const int NumberBitsExponent = 14; + private static readonly UInt128 PositiveInfinityValue = new UInt128(upper: 0x7800_0000_0000_0000, lower: 0); + private static readonly UInt128 NegativeInfinityValue = new UInt128(upper: 0xf800_0000_0000_0000, lower: 0); + private static readonly UInt128 ZeroValue = new UInt128(0, 0); + + internal Decimal128(UInt128 value) + { + _upper = value.Upper; + _lower = value.Lower; + } + + public Decimal128(Int128 significand, int exponent) + { + UInt128 value = Number.ConstructorToDecimalIeee754Bits(significand < 0, (UInt128)(significand < 0 ? -significand : significand), exponent); + _upper = value.Upper; + _lower = value.Lower; + } + + /// + /// Parses a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned. + public static Decimal128 Parse(string s) => Parse(s, NumberStyles.Number, provider: null); + + /// + /// Parses a from a in the given . + /// + /// The input to be parsed. + /// The used to parse the input. + /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned. + public static Decimal128 Parse(string s, NumberStyles style) => Parse(s, style, provider: null); + + /// + public static Decimal128 Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); + + /// + /// Parses a from a and . + /// + /// The input to be parsed. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned. + public static Decimal128 Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); + + /// + /// Parses a from a and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned. + public static Decimal128 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.ParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + /// Parses a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Decimal128's range, a or is returned. + public static Decimal128 Parse(string s, NumberStyles style, IFormatProvider? provider) + { + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); + } + + /// + /// Tries to parse a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse([NotNullWhen(true)] string? s, out Decimal128 result) => TryParse(s, NumberStyles.Number, provider: null, out result); + + /// + /// Tries to parse a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse(ReadOnlySpan s, out Decimal128 result) => TryParse(s, NumberStyles.Number, provider: null, out result); + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) => TryParse(s, NumberStyles.Number, provider, out result); + + /// + /// Tries to parse a from a with the given . + /// + /// The input to be parsed. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) => TryParse(s, NumberStyles.Number, provider, out result); + + /// + /// Tries to parse a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + /// + /// Tries to parse a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal128's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal128 result) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + + if (s == null) + { + result = default; + return false; + } + return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + /// + public int CompareTo(object? value) + { + if (value is not Decimal128 other) + { + return (value is null) ? 1 : throw new ArgumentException(SR.Arg_MustBeDecimal128); + } + return CompareTo(other); + } + + /// + public int CompareTo(Decimal128 other) + { + var current = new UInt128(_upper, _lower); + var another = new UInt128(other._upper, other._lower); + return Number.CompareDecimalIeee754(current, another); + } + + /// + public bool Equals(Decimal128 other) + { + var current = new UInt128(_upper, _lower); + var another = new UInt128(other._upper, other._lower); + return Number.CompareDecimalIeee754(current, another) == 0; + } + + /// + /// Returns a value that indicates whether this instance is equal to a specified . + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Decimal128 && Equals((Decimal128)obj); + } + + /// + /// Serves as the default hash function. + /// + public override int GetHashCode() + { + return new UInt128(_upper, _lower).GetHashCode(); + } + + /// + /// Returns a string representation of the current value. + /// + public override string ToString() + { + return Number.FormatDecimalIeee754(this, null, NumberFormatInfo.CurrentInfo); + } + + /// + /// Returns a string representation of the current value using the specified . + /// + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) + { + return Number.FormatDecimalIeee754(this, format, NumberFormatInfo.CurrentInfo); + } + + /// + /// Returns a string representation of the current value with the specified . + /// + public string ToString(IFormatProvider? provider) + { + return Number.FormatDecimalIeee754(this, null, NumberFormatInfo.GetInstance(provider)); + } + + /// + /// Returns a string representation of the current value using the specified and . + /// + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) + { + return Number.FormatDecimalIeee754(this, format, NumberFormatInfo.GetInstance(provider)); + } + + private static UInt128[] UInt128Powers10 => + [ + new UInt128(0, 1), + new UInt128(0, 10), + new UInt128(0, 100), + new UInt128(0, 1000), + new UInt128(0, 10000), + new UInt128(0, 100000), + new UInt128(0, 1000000), + new UInt128(0, 10000000), + new UInt128(0, 100000000), + new UInt128(0, 1000000000), + new UInt128(0, 10000000000), + new UInt128(0, 100000000000), + new UInt128(0, 1000000000000), + new UInt128(0, 10000000000000), + new UInt128(0, 100000000000000), + new UInt128(0, 1000000000000000), + new UInt128(0, 10000000000000000), + new UInt128(0, 100000000000000000), + new UInt128(0, 1000000000000000000), + new UInt128(0, 10000000000000000000), + new UInt128(5, 7766279631452241920), + new UInt128(54, 3875820019684212736), + new UInt128(542, 1864712049423024128), + new UInt128(5421, 200376420520689664), + new UInt128(54210, 2003764205206896640), + new UInt128(542101, 1590897978359414784), + new UInt128(5421010, 15908979783594147840), + new UInt128(54210108, 11515845246265065472), + new UInt128(542101086, 4477988020393345024), + new UInt128(5421010862, 7886392056514347008), + new UInt128(54210108624, 5076944270305263616), + new UInt128(542101086242, 13875954555633532928), + new UInt128(5421010862427, 9632337040368467968), + new UInt128(54210108624275, 4089650035136921600), + new UInt128(542101086242752, 4003012203950112768), + ]; + + static int IDecimalIeee754ParseAndFormatInfo.MaxScale => 6145; + + static int IDecimalIeee754ParseAndFormatInfo.MinScale => -6175; + + static unsafe byte* IDecimalIeee754ParseAndFormatInfo.ToDecChars(byte* p, UInt128 significand) + { + return Number.UInt128ToDecChars(p, significand, 0); + } + + Number.DecimalIeee754 IDecimalIeee754ParseAndFormatInfo.Unpack() + { + return Number.UnpackDecimalIeee754(new UInt128(_upper, _lower)); + } + + static unsafe UInt128 IDecimalIeee754ParseAndFormatInfo.NumberToSignificand(ref Number.NumberBuffer number, int digits) + { + if (digits <= 19) + { + return Number.DigitsToUInt64(number.DigitsPtr, digits); + } + else + { + Number.AccumulateDecimalDigitsIntoBigInteger(ref number, 0, (uint)digits, out Number.BigInteger result); + return result.ToUInt128(); + } + } + + static Decimal128 IDecimalIeee754ParseAndFormatInfo.Construct(UInt128 value) => new Decimal128(value); + + static int IDecimalIeee754ParseAndFormatInfo.ConvertToExponent(UInt128 value) => (int)value; + + static UInt128 IDecimalIeee754ParseAndFormatInfo.Power10(int exponent) => UInt128Powers10[exponent]; + + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; + + static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal128NumberBufferLength; + + static int IDecimalIeee754ParseAndFormatInfo.MaxExponent => MaxExponent; + + static int IDecimalIeee754ParseAndFormatInfo.MinExponent => MinExponent; + + static UInt128 IDecimalIeee754ParseAndFormatInfo.PositiveInfinity => PositiveInfinityValue; + + static UInt128 IDecimalIeee754ParseAndFormatInfo.NegativeInfinity => NegativeInfinityValue; + + static UInt128 IDecimalIeee754ParseAndFormatInfo.Zero => ZeroValue; + + static UInt128 IDecimalIeee754ParseAndFormatInfo.MostSignificantBitOfSignificandMask => new UInt128(0x0002_0000_0000_0000, 0); + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsEncoding => 128; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsExponent => NumberBitsExponent; + + static UInt128 IDecimalIeee754ParseAndFormatInfo.SignMask => new UInt128(0x8000_0000_0000_0000, 0); + + static UInt128 IDecimalIeee754ParseAndFormatInfo.G0G1Mask => new UInt128(0x6000_0000_0000_0000, 0); + + static int IDecimalIeee754ParseAndFormatInfo.ExponentBias => ExponentBias; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsSignificand => 110; + + static UInt128 IDecimalIeee754ParseAndFormatInfo.G0ToGwPlus1ExponentMask => new UInt128(0x7FFE_0000_0000_0000, 0); + + static UInt128 IDecimalIeee754ParseAndFormatInfo.G2ToGwPlus3ExponentMask => new UInt128(0x1FFF_8000_0000_0000, 0); + + static UInt128 IDecimalIeee754ParseAndFormatInfo.GwPlus2ToGwPlus4SignificandMask => new UInt128(0x0001_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); + + static UInt128 IDecimalIeee754ParseAndFormatInfo.GwPlus4SignificandMask => new UInt128(0x0000_7FFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); + + static UInt128 IDecimalIeee754ParseAndFormatInfo.MaxSignificand => new UInt128(upper: 0x0001_ED09_BEAD_87C0, lower: 0x378D_8E63_FFFF_FFFF); // 9_999_999_999_999_999_999_999_999_999_999_999; + + static unsafe void IDecimalIeee754ParseAndFormatInfo.ToNumber(UInt128 significand, ref Number.NumberBuffer number) + { + Number.UInt128ToNumber(significand, ref number); + } + + static int IDecimalIeee754ParseAndFormatInfo.SignificandBufferLength => Number.UInt128NumberBufferLength; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs new file mode 100644 index 00000000000000..154452827c086f --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -0,0 +1,315 @@ +// 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 + { + internal readonly uint _value; + + internal Decimal32(uint value) + { + _value = value; + } + + private const int MaxExponent = 90; + private const int MinExponent = -101; + private const int Precision = 7; + private const int ExponentBias = 101; + private const int NumberBitsExponent = 8; + private const uint PositiveInfinityValue = 0x7800_0000; + private const uint NegativeInfinityValue = 0xF800_0000; + private const uint ZeroValue = 0x00000000; + private const uint G0G1Mask = 0x6000_0000; + private const uint SignMask = 0x8000_0000; + private const uint MostSignificantBitOfSignificandMask = 0x0080_0000; + private const uint MaxSignificand = 9_999_999; + + private static ReadOnlySpan UInt32Powers10 => + [ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + ]; + + public Decimal32(int significand, int exponent) + { + _value = Number.ConstructorToDecimalIeee754Bits(significand < 0, (uint)Math.Abs(significand), exponent); + } + + /// + /// Parses a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned. + public static Decimal32 Parse(string s) => Parse(s, NumberStyles.Number, provider: null); + + /// + /// Parses a from a in the given . + /// + /// The input to be parsed. + /// The used to parse the input. + /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned. + public static Decimal32 Parse(string s, NumberStyles style) => Parse(s, style, provider: null); + + /// + public static Decimal32 Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); + + /// + /// Parses a from a and . + /// + /// The input to be parsed. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned. + public static Decimal32 Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); + + /// + /// Parses a from a and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned. + public static Decimal32 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.ParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + /// Parses a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Decimal32's range, a or is returned. + public static Decimal32 Parse(string s, NumberStyles style, IFormatProvider? provider) + { + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); + } + + /// + /// Tries to parse a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse([NotNullWhen(true)] string? s, out Decimal32 result) => TryParse(s, NumberStyles.Number, provider: null, out result); + + /// + /// Tries to parse a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse(ReadOnlySpan s, out Decimal32 result) => TryParse(s, NumberStyles.Number, provider: null, out result); + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) => TryParse(s, NumberStyles.Number, provider, out result); + + /// + /// Tries to parse a from a with the given . + /// + /// The input to be parsed. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) => TryParse(s, NumberStyles.Number, provider, out result); + + /// + /// Tries to parse a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + /// + /// Tries to parse a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal32's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal32 result) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + + if (s == null) + { + result = default; + return false; + } + return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + /// + public int CompareTo(object? value) + { + if (value == null) + { + return 1; + } + + if (value is not Decimal32 i) + { + throw new ArgumentException(SR.Arg_MustBeDecimal32); + } + + return Number.CompareDecimalIeee754(_value, i._value); + } + + /// + public int CompareTo(Decimal32 other) + { + return Number.CompareDecimalIeee754(_value, other._value); + } + + /// + public bool Equals(Decimal32 other) + { + return Number.CompareDecimalIeee754(_value, other._value) == 0; + } + + /// + /// Returns a value that indicates whether this instance is equal to a specified . + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Decimal32 && Equals((Decimal32)obj); + } + + /// + /// Serves as the default hash function. + /// + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + /// + /// Returns a string representation of the current value. + /// + public override string ToString() + { + return Number.FormatDecimalIeee754(this, null, NumberFormatInfo.CurrentInfo); + } + + /// + /// Returns a string representation of the current value using the specified . + /// + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) + { + return Number.FormatDecimalIeee754(this, format, NumberFormatInfo.CurrentInfo); + } + + /// + /// Returns a string representation of the current value with the specified . + /// + public string ToString(IFormatProvider? provider) + { + return Number.FormatDecimalIeee754(this, null, NumberFormatInfo.GetInstance(provider)); + } + + /// + /// Returns a string representation of the current value using the specified and . + /// + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) + { + return Number.FormatDecimalIeee754(this, format, NumberFormatInfo.GetInstance(provider)); + } + + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; + + static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal32NumberBufferLength; + + static unsafe byte* IDecimalIeee754ParseAndFormatInfo.ToDecChars(byte* p, uint significand) + { + return Number.UInt32ToDecChars(p, significand, 0); + } + + Number.DecimalIeee754 IDecimalIeee754ParseAndFormatInfo.Unpack() + { + return Number.UnpackDecimalIeee754(_value); + } + + static unsafe uint IDecimalIeee754ParseAndFormatInfo.NumberToSignificand(ref Number.NumberBuffer number, int digits) + { + return Number.DigitsToUInt32(number.DigitsPtr, digits); + } + + static Decimal32 IDecimalIeee754ParseAndFormatInfo.Construct(uint value) => new Decimal32(value); + static int IDecimalIeee754ParseAndFormatInfo.ConvertToExponent(uint value) => (int)value; + static uint IDecimalIeee754ParseAndFormatInfo.Power10(int exponent) => UInt32Powers10[exponent]; + + static int IDecimalIeee754ParseAndFormatInfo.MaxScale => 97; + static int IDecimalIeee754ParseAndFormatInfo.MinScale => -100; + + static int IDecimalIeee754ParseAndFormatInfo.MaxExponent => MaxExponent; + + static int IDecimalIeee754ParseAndFormatInfo.MinExponent => MinExponent; + + static uint IDecimalIeee754ParseAndFormatInfo.PositiveInfinity => PositiveInfinityValue; + + static uint IDecimalIeee754ParseAndFormatInfo.NegativeInfinity => NegativeInfinityValue; + + static uint IDecimalIeee754ParseAndFormatInfo.Zero => ZeroValue; + + static uint IDecimalIeee754ParseAndFormatInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsEncoding => 32; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsExponent => NumberBitsExponent; + + static uint IDecimalIeee754ParseAndFormatInfo.SignMask => SignMask; + + static uint IDecimalIeee754ParseAndFormatInfo.G0G1Mask => G0G1Mask; + + static int IDecimalIeee754ParseAndFormatInfo.ExponentBias => ExponentBias; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsSignificand => 20; + + static uint IDecimalIeee754ParseAndFormatInfo.G0ToGwPlus1ExponentMask => 0x7F80_0000; + + static uint IDecimalIeee754ParseAndFormatInfo.G2ToGwPlus3ExponentMask => 0x1FE0_0000; + + static uint IDecimalIeee754ParseAndFormatInfo.GwPlus2ToGwPlus4SignificandMask => 0x007F_FFFF; + + static uint IDecimalIeee754ParseAndFormatInfo.GwPlus4SignificandMask => 0x001F_FFFF; + + static uint IDecimalIeee754ParseAndFormatInfo.MaxSignificand => MaxSignificand; + + static unsafe void IDecimalIeee754ParseAndFormatInfo.ToNumber(uint significand, ref Number.NumberBuffer number) + { + Number.UInt32ToNumber(significand, ref number); + } + + static int IDecimalIeee754ParseAndFormatInfo.SignificandBufferLength => Number.UInt32NumberBufferLength; + } +} diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs new file mode 100644 index 00000000000000..bb3c91f1a5d3a1 --- /dev/null +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -0,0 +1,319 @@ +// 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 + { + internal readonly ulong _value; + + private const int MaxExponent = 369; + private const int MinExponent = -398; + private const int Precision = 16; + private const int ExponentBias = 398; + private const int NumberBitsExponent = 10; + private const ulong PositiveInfinityValue = 0x7800_0000_0000_0000; + private const ulong NegativeInfinityValue = 0xF800_0000_0000_0000; + private const ulong ZeroValue = 0x0000_0000_0000_0000; + private const ulong G0G1Mask = 0x6000_0000_0000_0000; + private const ulong SignMask = 0x8000_0000_0000_0000; + private const ulong MostSignificantBitOfSignificandMask = 0x0020_0000_0000_0000; + private const ulong MaxSignificand = 9_999_999_999_999_999; + + private static ReadOnlySpan UInt64Powers10 => + [ + 1, + 10, + 100, + 1000, + 10000, + 100000, + 1000000, + 10000000, + 100000000, + 1000000000, + 10000000000, + 100000000000, + 1000000000000, + 10000000000000, + 100000000000000, + 1000000000000000, + ]; + + public Decimal64(long significand, int exponent) + { + _value = Number.ConstructorToDecimalIeee754Bits(significand < 0, (ulong)Math.Abs(significand), exponent); + } + + internal Decimal64(ulong value) + { + _value = value; + } + + /// + /// Parses a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned. + public static Decimal64 Parse(string s) => Parse(s, NumberStyles.Number, provider: null); + + /// + /// Parses a from a in the given . + /// + /// The input to be parsed. + /// The used to parse the input. + /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned. + public static Decimal64 Parse(string s, NumberStyles style) => Parse(s, style, provider: null); + + /// + public static Decimal64 Parse(ReadOnlySpan s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); + + /// + /// Parses a from a and . + /// + /// The input to be parsed. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned. + public static Decimal64 Parse(string s, IFormatProvider? provider) => Parse(s, NumberStyles.Number, provider); + + /// + /// Parses a from a and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned. + public static Decimal64 Parse(ReadOnlySpan s, NumberStyles style = NumberStyles.Number, IFormatProvider? provider = null) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.ParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider)); + } + + /// + /// Parses a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string. If the input exceeds Decimal64's range, a or is returned. + public static Decimal64 Parse(string s, NumberStyles style, IFormatProvider? provider) + { + if (s is null) + { + ThrowHelper.ThrowArgumentNullException(ExceptionArgument.s); + } + return Parse(s.AsSpan(), style, provider); + } + + /// + /// Tries to parse a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse([NotNullWhen(true)] string? s, out Decimal64 result) => TryParse(s, NumberStyles.Number, provider: null, out result); + + /// + /// Tries to parse a from a in the default parse style. + /// + /// The input to be parsed. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse(ReadOnlySpan s, out Decimal64 result) => TryParse(s, NumberStyles.Number, provider: null, out result); + + /// + public static bool TryParse(ReadOnlySpan s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) => TryParse(s, NumberStyles.Number, provider, out result); + + /// + /// Tries to parse a from a with the given . + /// + /// The input to be parsed. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse([NotNullWhen(true)] string? s, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) => TryParse(s, NumberStyles.Number, provider, out result); + + /// + /// Tries to parse a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse(ReadOnlySpan s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + return Number.TryParseDecimalIeee754(s, style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + /// + /// Tries to parse a from a with the given and . + /// + /// The input to be parsed. + /// The used to parse the input. + /// A format provider. + /// The equivalent value representing the input string if the parse was successful. If the input exceeds Decimal64's range, a or is returned. If the parse was unsuccessful, a default value is returned. + /// if the parse was successful, otherwise. + public static bool TryParse([NotNullWhen(true)] string? s, NumberStyles style, IFormatProvider? provider, [MaybeNullWhen(false)] out Decimal64 result) + { + NumberFormatInfo.ValidateParseStyleFloatingPoint(style); + + if (s == null) + { + result = default; + return false; + } + return Number.TryParseDecimalIeee754(s.AsSpan(), style, NumberFormatInfo.GetInstance(provider), out result) == Number.ParsingStatus.OK; + } + + /// + public int CompareTo(object? value) + { + if (value is not Decimal64 other) + { + return (value is null) ? 1 : throw new ArgumentException(SR.Arg_MustBeDecimal64); + } + return CompareTo(other); + } + + /// + public int CompareTo(Decimal64 other) + { + return Number.CompareDecimalIeee754(_value, other._value); + } + + /// + public bool Equals(Decimal64 other) + { + return Number.CompareDecimalIeee754(_value, other._value) == 0; + } + + /// + /// Returns a value that indicates whether this instance is equal to a specified . + /// + public override bool Equals([NotNullWhen(true)] object? obj) + { + return obj is Decimal64 && Equals((Decimal64)obj); + } + + /// + /// Serves as the default hash function. + /// + public override int GetHashCode() + { + return _value.GetHashCode(); + } + + /// + /// Returns a string representation of the current value. + /// + public override string ToString() + { + return Number.FormatDecimalIeee754(this, null, NumberFormatInfo.CurrentInfo); + } + + /// + /// Returns a string representation of the current value using the specified . + /// + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format) + { + return Number.FormatDecimalIeee754(this, format, NumberFormatInfo.CurrentInfo); + } + + /// + /// Returns a string representation of the current value with the specified . + /// + public string ToString(IFormatProvider? provider) + { + return Number.FormatDecimalIeee754(this, null, NumberFormatInfo.GetInstance(provider)); + } + + /// + /// Returns a string representation of the current value using the specified and . + /// + public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] string? format, IFormatProvider? provider) + { + return Number.FormatDecimalIeee754(this, format, NumberFormatInfo.GetInstance(provider)); + } + + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; + + static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal64NumberBufferLength; + + static unsafe byte* IDecimalIeee754ParseAndFormatInfo.ToDecChars(byte* p, ulong significand) + { + return Number.UInt64ToDecChars(p, significand, 0); + } + + Number.DecimalIeee754 IDecimalIeee754ParseAndFormatInfo.Unpack() + { + return Number.UnpackDecimalIeee754(_value); + } + + static unsafe ulong IDecimalIeee754ParseAndFormatInfo.NumberToSignificand(ref Number.NumberBuffer number, int digits) + { + return Number.DigitsToUInt64(number.DigitsPtr, digits); + } + + static Decimal64 IDecimalIeee754ParseAndFormatInfo.Construct(ulong value) => new Decimal64(value); + + static int IDecimalIeee754ParseAndFormatInfo.ConvertToExponent(ulong value) => (int)value; + + static ulong IDecimalIeee754ParseAndFormatInfo.Power10(int exponent) => UInt64Powers10[exponent]; + + static int IDecimalIeee754ParseAndFormatInfo.MaxScale => 385; + + static int IDecimalIeee754ParseAndFormatInfo.MinScale => -397; + + static int IDecimalIeee754ParseAndFormatInfo.MaxExponent => MaxExponent; + + static int IDecimalIeee754ParseAndFormatInfo.MinExponent => MinExponent; + + static ulong IDecimalIeee754ParseAndFormatInfo.PositiveInfinity => PositiveInfinityValue; + + static ulong IDecimalIeee754ParseAndFormatInfo.NegativeInfinity => NegativeInfinityValue; + + static ulong IDecimalIeee754ParseAndFormatInfo.Zero => ZeroValue; + + static ulong IDecimalIeee754ParseAndFormatInfo.MostSignificantBitOfSignificandMask => MostSignificantBitOfSignificandMask; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsEncoding => 64; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsExponent => NumberBitsExponent; + + static ulong IDecimalIeee754ParseAndFormatInfo.SignMask => SignMask; + + static ulong IDecimalIeee754ParseAndFormatInfo.G0G1Mask => G0G1Mask; + + static int IDecimalIeee754ParseAndFormatInfo.ExponentBias => ExponentBias; + + static int IDecimalIeee754ParseAndFormatInfo.NumberBitsSignificand => 50; + + static ulong IDecimalIeee754ParseAndFormatInfo.G0ToGwPlus1ExponentMask => 0x7FE0_0000_0000_0000; + + static ulong IDecimalIeee754ParseAndFormatInfo.G2ToGwPlus3ExponentMask => 0x1FF8_0000_0000_0000; + + static ulong IDecimalIeee754ParseAndFormatInfo.GwPlus2ToGwPlus4SignificandMask => 0x001F_FFFF_FFFF_FFFF; + + static ulong IDecimalIeee754ParseAndFormatInfo.GwPlus4SignificandMask => 0x0007_FFFF_FFFF_FFFF; + + static ulong IDecimalIeee754ParseAndFormatInfo.MaxSignificand => MaxSignificand; + + static unsafe void IDecimalIeee754ParseAndFormatInfo.ToNumber(ulong significand, ref Number.NumberBuffer number) + { + Number.UInt64ToNumber(significand, ref number); + } + + static int IDecimalIeee754ParseAndFormatInfo.SignificandBufferLength => Number.UInt64NumberBufferLength; + } +} diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index e1d6c46cbfe499..be06b4141aaaf3 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11176,6 +11176,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 30556ca473ccfc..e0fd911496f053 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 @@ -80,6 +80,9 @@ + + + diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs new file mode 100644 index 00000000000000..e6bdd4d380f13b --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -0,0 +1,356 @@ +// 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[] { "0." + new string('0', 6174) + "12345", defaultStyle, invariantFormat, new Decimal128(12, -6176) }; + yield return new object[] { "-0." + new string('0', 6174) + "12345", defaultStyle, invariantFormat, new Decimal128(-12, -6176) }; + yield return new object[] { "0." + new string('0', 6174) + "12562", defaultStyle, invariantFormat, new Decimal128(13, -6176) }; + yield return new object[] { "-0." + new string('0', 6174) + "12562", defaultStyle, invariantFormat, new Decimal128(-13, -6176) }; + + yield return new object[] { emptyFormat.NumberDecimalSeparator + "234", defaultStyle, null, new Decimal128(234, -3) }; + yield return new object[] { "234" + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal128(234, 0) }; + yield return new object[] { "7" + new string('0', 6144) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal128(7, 6144) }; + yield return new object[] { "07" + new string('0', 6144) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal128(7, 6144) }; + + yield return new object[] { (123.1).ToString(), NumberStyles.AllowDecimalPoint, null, new Decimal128(1231, -1) }; + yield return new object[] { 1000.ToString("N0"), NumberStyles.AllowThousands, null, new Decimal128(1000, 0) }; + + yield return new object[] { "123", NumberStyles.Any, emptyFormat, new Decimal128(123, 0) }; + yield return new object[] { (123.567).ToString(), NumberStyles.Any, emptyFormat, new Decimal128(123567, -3) }; + yield return new object[] { "123", NumberStyles.Float, emptyFormat, new Decimal128(123, 0) }; + yield return new object[] { "$1000", NumberStyles.Currency, customFormat1, new Decimal128(1, 3) }; + yield return new object[] { "123.123", NumberStyles.Float, customFormat2, new Decimal128(123123, -3) }; + yield return new object[] { "(123)", NumberStyles.AllowParentheses, customFormat2, new Decimal128(-123, 0) }; + } + + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse(string value, NumberStyles style, IFormatProvider provider, Decimal128 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal128 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal128.TryParse(value, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal128.Parse(value)); + } + + Assert.Equal(expected, Decimal128.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal128.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal128.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal128.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal128.Parse(value, style)); + Assert.Equal(expected, Decimal128.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_Invalid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Number; + + var customFormat = new NumberFormatInfo(); + customFormat.CurrencySymbol = "$"; + customFormat.NumberDecimalSeparator = "."; + + yield return new object[] { null, defaultStyle, null, typeof(ArgumentNullException) }; + yield return new object[] { "", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { " ", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { "Garbage", defaultStyle, null, typeof(FormatException) }; + + yield return new object[] { "ab", defaultStyle, null, typeof(FormatException) }; // Hex value + yield return new object[] { "(123)", defaultStyle, null, typeof(FormatException) }; // Parentheses + yield return new object[] { 100.ToString("C0"), defaultStyle, null, typeof(FormatException) }; // Currency + + yield return new object[] { (123.456m).ToString(), NumberStyles.Integer, null, typeof(FormatException) }; // Decimal + yield return new object[] { " " + (123.456m).ToString(), NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { (123.456m).ToString() + " ", NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { "1E23", NumberStyles.None, null, typeof(FormatException) }; // Exponent + + yield return new object[] { "ab", NumberStyles.None, null, typeof(FormatException) }; // Hex value + yield return new object[] { " 123 ", NumberStyles.None, null, typeof(FormatException) }; // Trailing and leading whitespace + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal128 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite)) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.False(Decimal128.TryParse(value, out result)); + Assert.Equal(default(Decimal128), result); + + Assert.Throws(exceptionType, () => Decimal128.Parse(value)); + } + + Assert.Throws(exceptionType, () => Decimal128.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal128.TryParse(value, style, provider, out result)); + Assert.Equal(default(Decimal128), result); + + Assert.Throws(exceptionType, () => Decimal128.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal128.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(default(Decimal128), result); + + Assert.Throws(exceptionType, () => Decimal128.Parse(value, style)); + Assert.Throws(exceptionType, () => Decimal128.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_ValidWithOffsetCount_TestData() + { + foreach (object[] inputs in Parse_Valid_TestData()) + { + yield return new object[] { inputs[0], 0, ((string)inputs[0]).Length, inputs[1], inputs[2], inputs[3] }; + } + + yield return new object[] { "-123", 1, 3, NumberStyles.Number, null, new Decimal128(123, 0) }; + yield return new object[] { "-123", 0, 3, NumberStyles.Number, null, new Decimal128(-12, 0) }; + yield return new object[] { 1000.ToString("N0"), 0, 4, NumberStyles.AllowThousands, null, new Decimal128(100, 0) }; + yield return new object[] { 1000.ToString("N0"), 2, 3, NumberStyles.AllowThousands, null, new Decimal128(0, 0) }; + yield return new object[] { "(123)", 1, 3, NumberStyles.AllowParentheses, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal128(123, 0) }; + yield return new object[] { "1234567890123456789012345.678456", 1, 4, NumberStyles.Number, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal128(2345, 0) }; + } + + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, Decimal128 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal128 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal128.TryParse(value.AsSpan(offset, count), out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal128.Parse(value.AsSpan(offset, count))); + } + + Assert.Equal(expected, Decimal128.Parse(value.AsSpan(offset, count), provider: provider)); + } + + Assert.Equal(expected, Decimal128.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(Decimal128.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + Assert.Throws(exceptionType, () => Decimal128.Parse(value.AsSpan(), style, provider)); + + Assert.False(Decimal128.TryParse(value.AsSpan(), style, provider, out Decimal128 result)); + Assert.Equal(default, result); + } + } + + [Fact] + public static void Midpoint_Rounding() + { + var number = new Decimal128(Int128.Parse("12345688888888881234568888888888885"), 0); + Assert.Equal(new Decimal128(Int128.Parse("1234568888888888123456888888888888"), 1), number); + } + + [Fact] + public static void Parse_Rounding() + { + Assert.Equal(Decimal128.Parse(new string('9', 35)), new Decimal128(1, 35)); + Assert.Equal(Decimal128.Parse(new string('9', 35) + new string('0', 6109)), new Decimal128(Int128.Parse("1" + new string('0', 33)), 6111)); + Assert.Equal(Decimal128.Parse(new string('9', 34) + '5' + new string('0', 6109)), new Decimal128(Int128.Parse("1" + new string('0', 33)), 6111)); + Assert.Equal(Decimal128.Parse(new string('9', 34) + '4' + new string('0', 6109)), new Decimal128(Int128.Parse(new string('9', 34)), 6110)); + } + + [Fact] + public static void Rounding() + { + var number = new Decimal128(Int128.Parse("12345677777777771234567777777777778"), 0); + Assert.Equal(new Decimal128(Int128.Parse("1234567777777777123456777777777778"), 1), number); + + number = new Decimal128(Int128.Parse("12345677777777771234567777777777771"), 0); + Assert.Equal(new Decimal128(Int128.Parse("1234567777777777123456777777777777"), 1), number); + + number = new Decimal128(Int128.Parse("12345677777777771234567777777777771"), -6177); + Assert.Equal(new Decimal128(Int128.Parse("1234567777777777123456777777777777"), -6176), number); + } + + [Fact] + public static void 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()); + } + + [Fact] + public static void CompareToZero() + { + var zero = new Decimal128(0, 1); + Assert.Equal(zero, new Decimal128(0, 20)); + Assert.Equal(zero, new Decimal128(1, -6177)); + Assert.Equal(zero, new Decimal128(234, -10000)); + Assert.Equal(zero, new Decimal128(-1, -6177)); + Assert.Equal(zero, new Decimal128(-234, -10000)); + } + public static IEnumerable ToString_TestData() + { + foreach (NumberFormatInfo defaultFormat in new[] { null, NumberFormatInfo.CurrentInfo }) + { + yield return new object[] { new Decimal128(3, 6144), "G", defaultFormat, "3" + new string('0', 6144) }; + yield return new object[] { new Decimal128(-3, 6144), "G", defaultFormat, "-3" + new string('0', 6144) }; + yield return new object[] { new Decimal128(-4567, 0), "G", defaultFormat, "-4567" }; + yield return new object[] { new Decimal128(-4567891, -3), "G", defaultFormat, "-4567.891" }; + yield return new object[] { new Decimal128(0, 0), "G", defaultFormat, "0" }; + yield return new object[] { new Decimal128(4567, 0), "G", defaultFormat, "4567" }; + yield return new object[] { new Decimal128(4567891, -3), "G", defaultFormat, "4567.891" }; + + yield return new object[] { new Decimal128(2468, 0), "N", defaultFormat, "2,468.00" }; + + yield return new object[] { new Decimal128(2467, 0), "[#-##-#]", defaultFormat, "[2-46-7]" }; + + } + } + + [Fact] + public static void Test_ToString() + { + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + foreach (object[] testdata in ToString_TestData()) + { + ToString((Decimal128)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); + } + } + } + + private static void ToString(Decimal128 f, string format, IFormatProvider provider, string expected) + { + bool isDefaultProvider = provider == null; + if (string.IsNullOrEmpty(format) || format.ToUpperInvariant() == "G") + { + if (isDefaultProvider) + { + Assert.Equal(expected, f.ToString()); + Assert.Equal(expected, f.ToString((IFormatProvider)null)); + } + Assert.Equal(expected, f.ToString(provider)); + } + if (isDefaultProvider) + { + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant())); // If format is upper case, then exponents are printed in upper case + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant())); // If format is lower case, then exponents are printed in lower case + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), null)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), null)); + } + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), provider)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), provider)); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs new file mode 100644 index 00000000000000..f9ddc24c3b5d65 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -0,0 +1,357 @@ +// 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; +using static System.Runtime.InteropServices.JavaScript.JSType; + +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[] { "0." + new string('0', 99) + "12345", defaultStyle, invariantFormat, new Decimal32(12, -101) }; + yield return new object[] { "-0." + new string('0', 99) + "12345", defaultStyle, invariantFormat, new Decimal32(-12, -101) }; + yield return new object[] { "0." + new string('0', 99) + "12562", defaultStyle, invariantFormat, new Decimal32(13, -101) }; + yield return new object[] { "-0." + new string('0', 99) + "12562", defaultStyle, invariantFormat, new Decimal32(-13, -101) }; + + yield return new object[] { emptyFormat.NumberDecimalSeparator + "234", defaultStyle, null, new Decimal32(234, -3) }; + yield return new object[] { "234" + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal32(234, 0) }; + yield return new object[] { "7" + new string('0', 96) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal32(7, 96) }; + yield return new object[] { "07" + new string('0', 96) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal32(7, 96) }; + + yield return new object[] { (123.1).ToString(), NumberStyles.AllowDecimalPoint, null, new Decimal32(1231, -1) }; + yield return new object[] { 1000.ToString("N0"), NumberStyles.AllowThousands, null, new Decimal32(1000, 0) }; + + yield return new object[] { "123", NumberStyles.Any, emptyFormat, new Decimal32(123, 0) }; + yield return new object[] { (123.567).ToString(), NumberStyles.Any, emptyFormat, new Decimal32(123567, -3) }; + yield return new object[] { "123", NumberStyles.Float, emptyFormat, new Decimal32(123, 0) }; + yield return new object[] { "$1000", NumberStyles.Currency, customFormat1, new Decimal32(1, 3) }; + yield return new object[] { "123.123", NumberStyles.Float, customFormat2, new Decimal32(123123, -3) }; + yield return new object[] { "(123)", NumberStyles.AllowParentheses, customFormat2, new Decimal32(-123, 0) }; + } + + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse(string value, NumberStyles style, IFormatProvider provider, Decimal32 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal32 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal32.TryParse(value, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal32.Parse(value)); + } + + Assert.Equal(expected, Decimal32.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal32.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal32.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal32.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal32.Parse(value, style)); + Assert.Equal(expected, Decimal32.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_Invalid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Number; + + var customFormat = new NumberFormatInfo(); + customFormat.CurrencySymbol = "$"; + customFormat.NumberDecimalSeparator = "."; + + yield return new object[] { null, defaultStyle, null, typeof(ArgumentNullException) }; + yield return new object[] { "", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { " ", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { "Garbage", defaultStyle, null, typeof(FormatException) }; + + yield return new object[] { "ab", defaultStyle, null, typeof(FormatException) }; // Hex value + yield return new object[] { "(123)", defaultStyle, null, typeof(FormatException) }; // Parentheses + yield return new object[] { 100.ToString("C0"), defaultStyle, null, typeof(FormatException) }; // Currency + + yield return new object[] { (123.456m).ToString(), NumberStyles.Integer, null, typeof(FormatException) }; // Decimal + yield return new object[] { " " + (123.456m).ToString(), NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { (123.456m).ToString() + " ", NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { "1E23", NumberStyles.None, null, typeof(FormatException) }; // Exponent + + yield return new object[] { "ab", NumberStyles.None, null, typeof(FormatException) }; // Hex value + yield return new object[] { " 123 ", NumberStyles.None, null, typeof(FormatException) }; // Trailing and leading whitespace + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal32 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite)) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.False(Decimal32.TryParse(value, out result)); + Assert.Equal(default(Decimal32), result); + + Assert.Throws(exceptionType, () => Decimal32.Parse(value)); + } + + Assert.Throws(exceptionType, () => Decimal32.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal32.TryParse(value, style, provider, out result)); + Assert.Equal(default(Decimal32), result); + + Assert.Throws(exceptionType, () => Decimal32.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal32.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(default(Decimal32), result); + + Assert.Throws(exceptionType, () => Decimal32.Parse(value, style)); + Assert.Throws(exceptionType, () => Decimal32.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_ValidWithOffsetCount_TestData() + { + foreach (object[] inputs in Parse_Valid_TestData()) + { + yield return new object[] { inputs[0], 0, ((string)inputs[0]).Length, inputs[1], inputs[2], inputs[3] }; + } + + yield return new object[] { "-123", 1, 3, NumberStyles.Number, null, new Decimal32(123, 0) }; + yield return new object[] { "-123", 0, 3, NumberStyles.Number, null, new Decimal32(-12, 0) }; + yield return new object[] { 1000.ToString("N0"), 0, 4, NumberStyles.AllowThousands, null, new Decimal32(100, 0) }; + yield return new object[] { 1000.ToString("N0"), 2, 3, NumberStyles.AllowThousands, null, new Decimal32(0, 0) }; + yield return new object[] { "(123)", 1, 3, NumberStyles.AllowParentheses, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal32(123, 0) }; + yield return new object[] { "1234567890123456789012345.678456", 1, 4, NumberStyles.Number, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal32(2345, 0) }; + } + + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, Decimal32 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal32 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal32.TryParse(value.AsSpan(offset, count), out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal32.Parse(value.AsSpan(offset, count))); + } + + Assert.Equal(expected, Decimal32.Parse(value.AsSpan(offset, count), provider: provider)); + } + + Assert.Equal(expected, Decimal32.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(Decimal32.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + Assert.Throws(exceptionType, () => Decimal32.Parse(value.AsSpan(), style, provider)); + + Assert.False(Decimal32.TryParse(value.AsSpan(), style, provider, out Decimal32 result)); + Assert.Equal(default, result); + } + } + + [Fact] + public static void Parse_Rounding() + { + Assert.Equal(Decimal32.Parse(new string('9', 8)), new Decimal32(1, 8)); + Assert.Equal(Decimal32.Parse(new string('9', 8) + new string('0', 88)), new Decimal32(1_000_000, 90)); + Assert.Equal(Decimal32.Parse(new string('9', 7) + '5' + new string('0', 88)), new Decimal32(1_000_000, 90)); + Assert.Equal(Decimal32.Parse(new string('9', 7) + '4' + new string('0', 88)), new Decimal32(9_999_999, 89)); + } + + [Fact] + public static void Midpoint_Rounding() + { + var number = new Decimal32(12345685, 0); + Assert.Equal(new Decimal32(1234568, 1), number); + } + + [Fact] + public static void Rounding() + { + var number = new Decimal32(12345678, 0); + Assert.Equal(new Decimal32(1234568, 1), number); + + number = new Decimal32(12345671, 0); + Assert.Equal(new Decimal32(1234567, 1), number); + + number = new Decimal32(12345650, -103); + Assert.Equal(new Decimal32(123456, -101), number); + } + + [Fact] + public static void 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()); + } + + [Fact] + public static void CompareToZero() + { + var zero = new Decimal32(0, 1); + Assert.Equal(zero, new Decimal32(0, 20)); + Assert.Equal(zero, new Decimal32(1, -102)); + Assert.Equal(zero, new Decimal32(234, -1000)); + Assert.Equal(zero, new Decimal32(-1, -102)); + Assert.Equal(zero, new Decimal32(-234, -1000)); + } + + public static IEnumerable ToString_TestData() + { + foreach (NumberFormatInfo defaultFormat in new[] { null, NumberFormatInfo.CurrentInfo }) + { + yield return new object[] { new Decimal32(3, 96), "G", defaultFormat, "3" + new string('0', 96) }; + yield return new object[] { new Decimal32(-3, 96), "G", defaultFormat, "-3" + new string('0', 96) }; + yield return new object[] { new Decimal32(-4567, 0), "G", defaultFormat, "-4567" }; + yield return new object[] { new Decimal32(-4567891, -3), "G", defaultFormat, "-4567.891" }; + yield return new object[] { new Decimal32(0, 0), "G", defaultFormat, "0" }; + yield return new object[] { new Decimal32(4567, 0), "G", defaultFormat, "4567" }; + yield return new object[] { new Decimal32(4567891, -3), "G", defaultFormat, "4567.891" }; + + yield return new object[] { new Decimal32(2468, 0), "N", defaultFormat, "2,468.00" }; + + yield return new object[] { new Decimal32(2467, 0), "[#-##-#]", defaultFormat, "[2-46-7]" }; + + } + } + + [Fact] + public static void Test_ToString() + { + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + foreach (object[] testdata in ToString_TestData()) + { + ToString((Decimal32)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); + } + } + } + + private static void ToString(Decimal32 f, string format, IFormatProvider provider, string expected) + { + bool isDefaultProvider = provider == null; + if (string.IsNullOrEmpty(format) || format.ToUpperInvariant() == "G") + { + if (isDefaultProvider) + { + Assert.Equal(expected, f.ToString()); + Assert.Equal(expected, f.ToString((IFormatProvider)null)); + } + Assert.Equal(expected, f.ToString(provider)); + } + if (isDefaultProvider) + { + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant())); // If format is upper case, then exponents are printed in upper case + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant())); // If format is lower case, then exponents are printed in lower case + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), null)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), null)); + } + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), provider)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), provider)); + } + } +} diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs new file mode 100644 index 00000000000000..eb131192d34879 --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -0,0 +1,358 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +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[] { new string('9', 17), defaultStyle, invariantFormat, new Decimal64(1, 17) }; + + yield return new object[] { "0." + new string('0', 398) + "1", defaultStyle, invariantFormat, new Decimal64(0, 0) }; + yield return new object[] { "-0." + new string('0', 398) + "1", defaultStyle, invariantFormat, new Decimal64(0, 0) }; + yield return new object[] { "0." + new string('0', 397) + "1", defaultStyle, invariantFormat, new Decimal64(1, -398) }; + yield return new object[] { "-0." + new string('0', 397) + "1", defaultStyle, invariantFormat, new Decimal64(-1, -398) }; + + yield return new object[] { "0." + new string('0', 396) + "12345", defaultStyle, invariantFormat, new Decimal64(12, -398) }; + yield return new object[] { "-0." + new string('0', 396) + "12345", defaultStyle, invariantFormat, new Decimal64(-12, -398) }; + yield return new object[] { "0." + new string('0', 396) + "12562", defaultStyle, invariantFormat, new Decimal64(13, -398) }; + yield return new object[] { "-0." + new string('0', 396) + "12562", defaultStyle, invariantFormat, new Decimal64(-13, -398) }; + + yield return new object[] { emptyFormat.NumberDecimalSeparator + "234", defaultStyle, null, new Decimal64(234, -3) }; + yield return new object[] { "234" + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal64(234, 0) }; + yield return new object[] { "7" + new string('0', 384) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal64(7, 384) }; + yield return new object[] { "07" + new string('0', 384) + emptyFormat.NumberDecimalSeparator, defaultStyle, null, new Decimal64(7, 384) }; + + yield return new object[] { (123.1).ToString(), NumberStyles.AllowDecimalPoint, null, new Decimal64(1231, -1) }; + yield return new object[] { 1000.ToString("N0"), NumberStyles.AllowThousands, null, new Decimal64(1000, 0) }; + + yield return new object[] { "123", NumberStyles.Any, emptyFormat, new Decimal64(123, 0) }; + yield return new object[] { (123.567).ToString(), NumberStyles.Any, emptyFormat, new Decimal64(123567, -3) }; + yield return new object[] { "123", NumberStyles.Float, emptyFormat, new Decimal64(123, 0) }; + yield return new object[] { "$1000", NumberStyles.Currency, customFormat1, new Decimal64(1, 3) }; + yield return new object[] { "123.123", NumberStyles.Float, customFormat2, new Decimal64(123123, -3) }; + yield return new object[] { "(123)", NumberStyles.AllowParentheses, customFormat2, new Decimal64(-123, 0) }; + } + + + [Theory] + [MemberData(nameof(Parse_Valid_TestData))] + public static void Parse(string value, NumberStyles style, IFormatProvider provider, Decimal64 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal64 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal64.TryParse(value, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal64.Parse(value)); + } + + Assert.Equal(expected, Decimal64.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal64.TryParse(value, style, provider, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal64.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.True(Decimal64.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal64.Parse(value, style)); + Assert.Equal(expected, Decimal64.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_Invalid_TestData() + { + NumberStyles defaultStyle = NumberStyles.Number; + + var customFormat = new NumberFormatInfo(); + customFormat.CurrencySymbol = "$"; + customFormat.NumberDecimalSeparator = "."; + + yield return new object[] { null, defaultStyle, null, typeof(ArgumentNullException) }; + yield return new object[] { "", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { " ", defaultStyle, null, typeof(FormatException) }; + yield return new object[] { "Garbage", defaultStyle, null, typeof(FormatException) }; + + yield return new object[] { "ab", defaultStyle, null, typeof(FormatException) }; // Hex value + yield return new object[] { "(123)", defaultStyle, null, typeof(FormatException) }; // Parentheses + yield return new object[] { 100.ToString("C0"), defaultStyle, null, typeof(FormatException) }; // Currency + + yield return new object[] { (123.456m).ToString(), NumberStyles.Integer, null, typeof(FormatException) }; // Decimal + yield return new object[] { " " + (123.456m).ToString(), NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { (123.456m).ToString() + " ", NumberStyles.None, null, typeof(FormatException) }; // Leading space + yield return new object[] { "1E23", NumberStyles.None, null, typeof(FormatException) }; // Exponent + + yield return new object[] { "ab", NumberStyles.None, null, typeof(FormatException) }; // Hex value + yield return new object[] { " 123 ", NumberStyles.None, null, typeof(FormatException) }; // Trailing and leading whitespace + } + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal64 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None && (style & NumberStyles.AllowLeadingWhite) == (style & NumberStyles.AllowTrailingWhite)) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.False(Decimal64.TryParse(value, out result)); + Assert.Equal(default(Decimal64), result); + + Assert.Throws(exceptionType, () => Decimal64.Parse(value)); + } + + Assert.Throws(exceptionType, () => Decimal64.Parse(value, provider)); + } + + // Use Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal64.TryParse(value, style, provider, out result)); + Assert.Equal(default(Decimal64), result); + + Assert.Throws(exceptionType, () => Decimal64.Parse(value, style, provider)); + + if (isDefaultProvider) + { + // Use Parse(string, NumberStyles) or Parse(string, NumberStyles, IFormatProvider) + Assert.False(Decimal64.TryParse(value, style, NumberFormatInfo.CurrentInfo, out result)); + Assert.Equal(default(Decimal64), result); + + Assert.Throws(exceptionType, () => Decimal64.Parse(value, style)); + Assert.Throws(exceptionType, () => Decimal64.Parse(value, style, NumberFormatInfo.CurrentInfo)); + } + } + + public static IEnumerable Parse_ValidWithOffsetCount_TestData() + { + foreach (object[] inputs in Parse_Valid_TestData()) + { + yield return new object[] { inputs[0], 0, ((string)inputs[0]).Length, inputs[1], inputs[2], inputs[3] }; + } + + yield return new object[] { "-123", 1, 3, NumberStyles.Number, null, new Decimal64(123, 0) }; + yield return new object[] { "-123", 0, 3, NumberStyles.Number, null, new Decimal64(-12, 0) }; + yield return new object[] { 1000.ToString("N0"), 0, 4, NumberStyles.AllowThousands, null, new Decimal64(100, 0) }; + yield return new object[] { 1000.ToString("N0"), 2, 3, NumberStyles.AllowThousands, null, new Decimal64(0, 0) }; + yield return new object[] { "(123)", 1, 3, NumberStyles.AllowParentheses, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal64(123, 0) }; + yield return new object[] { "1234567890123456789012345.678456", 1, 4, NumberStyles.Number, new NumberFormatInfo() { NumberDecimalSeparator = "." }, new Decimal64(2345, 0) }; + } + + [Theory] + [MemberData(nameof(Parse_ValidWithOffsetCount_TestData))] + public static void Parse_Span_Valid(string value, int offset, int count, NumberStyles style, IFormatProvider provider, Decimal64 expected) + { + bool isDefaultProvider = provider == null || provider == NumberFormatInfo.CurrentInfo; + Decimal64 result; + if ((style & ~NumberStyles.Number) == 0 && style != NumberStyles.None) + { + // Use Parse(string) or Parse(string, IFormatProvider) + if (isDefaultProvider) + { + Assert.True(Decimal64.TryParse(value.AsSpan(offset, count), out result)); + Assert.Equal(expected, result); + + Assert.Equal(expected, Decimal64.Parse(value.AsSpan(offset, count))); + } + + Assert.Equal(expected, Decimal64.Parse(value.AsSpan(offset, count), provider: provider)); + } + + Assert.Equal(expected, Decimal64.Parse(value.AsSpan(offset, count), style, provider)); + + Assert.True(Decimal64.TryParse(value.AsSpan(offset, count), style, provider, out result)); + Assert.Equal(expected, result); + } + + + [Theory] + [MemberData(nameof(Parse_Invalid_TestData))] + public static void Parse_Span_Invalid(string value, NumberStyles style, IFormatProvider provider, Type exceptionType) + { + if (value != null) + { + Assert.Throws(exceptionType, () => Decimal64.Parse(value.AsSpan(), style, provider)); + + Assert.False(Decimal64.TryParse(value.AsSpan(), style, provider, out Decimal64 result)); + Assert.Equal(default, result); + } + } + + [Fact] + public static void Midpoint_Rounding() + { + var number = new Decimal64(12345688888888885, 0); + Assert.Equal(new Decimal64(1234568888888888, 1), number); + } + + [Fact] + public static void Parse_Rounding() + { + Assert.Equal(Decimal64.Parse(new string('9', 17)), new Decimal64(1, 17)); + Assert.Equal(Decimal64.Parse(new string('9', 17) + new string('0', 367)), new Decimal64(1_000_000_000_000_000, 369)); + Assert.Equal(Decimal64.Parse(new string('9', 16) + '5' + new string('0', 367)), new Decimal64(1_000_000_000_000_000, 369)); + Assert.Equal(Decimal64.Parse(new string('9', 16) + '4' + new string('0', 367)), new Decimal64(9_999_999_999_999_999, 368)); + } + + [Fact] + public static void Rounding() + { + var number = new Decimal64(12345677777777778, 0); + Assert.Equal(new Decimal64(1234567777777778, 1), number); + + number = new Decimal64(12345677777777771, 0); + Assert.Equal(new Decimal64(1234567777777777, 1), number); + + number = new Decimal64(12345677777777771, -399); + Assert.Equal(new Decimal64(1234567777777777, -398), number); + } + + [Fact] + public static void 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()); + } + + [Fact] + public static void CompareToZero() + { + var zero = new Decimal64(0, 1); + Assert.Equal(zero, new Decimal64(0, 20)); + Assert.Equal(zero, new Decimal64(1, -399)); + Assert.Equal(zero, new Decimal64(234, -1000)); + Assert.Equal(zero, new Decimal64(-1, -399)); + Assert.Equal(zero, new Decimal64(-234, -1000)); + } + + public static IEnumerable ToString_TestData() + { + foreach (NumberFormatInfo defaultFormat in new[] { null, NumberFormatInfo.CurrentInfo }) + { + yield return new object[] { new Decimal64(3, 384), "G", defaultFormat, "3" + new string('0', 384) }; + yield return new object[] { new Decimal64(-3, 384), "G", defaultFormat, "-3" + new string('0', 384) }; + yield return new object[] { new Decimal64(-4567, 0), "G", defaultFormat, "-4567" }; + yield return new object[] { new Decimal64(-4567891, -3), "G", defaultFormat, "-4567.891" }; + yield return new object[] { new Decimal64(0, 0), "G", defaultFormat, "0" }; + yield return new object[] { new Decimal64(4567, 0), "G", defaultFormat, "4567" }; + yield return new object[] { new Decimal64(4567891, -3), "G", defaultFormat, "4567.891" }; + + yield return new object[] { new Decimal64(2468, 0), "N", defaultFormat, "2,468.00" }; + + yield return new object[] { new Decimal64(2467, 0), "[#-##-#]", defaultFormat, "[2-46-7]" }; + + } + } + + [Fact] + public static void Test_ToString() + { + using (new ThreadCultureChange(CultureInfo.InvariantCulture)) + { + foreach (object[] testdata in ToString_TestData()) + { + ToString((Decimal64)testdata[0], (string)testdata[1], (IFormatProvider)testdata[2], (string)testdata[3]); + } + } + } + + private static void ToString(Decimal64 f, string format, IFormatProvider provider, string expected) + { + bool isDefaultProvider = provider == null; + if (string.IsNullOrEmpty(format) || format.ToUpperInvariant() == "G") + { + if (isDefaultProvider) + { + Assert.Equal(expected, f.ToString()); + Assert.Equal(expected, f.ToString((IFormatProvider)null)); + } + Assert.Equal(expected, f.ToString(provider)); + } + if (isDefaultProvider) + { + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant())); // If format is upper case, then exponents are printed in upper case + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant())); // If format is lower case, then exponents are printed in lower case + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), null)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), null)); + } + Assert.Equal(expected.Replace('e', 'E'), f.ToString(format.ToUpperInvariant(), provider)); + Assert.Equal(expected.Replace('E', 'e'), f.ToString(format.ToLowerInvariant(), provider)); + } + } +}