From 316f66ca74b23f60736a38bbcd0e3f7d4647e69c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 9 Jul 2026 17:38:12 -0700 Subject: [PATCH 01/19] Implement unary plus/negation for Decimal32/64/128 Adds the IUnaryPlusOperators and IUnaryNegationOperators operators to Decimal32, Decimal64, and Decimal128 as part of #81376. Unary plus returns the value unchanged; unary negation toggles the sign bit, matching the IEEE 754 negate operation for finite values, infinities, and NaN. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/Decimal128.cs | 10 ++++ .../src/System/Numerics/Decimal32.cs | 10 ++++ .../src/System/Numerics/Decimal64.cs | 10 ++++ .../System.Runtime/ref/System.Runtime.cs | 9 ++++ .../System/Decimal128Tests.cs | 46 +++++++++++++++++ .../System/Decimal32Tests.cs | 50 +++++++++++++++++++ .../System/Decimal64Tests.cs | 50 +++++++++++++++++++ 7 files changed, 185 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index 66dbf5a88ea481..b2d7914fe80cd0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -252,6 +252,16 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return Number.FormatDecimalIeee754(new UInt128(_upper, _lower), format, NumberFormatInfo.GetInstance(provider)); } + /// Computes the unary plus of a value. + /// The value for which to compute the unary plus. + /// unchanged. + public static Decimal128 operator +(Decimal128 value) => value; + + /// Computes the unary negation of a value. + /// The value for which to compute the unary negation. + /// The unary negation of . + public static Decimal128 operator -(Decimal128 value) => new Decimal128(value._upper ^ SignMaskUpper, value._lower); + private static readonly UInt128[] UInt128Powers10 = [ new UInt128(0, 1), diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index 96818f968f75f3..59abfa73a39957 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -258,6 +258,16 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return Number.FormatDecimalIeee754(_value, format, NumberFormatInfo.GetInstance(provider)); } + /// Computes the unary plus of a value. + /// The value for which to compute the unary plus. + /// unchanged. + public static Decimal32 operator +(Decimal32 value) => value; + + /// Computes the unary negation of a value. + /// The value for which to compute the unary negation. + /// The unary negation of . + public static Decimal32 operator -(Decimal32 value) => new Decimal32(value._value ^ SignMask); + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal32NumberBufferLength; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index 0dd9a829786f1c..91ca8beb68410a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -259,6 +259,16 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return Number.FormatDecimalIeee754(_value, format, NumberFormatInfo.GetInstance(provider)); } + /// Computes the unary plus of a value. + /// The value for which to compute the unary plus. + /// unchanged. + public static Decimal64 operator +(Decimal64 value) => value; + + /// Computes the unary negation of a value. + /// The value for which to compute the unary negation. + /// The unary negation of . + public static Decimal64 operator -(Decimal64 value) => new Decimal64(value._value ^ SignMask); + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal64NumberBufferLength; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 4c23d358bdf940..d6d1ad210d80ab 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11401,6 +11401,9 @@ public readonly struct Decimal32 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 static Decimal32 operator +(Decimal32 value) { throw null; } + public static Decimal32 operator -(Decimal32 value) { throw null; } } public readonly struct Decimal64 @@ -11442,6 +11445,9 @@ public readonly struct Decimal64 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 static Decimal64 operator +(Decimal64 value) { throw null; } + public static Decimal64 operator -(Decimal64 value) { throw null; } } public readonly struct Decimal128 @@ -11483,6 +11489,9 @@ public readonly struct Decimal128 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 Decimal128 operator +(Decimal128 value) { throw null; } + public static Decimal128 operator -(Decimal128 value) { throw null; } } public readonly partial struct BFloat16 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators 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 index 8456d115035fbd..d0495ab8772b23 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -502,5 +502,51 @@ public static IEnumerable NaN_NonCanonicalEncodings128_TestData() yield return new object[] { Decimal128.NaN, canonical | (UInt128)0xFFFFF }; yield return new object[] { Decimal128.NaN, canonical | (((UInt128)1 << 110) - 1) }; } + + public static IEnumerable UnaryNegation_TestData() + { + yield return new object[] { new UInt128(0x3040_0000_0000_0000, 0), new UInt128(0xB040_0000_0000_0000, 0) }; // +0 -> -0 + yield return new object[] { new UInt128(0xB040_0000_0000_0000, 0), new UInt128(0x3040_0000_0000_0000, 0) }; // -0 -> +0 + yield return new object[] { new UInt128(0x7800_0000_0000_0000, 0), new UInt128(0xF800_0000_0000_0000, 0) }; // +Infinity -> -Infinity + yield return new object[] { new UInt128(0xF800_0000_0000_0000, 0), new UInt128(0x7800_0000_0000_0000, 0) }; // -Infinity -> +Infinity + yield return new object[] { new UInt128(0xFC00_0000_0000_0000, 0), new UInt128(0x7C00_0000_0000_0000, 0) }; // NaN -> sign-flipped NaN + yield return new object[] { new UInt128(0x7C00_0000_0000_0000, 0), new UInt128(0xFC00_0000_0000_0000, 0) }; + } + + [Theory] + [MemberData(nameof(UnaryNegation_TestData))] + public static void op_UnaryNegation(UInt128 value, UInt128 expected) + { + Decimal128 result = -Unsafe.BitCast(value); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Theory] + [InlineData("123")] + [InlineData("-123")] + [InlineData("4567.891")] + [InlineData("-4567.891")] + [InlineData("9.999999999999999999999999999999999e6144")] + [InlineData("1e-6176")] + public static void op_UnaryNegation_FiniteRoundTrips(string value) + { + Decimal128 d = Decimal128.Parse(value, CultureInfo.InvariantCulture); + Decimal128 negated = -d; + + UInt128 dBits = Unsafe.BitCast(d); + UInt128 negatedBits = Unsafe.BitCast(negated); + + Assert.Equal(dBits ^ new UInt128(0x8000_0000_0000_0000, 0), negatedBits); // only the sign bit differs + Assert.Equal(dBits, Unsafe.BitCast(-negated)); // double negation is identity + } + + [Theory] + [MemberData(nameof(UnaryNegation_TestData))] + public static void op_UnaryPlus(UInt128 value, UInt128 expected) + { + _ = expected; + Decimal128 d = Unsafe.BitCast(value); + Assert.Equal(value, Unsafe.BitCast(+d)); + } } } 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 index e62c9c53359350..58e313f54bccf0 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -498,5 +498,55 @@ public static IEnumerable NaN_NonCanonicalEncodings_TestData() yield return new object[] { Decimal32.NaN, canonical | 0x000F_FFFFU }; yield return new object[] { Decimal32.NaN, canonical | 0x03FF_FFFFU }; } + + public static IEnumerable UnaryNegation_TestData() + { + yield return new object[] { 0x3280_0000U, 0xB280_0000U }; // +0 -> -0 + yield return new object[] { 0xB280_0000U, 0x3280_0000U }; // -0 -> +0 + yield return new object[] { 0x7800_0000U, 0xF800_0000U }; // +Infinity -> -Infinity + yield return new object[] { 0xF800_0000U, 0x7800_0000U }; // -Infinity -> +Infinity + yield return new object[] { 0xFC00_0000U, 0x7C00_0000U }; // NaN -> sign-flipped NaN + yield return new object[] { 0x7C00_0000U, 0xFC00_0000U }; + } + + [Theory] + [MemberData(nameof(UnaryNegation_TestData))] + public static void op_UnaryNegation(uint value, uint expected) + { + Decimal32 result = -Unsafe.BitCast(value); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Theory] + [InlineData("123")] + [InlineData("-123")] + [InlineData("4567.891")] + [InlineData("-4567.891")] + [InlineData("9.999999e96")] + [InlineData("1e-101")] + public static void op_UnaryNegation_FiniteRoundTrips(string value) + { + Decimal32 d = Decimal32.Parse(value, CultureInfo.InvariantCulture); + Decimal32 negated = -d; + + uint dBits = Unsafe.BitCast(d); + uint negatedBits = Unsafe.BitCast(negated); + + Assert.Equal(dBits ^ 0x8000_0000U, negatedBits); // only the sign bit differs + Assert.Equal(dBits, Unsafe.BitCast(-negated)); // double negation is identity + } + + [Theory] + [InlineData(0x3280_0000U)] // +0 + [InlineData(0xB280_0000U)] // -0 + [InlineData(0x7800_0000U)] // +Infinity + [InlineData(0xF800_0000U)] // -Infinity + [InlineData(0xFC00_0000U)] // NaN + [InlineData(0x00F4_9F87U)] // +9.999999e-90 (arbitrary finite) + public static void op_UnaryPlus(uint value) + { + Decimal32 d = Unsafe.BitCast(value); + Assert.Equal(value, Unsafe.BitCast(+d)); + } } } 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 index 6258b5512558f0..d39e29d2d41c25 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -504,5 +504,55 @@ public static IEnumerable NaN_NonCanonicalEncodings64_TestData() yield return new object[] { Decimal64.NaN, canonical | 0x0000_0000_000F_FFFFUL }; yield return new object[] { Decimal64.NaN, canonical | 0x03FF_FFFF_FFFF_FFFFUL }; } + + public static IEnumerable UnaryNegation_TestData() + { + yield return new object[] { 0x31C0_0000_0000_0000UL, 0xB1C0_0000_0000_0000UL }; // +0 -> -0 + yield return new object[] { 0xB1C0_0000_0000_0000UL, 0x31C0_0000_0000_0000UL }; // -0 -> +0 + yield return new object[] { 0x7800_0000_0000_0000UL, 0xF800_0000_0000_0000UL }; // +Infinity -> -Infinity + yield return new object[] { 0xF800_0000_0000_0000UL, 0x7800_0000_0000_0000UL }; // -Infinity -> +Infinity + yield return new object[] { 0xFC00_0000_0000_0000UL, 0x7C00_0000_0000_0000UL }; // NaN -> sign-flipped NaN + yield return new object[] { 0x7C00_0000_0000_0000UL, 0xFC00_0000_0000_0000UL }; + } + + [Theory] + [MemberData(nameof(UnaryNegation_TestData))] + public static void op_UnaryNegation(ulong value, ulong expected) + { + Decimal64 result = -Unsafe.BitCast(value); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Theory] + [InlineData("123")] + [InlineData("-123")] + [InlineData("4567.891")] + [InlineData("-4567.891")] + [InlineData("9.999999999999999e384")] + [InlineData("1e-398")] + public static void op_UnaryNegation_FiniteRoundTrips(string value) + { + Decimal64 d = Decimal64.Parse(value, CultureInfo.InvariantCulture); + Decimal64 negated = -d; + + ulong dBits = Unsafe.BitCast(d); + ulong negatedBits = Unsafe.BitCast(negated); + + Assert.Equal(dBits ^ 0x8000_0000_0000_0000UL, negatedBits); // only the sign bit differs + Assert.Equal(dBits, Unsafe.BitCast(-negated)); // double negation is identity + } + + [Theory] + [InlineData(0x31C0_0000_0000_0000UL)] // +0 + [InlineData(0xB1C0_0000_0000_0000UL)] // -0 + [InlineData(0x7800_0000_0000_0000UL)] // +Infinity + [InlineData(0xF800_0000_0000_0000UL)] // -Infinity + [InlineData(0xFC00_0000_0000_0000UL)] // NaN + [InlineData(0x2FE8_6F26_FC0F_FFFFUL)] // arbitrary finite + public static void op_UnaryPlus(ulong value) + { + Decimal64 d = Unsafe.BitCast(value); + Assert.Equal(value, Unsafe.BitCast(+d)); + } } } From fae0d5cf95bfa3c590fec1bd9f7ea68eb4e23ce2 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 9 Jul 2026 18:38:07 -0700 Subject: [PATCH 02/19] Implement addition/subtraction for Decimal32/64/128 Add binary operator + and operator - for the IEEE 754 decimal types via a shared generic Number.AddDecimalIeee754 helper. The helper aligns operands to the smaller (preferred) exponent using exact base-10 digit arithmetic, folds truly-dropped low-order digits into a sticky flag, and feeds the exact sum into the existing NumberToDecimalIeee754Bits round-nearest-ties-even pipeline. Subtraction negates the right operand's sign bit and adds. Infinity results are canonicalized. Adds exact-bit test vectors validated against an independent oracle. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 329 ++++++++++++++++++ .../src/System/Numerics/Decimal128.cs | 21 ++ .../src/System/Numerics/Decimal32.cs | 18 + .../src/System/Numerics/Decimal64.cs | 18 + .../System.Runtime/ref/System.Runtime.cs | 6 + .../System/Decimal128Tests.cs | 206 ++++++++++- .../System/Decimal32Tests.cs | 206 ++++++++++- .../System/Decimal64Tests.cs | 206 ++++++++++- 8 files changed, 1007 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index 22e0d7952e3749..3837556bcfdef4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -471,5 +471,334 @@ private static TValue DecimalIeee754Rounding(ref NumberBuffer return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, significand, exponent); } + + /// + /// Adds two IEEE 754 decimal values represented by their raw bit patterns and returns the + /// bit pattern of the correctly rounded (round-to-nearest, ties-to-even) sum. + /// + /// + /// The two operands are decoded, aligned to their common (smaller) exponent, and summed + /// exactly in base 10. Digits that fall below the retained precision are folded into a + /// sticky flag so the shared rounding path produces the same result as computing the exact + /// sum. This mirrors the mathematical behavior of the Intel reference implementation while + /// remaining independent of the underlying integer width. + /// + internal static TValue AddDecimalIeee754(TValue left, TValue right) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `bid32_add`, `bid64_add`, and `bid128_add` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (TDecimal.IsNaN(left) || TDecimal.IsNaN(right)) + { + return TDecimal.NaN; + } + + if (TDecimal.IsInfinity(left)) + { + // Inf + Inf with opposing signs is invalid (NaN); every other combination + // that includes at least one infinity returns that infinity (canonicalized). + if (TDecimal.IsInfinity(right) && (TDecimal.IsNegative(left) != TDecimal.IsNegative(right))) + { + return TDecimal.NaN; + } + return TDecimal.IsNegative(left) ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + if (TDecimal.IsInfinity(right)) + { + return TDecimal.IsNegative(right) ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(left); + DecodedDecimalIeee754 b = UnpackDecimalIeee754(right); + + bool aZero = TValue.IsZero(a.Significand); + bool bZero = TValue.IsZero(b.Significand); + + if (aZero && bZero) + { + // The sum of two zeros keeps the shared sign, otherwise it is +0 under + // round-to-nearest. The preferred exponent for a zero result is the smaller one. + bool zeroSign = a.Signed == b.Signed && a.Signed; + int zeroExponent = Math.Min(a.UnbiasedExponent, b.UnbiasedExponent); + return DecimalIeee754FiniteNumberBinaryEncoding(zeroSign, TValue.Zero, zeroExponent); + } + + if (aZero || bZero) + { + // Adding zero yields the other operand's value, but the preferred exponent is the + // smaller of the two exponents. When the zero's exponent is larger there is nothing + // to do; otherwise the coefficient is padded with trailing zeros (bounded by the + // available precision) to lower the exponent toward the zero's exponent. + DecodedDecimalIeee754 nonZero = aZero ? b : a; + TValue nonZeroBits = aZero ? right : left; + int zeroExponent = aZero ? a.UnbiasedExponent : b.UnbiasedExponent; + + if (zeroExponent >= nonZero.UnbiasedExponent) + { + return nonZeroBits; + } + + int nonZeroDigits = TDecimal.CountDigits(nonZero.Significand); + int pad = Math.Min(nonZero.UnbiasedExponent - zeroExponent, TDecimal.Precision - nonZeroDigits); + TValue paddedSignificand = nonZero.Significand * TDecimal.Power10(pad); + return DecimalIeee754FiniteNumberBinaryEncoding(nonZero.Signed, paddedSignificand, nonZero.UnbiasedExponent - pad); + } + + // Both operands are finite and non-zero. Order them so `hi` has the larger (or equal) + // exponent, then align `lo` to `hi` by scaling `hi` up. The exponent difference is + // capped: anything beyond `guard` extra digits cannot influence the retained precision + // except through rounding, so those low-order digits of `lo` become a sticky flag. + DecodedDecimalIeee754 hi; + DecodedDecimalIeee754 lo; + + if (a.UnbiasedExponent >= b.UnbiasedExponent) + { + hi = a; + lo = b; + } + else + { + hi = b; + lo = a; + } + + int exponentDifference = hi.UnbiasedExponent - lo.UnbiasedExponent; + int guard = TDecimal.Precision + 2; + int effectiveDifference = Math.Min(exponentDifference, guard); + int droppedDigits = exponentDifference - effectiveDifference; + int commonExponent = hi.UnbiasedExponent - effectiveDifference; + + int capacity = (2 * TDecimal.Precision) + 4; + Span hiDigits = stackalloc byte[capacity]; + Span loDigits = stackalloc byte[capacity]; + + int hiLength = WriteDigits(hi.Significand, hiDigits, TDecimal.CountDigits(hi.Significand)); + for (int i = 0; i < effectiveDifference; i++) + { + hiDigits[hiLength++] = (byte)'0'; + } + + int loRawLength = TDecimal.CountDigits(lo.Significand); + bool sticky = false; + int loLength; + + if (droppedDigits >= loRawLength) + { + // Every digit of `lo` falls below the retained range; it only contributes stickiness. + loLength = 0; + sticky = true; + } + else if (droppedDigits > 0) + { + Span loRaw = stackalloc byte[capacity]; + WriteDigits(lo.Significand, loRaw, loRawLength); + loLength = loRawLength - droppedDigits; + loRaw.Slice(0, loLength).CopyTo(loDigits); + + for (int i = loLength; i < loRawLength; i++) + { + if (loRaw[i] != (byte)'0') + { + sticky = true; + break; + } + } + } + else + { + loLength = WriteDigits(lo.Significand, loDigits, loRawLength); + } + + bool sameSign = hi.Signed == lo.Signed; + bool resultSign; + int magnitudeLength; + Span magnitude = stackalloc byte[capacity + 1]; + + if (sameSign) + { + magnitudeLength = BigAdd(hiDigits.Slice(0, hiLength), loDigits.Slice(0, loLength), magnitude); + resultSign = hi.Signed; + } + else + { + int comparison = BigCompare(hiDigits.Slice(0, hiLength), loDigits.Slice(0, loLength)); + + if (comparison > 0) + { + magnitudeLength = BigSub(hiDigits.Slice(0, hiLength), loDigits.Slice(0, loLength), magnitude); + resultSign = hi.Signed; + + if (sticky) + { + // The true `lo` magnitude is slightly larger than its retained digits, so the + // exact difference is one unit smaller with a non-zero fractional remainder. + BigDecrement(magnitude.Slice(0, magnitudeLength)); + } + } + else if (comparison < 0) + { + // `droppedDigits` is always zero here, so there is no sticky remainder to account for. + magnitudeLength = BigSub(loDigits.Slice(0, loLength), hiDigits.Slice(0, hiLength), magnitude); + resultSign = lo.Signed; + } + else + { + // Exact cancellation produces +0 under round-to-nearest. + return DecimalIeee754FiniteNumberBinaryEncoding(false, TValue.Zero, commonExponent); + } + } + + int start = 0; + while (start < magnitudeLength && magnitude[start] == (byte)'0') + { + start++; + } + int digitsCount = magnitudeLength - start; + + if (digitsCount == 0) + { + return DecimalIeee754FiniteNumberBinaryEncoding(false, TValue.Zero, commonExponent); + } + + Span numberDigits = stackalloc byte[capacity + 2]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, numberDigits); + magnitude.Slice(start, digitsCount).CopyTo(number.Digits); + number.Digits[digitsCount] = 0; + number.DigitsCount = digitsCount; + number.Scale = digitsCount + commonExponent; + number.IsNegative = resultSign; + number.HasNonZeroTail = sticky; + number.CheckConsistency(); + + return NumberToDecimalIeee754Bits(ref number); + + static int WriteDigits(TValue value, Span destination, int length) + { + TValue ten = TValue.CreateTruncating(10); + + for (int i = length - 1; i >= 0; i--) + { + (value, TValue remainder) = TValue.DivRem(value, ten); + destination[i] = (byte)('0' + int.CreateTruncating(remainder)); + } + + return length; + } + } + + private static ReadOnlySpan TrimLeadingZeros(ReadOnlySpan digits) + { + int start = 0; + while (start < digits.Length && digits[start] == (byte)'0') + { + start++; + } + return digits.Slice(start); + } + + private static int BigCompare(ReadOnlySpan left, ReadOnlySpan right) + { + left = TrimLeadingZeros(left); + right = TrimLeadingZeros(right); + + if (left.Length != right.Length) + { + return left.Length < right.Length ? -1 : 1; + } + + return left.SequenceCompareTo(right); + } + + private static int BigAdd(ReadOnlySpan left, ReadOnlySpan right, Span result) + { + int leftLength = left.Length; + int rightLength = right.Length; + int length = Math.Max(leftLength, rightLength) + 1; + int carry = 0; + + for (int position = 0; position < length; position++) + { + int sum = carry; + + if (position < leftLength) + { + sum += left[leftLength - 1 - position] - '0'; + } + if (position < rightLength) + { + sum += right[rightLength - 1 - position] - '0'; + } + + if (sum >= 10) + { + sum -= 10; + carry = 1; + } + else + { + carry = 0; + } + + result[length - 1 - position] = (byte)('0' + sum); + } + + return length; + } + + // Subtracts `right` from `left`, which must be greater than or equal to `right` in magnitude. + private static int BigSub(ReadOnlySpan left, ReadOnlySpan right, Span result) + { + left = TrimLeadingZeros(left); + right = TrimLeadingZeros(right); + + int leftLength = left.Length; + int rightLength = right.Length; + int borrow = 0; + + for (int position = 0; position < leftLength; position++) + { + int difference = (left[leftLength - 1 - position] - '0') - borrow; + + if (position < rightLength) + { + difference -= right[rightLength - 1 - position] - '0'; + } + + if (difference < 0) + { + difference += 10; + borrow = 1; + } + else + { + borrow = 0; + } + + result[leftLength - 1 - position] = (byte)('0' + difference); + } + + return leftLength; + } + + // Subtracts one from a non-zero magnitude in place. The caller guarantees no underflow. + private static void BigDecrement(Span magnitude) + { + for (int position = magnitude.Length - 1; position >= 0; position--) + { + if (magnitude[position] > (byte)'0') + { + magnitude[position]--; + return; + } + + magnitude[position] = (byte)'9'; + } + } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index b2d7914fe80cd0..7dc35ed4af78a8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -262,6 +262,27 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The unary negation of . public static Decimal128 operator -(Decimal128 value) => new Decimal128(value._upper ^ SignMaskUpper, value._lower); + /// Adds two values together to compute their sum. + /// The value to which is added. + /// The value which is added to . + /// The sum of and . + public static Decimal128 operator +(Decimal128 left, Decimal128 right) + { + UInt128 result = Number.AddDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); + return new Decimal128(result); + } + + /// Subtracts two values to compute their difference. + /// The value from which is subtracted. + /// The value which is subtracted from . + /// The difference of subtracted from . + public static Decimal128 operator -(Decimal128 left, Decimal128 right) + { + UInt128 negatedRight = new UInt128(right._upper ^ SignMaskUpper, right._lower); + UInt128 result = Number.AddDecimalIeee754(new UInt128(left._upper, left._lower), negatedRight); + return new Decimal128(result); + } + private static readonly UInt128[] UInt128Powers10 = [ new UInt128(0, 1), diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index 59abfa73a39957..2bdc66a918c902 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -268,6 +268,24 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The unary negation of . public static Decimal32 operator -(Decimal32 value) => new Decimal32(value._value ^ SignMask); + /// Adds two values together to compute their sum. + /// The value to which is added. + /// The value which is added to . + /// The sum of and . + public static Decimal32 operator +(Decimal32 left, Decimal32 right) + { + return new Decimal32(Number.AddDecimalIeee754(left._value, right._value)); + } + + /// Subtracts two values to compute their difference. + /// The value from which is subtracted. + /// The value which is subtracted from . + /// The difference of subtracted from . + public static Decimal32 operator -(Decimal32 left, Decimal32 right) + { + return new Decimal32(Number.AddDecimalIeee754(left._value, right._value ^ SignMask)); + } + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal32NumberBufferLength; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index 91ca8beb68410a..a896679c43bbb8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -269,6 +269,24 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The unary negation of . public static Decimal64 operator -(Decimal64 value) => new Decimal64(value._value ^ SignMask); + /// Adds two values together to compute their sum. + /// The value to which is added. + /// The value which is added to . + /// The sum of and . + public static Decimal64 operator +(Decimal64 left, Decimal64 right) + { + return new Decimal64(Number.AddDecimalIeee754(left._value, right._value)); + } + + /// Subtracts two values to compute their difference. + /// The value from which is subtracted. + /// The value which is subtracted from . + /// The difference of subtracted from . + public static Decimal64 operator -(Decimal64 left, Decimal64 right) + { + return new Decimal64(Number.AddDecimalIeee754(left._value, right._value ^ SignMask)); + } + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal64NumberBufferLength; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index d6d1ad210d80ab..b04e472d94d3c8 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11404,6 +11404,8 @@ public readonly struct Decimal32 public static Decimal32 operator +(Decimal32 value) { throw null; } public static Decimal32 operator -(Decimal32 value) { throw null; } + public static Decimal32 operator +(Decimal32 left, Decimal32 right) { throw null; } + public static Decimal32 operator -(Decimal32 left, Decimal32 right) { throw null; } } public readonly struct Decimal64 @@ -11448,6 +11450,8 @@ public readonly struct Decimal64 public static Decimal64 operator +(Decimal64 value) { throw null; } public static Decimal64 operator -(Decimal64 value) { throw null; } + public static Decimal64 operator +(Decimal64 left, Decimal64 right) { throw null; } + public static Decimal64 operator -(Decimal64 left, Decimal64 right) { throw null; } } public readonly struct Decimal128 @@ -11492,6 +11496,8 @@ public readonly struct Decimal128 public static Decimal128 operator +(Decimal128 value) { throw null; } public static Decimal128 operator -(Decimal128 value) { throw null; } + public static Decimal128 operator +(Decimal128 left, Decimal128 right) { throw null; } + public static Decimal128 operator -(Decimal128 left, Decimal128 right) { throw null; } } public readonly partial struct BFloat16 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators 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 index d0495ab8772b23..69804757a23c08 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; @@ -548,5 +548,209 @@ public static void op_UnaryPlus(UInt128 value, UInt128 expected) Decimal128 d = Unsafe.BitCast(value); Assert.Equal(value, Unsafe.BitCast(+d)); } + public static IEnumerable op_Addition_TestData() + { + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN + 1 -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // 1 + NaN -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + 1 -> +Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // 1 + +Inf -> +Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf + -Inf -> NaN + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf + +Inf -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + +Inf -> +Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf + -Inf -> -Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 + +0 -> +0 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000) }; // -0 + -0 -> -0 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 + -0 -> +0 (round-half-even) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -0 + +0 -> +0 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 1 + 0 -> 1 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 0 + 1 -> 1 + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf + 1 -> canonical +Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000005), new UInt128(0xF800000000000000, 0x0000000000000000) }; // 1 + non-canonical -Inf -> canonical -Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x000000000000000F), new UInt128(0xF800000000000000, 0x0000000000000003), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // non-canonical +Inf + non-canonical -Inf -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000003) }; // 1 + 2 -> 3 + yield return new object[] { new UInt128(0x303E000000000000, 0x000000000000000F), new UInt128(0x303E000000000000, 0x0000000000000019), new UInt128(0x303E000000000000, 0x0000000000000028) }; // 1.5 + 2.5 -> 4.0 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000002), new UInt128(0x303E000000000000, 0x0000000000000003) }; // 0.1 + 0.2 -> 0.3 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000) }; // 1 + -1 -> +0 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -1 + 1 -> +0 + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3042314DC6448D93, 0x38C15B0A00000000) }; // all-nines + 1 (carry/overflow to next magnitude) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3042629B8C891B26, 0x7182B61400000000) }; // big + big (round) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x2FFC000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // 1 + 1e-P (alignment beyond precision) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x2FF2000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // 1 + tiny (sticky rounding) + yield return new object[] { new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE629B8C891B26, 0x7182B61400000000) }; // max-ish + max-ish (overflow to Inf) + yield return new object[] { new UInt128(0x3034000000000000, 0x000000000012D687), new UInt128(0x3034000000000000, 0x000000000074CBB1), new UInt128(0x3034000000000000, 0x000000000087A238) }; // cohort / preferred exponent + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000064), new UInt128(0x303A000000000000, 0x0000000000000001), new UInt128(0x303A000000000000, 0x00000000000186A1) }; // 100 + 0.001 (exponent spread) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0xB041ED09BEAD87C0, 0x378D8E63FFFFFFFE), new UInt128(0x3040000000000000, 0x0000000000000001) }; // cancellation leaving small + yield return new object[] { new UInt128(0x300C000000000000, 0x0386DF690C6EF5BE), new UInt128(0xB018000000000000, 0x00022FCFF31AA0B9), new UInt128(0xB00C000000000021, 0x5A87EFD0027FEA82) }; + yield return new object[] { new UInt128(0x3016000000000000, 0x00000000025C222D), new UInt128(0xAFEE0000105989B6, 0x14E871DF7FB4E7FF), new UInt128(0xAFEE0000038E860F, 0x754522FCF5E4E7FF) }; + yield return new object[] { new UInt128(0xAFE4004BC3F60664, 0xA2D28316972FB16C), new UInt128(0x3024000000000000, 0x336683344D857468), new UInt128(0x3006B69C6D6AD20B, 0x43BBF7251F590CE0) }; + yield return new object[] { new UInt128(0xB026000000000000, 0x0000000000000009), new UInt128(0xD36C269369438B15, 0x4317B7B7F941F96B), new UInt128(0xD36B81C21CA36ED4, 0x9EED2D2FBC93BE2E) }; + yield return new object[] { new UInt128(0xB030000000000000, 0x00000006C1105459), new UInt128(0xB01E100AA3FBDB37, 0x590C5A8BE72C6FA0), new UInt128(0xB01E100AA3FBDB38, 0xEBA096260A44A9A0) }; + yield return new object[] { new UInt128(0x4592000000000000, 0x0125EB92A4B6C61E), new UInt128(0xB03E000000000000, 0x00000001A8B80287), new UInt128(0x457197E56F0AC19C, 0x7790248FB22C0000) }; + yield return new object[] { new UInt128(0xB018000000000000, 0x000016E8FC21185B), new UInt128(0xB052000000000000, 0x00000000EFF4031C), new UInt128(0xB022C67C0F8B01FB, 0xA2D1A6E0AB03AD2F) }; + yield return new object[] { new UInt128(0x3020000000000000, 0x0000009AA0434574), new UInt128(0x3048000000000000, 0x0000000000001ED7), new UInt128(0x302000000000A72E, 0xE1796A54B2B34574) }; + yield return new object[] { new UInt128(0xB058000000000000, 0x00000000000003DF), new UInt128(0x300C000000000000, 0x000179DCB4BE3FF7), new UInt128(0xB01BE899C8FE6687, 0x7271EA097D860D73) }; + yield return new object[] { new UInt128(0x9EE405C739C662F4, 0x5752242E1DB1EC20), new UInt128(0x3004000000000000, 0x0000BD66F041ECD5), new UInt128(0x2FDE66ACD270BB66, 0x6BD8D5D09E080000) }; + yield return new object[] { new UInt128(0xAFE6000119C10D0F, 0xC98968772E7E6EF7), new UInt128(0xB0020000000002DA, 0x7B7ACAB57EE654B5), new UInt128(0xAFEC426FDE55CADB, 0xA69EFD5C6E732E83) }; + yield return new object[] { new UInt128(0xB00E000000000000, 0x00000000004E6990), new UInt128(0x3852000000000000, 0x000000000000031D), new UInt128(0x381588F38AEA0C31, 0x8456F6DC80000000) }; + yield return new object[] { new UInt128(0x3008000000000000, 0x000000014204609D), new UInt128(0xB020000000000000, 0x000000000000CCF5), new UInt128(0xB008000000000000, 0x00BA6845C8B3EF63) }; + yield return new object[] { new UInt128(0x8462000000000000, 0x00000000EAC878D0), new UInt128(0xB034000000000000, 0x0000000000000110), new UInt128(0xAFF6861B3A0224EC, 0x9A5FD8E800000000) }; + yield return new object[] { new UInt128(0xB02400012DE50EC6, 0xBBBA78C47B74A0E0), new UInt128(0x302200000000012F, 0x1F7FF8F741AE5567), new UInt128(0xB022000BCAF29294, 0x35C8BEB590DFF359) }; + yield return new object[] { new UInt128(0x3038000000000000, 0x0000000000000057), new UInt128(0xB03C000000000000, 0x000000110FC8CD30), new UInt128(0xB038000000000000, 0x000006AA2A702669) }; + yield return new object[] { new UInt128(0x3030000000000000, 0x17E95893CB288412), new UInt128(0xC6E2000000000000, 0x52ABCE0A9DE14E35), new UInt128(0xC6C525B4F0614608, 0xC7F6706318188000) }; + yield return new object[] { new UInt128(0x0870000000000000, 0x000000DD250AAC57), new UInt128(0xB00000000000092C, 0x7754F6DC4C1D7D08), new UInt128(0xAFEAD5977D016087, 0x955141749AFF4000) }; + yield return new object[] { new UInt128(0xB036000000000000, 0x000000130041C7D7), new UInt128(0xB016000000000000, 0x002262E6BEFE8EBF), new UInt128(0xB016000002A30D1F, 0x4AC08D62A1158EBF) }; + yield return new object[] { new UInt128(0x3036000000000000, 0x00000001B6848ADB), new UInt128(0x301E000000000000, 0x00000000000A8089), new UInt128(0x301E00000000018E, 0xD45E584B23DF3089) }; + yield return new object[] { new UInt128(0xC3AE000000000000, 0x04CF4E8A302A8BDC), new UInt128(0xAFFA00000000002E, 0x3420D6865B6A6E4E), new UInt128(0xC38EAAE0CE12AD01, 0x0D9220AFD4DC0000) }; + yield return new object[] { new UInt128(0xD258000000000000, 0x000000000091C9EF), new UInt128(0x300A000000000000, 0x0000167364209915), new UInt128(0xD223D711ABE4492C, 0x8AD6F30498000000) }; + yield return new object[] { new UInt128(0x301C000000000000, 0x000000000439E2E9), new UInt128(0xB014000000000032, 0x708772352DE98CF2), new UInt128(0xB014000000000032, 0x7087719018B9DF62) }; + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000064), new UInt128(0xB03A000000000000, 0x00003B2E17E3E88B), new UInt128(0xB03A000000000000, 0x00003B2E17E56F2B) }; + yield return new object[] { new UInt128(0xB044000000000000, 0x00000000023F5ABE), new UInt128(0xB03A000000006CA7, 0xBAF42939DF4E7BA0), new UInt128(0xB03A000000006CA7, 0xBAF42CA7CB24A660) }; + yield return new object[] { new UInt128(0xC6E6000000000000, 0x00000001B8D3E89E), new UInt128(0x301C000000000007, 0x0964D911A0BBD8F4), new UInt128(0xC6B76CA4E932F6AB, 0xE7ED87915E000000) }; + yield return new object[] { new UInt128(0x305E000000000000, 0x000000000000A71E), new UInt128(0x3026000000000000, 0x000000795A3C873F), new UInt128(0x30261517D8F9A995, 0x6CE399493A3C873F) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x0000000005C867C0), new UInt128(0x302E000000000000, 0x0000000000572AE6), new UInt128(0x301A000000000000, 0x00CAF3EBEC4E7040) }; + yield return new object[] { new UInt128(0xB650000000D0224F, 0xFD6F80D1270DDE07), new UInt128(0xB010000000000000, 0x0000021670F50113), new UInt128(0xB6427C0EBBAA80B2, 0x896A25B17B2F1D80) }; + yield return new object[] { new UInt128(0xB02C000000000006, 0x909F1381718DE51E), new UInt128(0xB01E00000000002D, 0xCE88CD810A4F6601), new UInt128(0xB01E000003E9BAA6, 0xE85BD302FF518901) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x00000000000018F2), new UInt128(0x303C000000000000, 0x000000000046CBDD), new UInt128(0x301A00000000623F, 0xE9B2FED12E21E70E) }; + yield return new object[] { new UInt128(0x5F400000000005E8, 0x2C8923BCBDB9FD3E), new UInt128(0xB034000000000000, 0x00000000000C3C2B), new UInt128(0x5F2A89880B37B794, 0xE2748F76B8143000) }; + yield return new object[] { new UInt128(0xD95C0000000051B5, 0x62B3BD63621B218F), new UInt128(0xB01E000000000000, 0x0000000000000000), new UInt128(0xD948BE3E155B3E8C, 0x9858E4AB87085C00) }; + yield return new object[] { new UInt128(0xB0280000000006F8, 0x435BE30981A1CB95), new UInt128(0x54FE000000000000, 0x00015E8772D1DBC6), new UInt128(0x54D8BE05AF221D3F, 0x366FDDE421700000) }; + yield return new object[] { new UInt128(0x5C72000037D35F7D, 0xF4E8427727658DEF), new UInt128(0x3042000000000000, 0x0001692E610A8ED8), new UInt128(0x5C68552EE79591D2, 0xE66B107D55B2CF60) }; + yield return new object[] { new UInt128(0x305C000000000000, 0x000000000000FE36), new UInt128(0x3014000000000000, 0x000000D1D108B375), new UInt128(0x302340DBFBE65F62, 0x49AC0F7DC0016004) }; + yield return new object[] { new UInt128(0xB042000000000000, 0x000000000000A932), new UInt128(0x303E000000000000, 0x000002503A034940), new UInt128(0x303E000000000000, 0x0000025039C131B8) }; + yield return new object[] { new UInt128(0xB00E000000000000, 0x587FADD919E02966), new UInt128(0x3014000000000000, 0x0721E25C46337978), new UInt128(0x300E00000000001B, 0x83DC8A991F32535A) }; + yield return new object[] { new UInt128(0xAFFA4821D7034A02, 0xB8ACC25DF6F4CF86), new UInt128(0xB044000000000000, 0x00E1766220EE98D3), new UInt128(0xB02338E458FDFD49, 0x227DFD5961604959) }; + yield return new object[] { new UInt128(0x3024000000000000, 0x000000000007C72E), new UInt128(0x1758000000000000, 0x000000041F72D9AA), new UInt128(0x2FECFB527C560A41, 0x002750E0E0000000) }; + yield return new object[] { new UInt128(0x301A0000000061C8, 0xEEFC10C4D3DADBE3), new UInt128(0xB0100003A1125F48, 0x953E3B9231F49265), new UInt128(0xB01000030BDD259F, 0x3627E5CE5E2F5285) }; + yield return new object[] { new UInt128(0x301000004BB9B532, 0xE5CB89F41922BD32), new UInt128(0xDE64000000000000, 0x0000CCBC550D3404), new UInt128(0xDE3E6EFCC8409C2A, 0x0D9E80DD47A00000) }; + yield return new object[] { new UInt128(0xB024000000000000, 0x000000000116C396), new UInt128(0x3028000000005351, 0x729900C58D0BD408), new UInt128(0x3024000000208BD0, 0xC3C44D2B17880F8A) }; + yield return new object[] { new UInt128(0x300200000220753C, 0x8CA178C667F5ED50), new UInt128(0xD382000000000000, 0x0A69C7380DBC5B46), new UInt128(0xD36371F3777B420E, 0x8D9DF2C029C60000) }; + yield return new object[] { new UInt128(0x300C045E788BFD23, 0xFDC576761B96F039), new UInt128(0xB01A000000000000, 0x0AE0ABB2D104776E), new UInt128(0x300C045E78858158, 0xF6A8BBEE1210C539) }; + yield return new object[] { new UInt128(0xCD32000000000000, 0x00000062ADC4C04D), new UInt128(0xB040000000000000, 0xD235D4832BF52B34), new UInt128(0xCD06D0F5E02D285C, 0x39CE6BD79D400000) }; + yield return new object[] { new UInt128(0xB040000000000000, 0x00017F274F195D94), new UInt128(0x2FFA000000000000, 0x022F53F33F61565B), new UInt128(0xB01ACFB53C9B53FE, 0x7F30F461021FFFF0) }; + yield return new object[] { new UInt128(0xB044000000000000, 0x0000000000000046), new UInt128(0x300A00000005E4A6, 0x3DBCC6C875ABB5B6), new UInt128(0xB00A00585A32650B, 0xAF75BA9F4A544A4A) }; + yield return new object[] { new UInt128(0x3066000000000000, 0x0000000000000015), new UInt128(0x3014000000008E7B, 0xA150E858F5A1264C), new UInt128(0x30266789B9F65C81, 0xF732098AA3786387) }; + yield return new object[] { new UInt128(0x21A60000000048E8, 0xDC7FE586D3707752), new UInt128(0xB024000000000023, 0x2E0D31C20845EFD5), new UInt128(0xB00B3FF58FC5004C, 0xC2033C5B68BF2000) }; + yield return new object[] { new UInt128(0xAFF403A1D73F5ACD, 0x96A762C7BAA70840), new UInt128(0x3042000000000000, 0x000000019FF4DE39), new UInt128(0x3013581237E839A6, 0x10386C6F634043B0) }; + yield return new object[] { new UInt128(0x303C000000000000, 0x00113925FCD09CDA), new UInt128(0x3000000000007140, 0x43CE4908B69E66F9), new UInt128(0x3018EF0539CB08CE, 0x80E380BA007D13FC) }; + yield return new object[] { new UInt128(0x3004000000000000, 0x00003C65D7E4774B), new UInt128(0x301825B99EFDAE69, 0x303766A98D4C34B5), new UInt128(0x3017794035E8D01B, 0xE22A029F84FB127A) }; + yield return new object[] { new UInt128(0xB0220000004AE762, 0xF65E345A6F0E69F9), new UInt128(0x301A000000000000, 0x00000000000DC8E9), new UInt128(0xB01A000B6DEE89B7, 0xBFDD0C9222FDC5A7) }; + yield return new object[] { new UInt128(0x3052000000000000, 0x000000000000002A), new UInt128(0x300000001C3D6C1F, 0x26F3E82E5A4692F0), new UInt128(0x3012CF1373ECB904, 0x67A96D19B1D3BCB5) }; + yield return new object[] { new UInt128(0xB048000000000000, 0x0000000000000280), new UInt128(0xAFFC000000000000, 0x0AE997D86F01F581), new UInt128(0xB00B3B8B5B5056E1, 0x6B3BE0524EDF1EC2) }; + yield return new object[] { new UInt128(0xB018000000120CBF, 0x23F4CD9940BD2B9A), new UInt128(0xDFCC005830554042, 0x149E930F0D337664), new UInt128(0xDFC7587CCD030220, 0x8B6E72CB910676A0) }; + yield return new object[] { new UInt128(0x300E000000000000, 0x0112E59C50FC31A5), new UInt128(0x2FEC16CD5779F833, 0xFD5BB8EB3F0C6FD3), new UInt128(0x2FED944C58617AA0, 0x5472BD3EF2FE6FD3) }; + yield return new object[] { new UInt128(0x3D3E000000000000, 0x00000000E4F7F4D6), new UInt128(0xD270000000000000, 0x00000815ABADD29E), new UInt128(0xD247B64511B61A22, 0x8091E2DEA6C00000) }; + yield return new object[] { new UInt128(0x3034000000000000, 0x00181648272743F6), new UInt128(0x569600000002FBFF, 0x4C2693B7C7D3A38C), new UInt128(0x5684B1E1C8F82556, 0xA82AFFAEA5447800) }; + } + + [Theory] + [MemberData(nameof(op_Addition_TestData))] + public static void op_Addition(UInt128 left, UInt128 right, UInt128 expected) + { + Decimal128 result = Unsafe.BitCast(left) + Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + public static IEnumerable op_Subtraction_TestData() + { + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN + 1 -> NaN (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // 1 + NaN -> NaN (sub) + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + 1 -> +Inf (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // 1 + +Inf -> +Inf (sub) + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + -Inf -> NaN (sub) + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf + +Inf -> NaN (sub) + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf + +Inf -> +Inf (sub) + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf + -Inf -> -Inf (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 + +0 -> +0 (sub) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -0 + -0 -> -0 (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 + -0 -> +0 (round-half-even) (sub) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000) }; // -0 + +0 -> +0 (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 1 + 0 -> 1 (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000001) }; // 0 + 1 -> 1 (sub) + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf + 1 -> canonical +Inf (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000005), new UInt128(0x7800000000000000, 0x0000000000000000) }; // 1 + non-canonical -Inf -> canonical -Inf (sub) + yield return new object[] { new UInt128(0x7800000000000000, 0x000000000000000F), new UInt128(0xF800000000000000, 0x0000000000000003), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf + non-canonical -Inf -> NaN (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0xB040000000000000, 0x0000000000000001) }; // 1 + 2 -> 3 (sub) + yield return new object[] { new UInt128(0x303E000000000000, 0x000000000000000F), new UInt128(0x303E000000000000, 0x0000000000000019), new UInt128(0xB03E000000000000, 0x000000000000000A) }; // 1.5 + 2.5 -> 4.0 (sub) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000002), new UInt128(0xB03E000000000000, 0x0000000000000001) }; // 0.1 + 0.2 -> 0.3 (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002) }; // 1 + -1 -> +0 (sub) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000002) }; // -1 + 1 -> +0 (sub) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE) }; // all-nines + 1 (carry/overflow to next magnitude) (sub) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3040000000000000, 0x0000000000000000) }; // big + big (round) (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x2FFC000000000000, 0x0000000000000001), new UInt128(0x2FFDED09BEAD87C0, 0x378D8E63FFFFFFFF) }; // 1 + 1e-P (alignment beyond precision) (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x2FF2000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // 1 + tiny (sticky rounding) (sub) + yield return new object[] { new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE000000000000, 0x0000000000000000) }; // max-ish + max-ish (overflow to Inf) (sub) + yield return new object[] { new UInt128(0x3034000000000000, 0x000000000012D687), new UInt128(0x3034000000000000, 0x000000000074CBB1), new UInt128(0xB034000000000000, 0x000000000061F52A) }; // cohort / preferred exponent (sub) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000064), new UInt128(0x303A000000000000, 0x0000000000000001), new UInt128(0x303A000000000000, 0x000000000001869F) }; // 100 + 0.001 (exponent spread) (sub) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0xB041ED09BEAD87C0, 0x378D8E63FFFFFFFE), new UInt128(0x3042629B8C891B26, 0x7182B61400000000) }; // cancellation leaving small (sub) + yield return new object[] { new UInt128(0x300C000000000000, 0x0386DF690C6EF5BE), new UInt128(0xB018000000000000, 0x00022FCFF31AA0B9), new UInt128(0x300C000000000021, 0x6195AEA21B5DD5FE) }; + yield return new object[] { new UInt128(0x3016000000000000, 0x00000000025C222D), new UInt128(0xAFEE0000105989B6, 0x14E871DF7FB4E7FF), new UInt128(0x2FEE00001D248D5C, 0xB48BC0C20984E7FF) }; + yield return new object[] { new UInt128(0xAFE4004BC3F60664, 0xA2D28316972FB16C), new UInt128(0x3024000000000000, 0x336683344D857468), new UInt128(0xB006B69C6D6AD20B, 0x43BC6455A5EEF320) }; + yield return new object[] { new UInt128(0xB026000000000000, 0x0000000000000009), new UInt128(0xD36C269369438B15, 0x4317B7B7F941F96B), new UInt128(0x536B81C21CA36ED4, 0x9EED2D2FBC93BE2E) }; + yield return new object[] { new UInt128(0xB030000000000000, 0x00000006C1105459), new UInt128(0xB01E100AA3FBDB37, 0x590C5A8BE72C6FA0), new UInt128(0x301E100AA3FBDB35, 0xC6781EF1C41435A0) }; + yield return new object[] { new UInt128(0x4592000000000000, 0x0125EB92A4B6C61E), new UInt128(0xB03E000000000000, 0x00000001A8B80287), new UInt128(0x457197E56F0AC19C, 0x7790248FB22C0000) }; + yield return new object[] { new UInt128(0xB018000000000000, 0x000016E8FC21185B), new UInt128(0xB052000000000000, 0x00000000EFF4031C), new UInt128(0x3022C67C0F8B01FB, 0xA2D1A6E08CFC52D1) }; + yield return new object[] { new UInt128(0x3020000000000000, 0x0000009AA0434574), new UInt128(0x3048000000000000, 0x0000000000001ED7), new UInt128(0xB02000000000A72E, 0xE179691F722CBA8C) }; + yield return new object[] { new UInt128(0xB058000000000000, 0x00000000000003DF), new UInt128(0x300C000000000000, 0x000179DCB4BE3FF7), new UInt128(0xB01BE899C8FE6687, 0x7271EA098279F28D) }; + yield return new object[] { new UInt128(0x9EE405C739C662F4, 0x5752242E1DB1EC20), new UInt128(0x3004000000000000, 0x0000BD66F041ECD5), new UInt128(0xAFDE66ACD270BB66, 0x6BD8D5D09E080000) }; + yield return new object[] { new UInt128(0xAFE6000119C10D0F, 0xC98968772E7E6EF7), new UInt128(0xB0020000000002DA, 0x7B7ACAB57EE654B5), new UInt128(0x2FEC426FDDC588C8, 0xACDFBD26F1F0E17D) }; + yield return new object[] { new UInt128(0xB00E000000000000, 0x00000000004E6990), new UInt128(0x3852000000000000, 0x000000000000031D), new UInt128(0xB81588F38AEA0C31, 0x8456F6DC80000000) }; + yield return new object[] { new UInt128(0x3008000000000000, 0x000000014204609D), new UInt128(0xB020000000000000, 0x000000000000CCF5), new UInt128(0x3008000000000000, 0x00BA68484CBCB09D) }; + yield return new object[] { new UInt128(0x8462000000000000, 0x00000000EAC878D0), new UInt128(0xB034000000000000, 0x0000000000000110), new UInt128(0x2FF6861B3A0224EC, 0x9A5FD8E800000000) }; + yield return new object[] { new UInt128(0xB02400012DE50EC6, 0xBBBA78C47B74A0E0), new UInt128(0x302200000000012F, 0x1F7FF8F741AE5567), new UInt128(0xB022000BCAF294F2, 0x74C8B0A4143C9E27) }; + yield return new object[] { new UInt128(0x3038000000000000, 0x0000000000000057), new UInt128(0xB03C000000000000, 0x000000110FC8CD30), new UInt128(0x3038000000000000, 0x000006AA2A702717) }; + yield return new object[] { new UInt128(0x3030000000000000, 0x17E95893CB288412), new UInt128(0xC6E2000000000000, 0x52ABCE0A9DE14E35), new UInt128(0x46C525B4F0614608, 0xC7F6706318188000) }; + yield return new object[] { new UInt128(0x0870000000000000, 0x000000DD250AAC57), new UInt128(0xB00000000000092C, 0x7754F6DC4C1D7D08), new UInt128(0x2FEAD5977D016087, 0x955141749AFF4000) }; + yield return new object[] { new UInt128(0xB036000000000000, 0x000000130041C7D7), new UInt128(0xB016000000000000, 0x002262E6BEFE8EBF), new UInt128(0xB016000002A30D1F, 0x4A7BC79523187141) }; + yield return new object[] { new UInt128(0x3036000000000000, 0x00000001B6848ADB), new UInt128(0x301E000000000000, 0x00000000000A8089), new UInt128(0x301E00000000018E, 0xD45E584B23CA2F77) }; + yield return new object[] { new UInt128(0xC3AE000000000000, 0x04CF4E8A302A8BDC), new UInt128(0xAFFA00000000002E, 0x3420D6865B6A6E4E), new UInt128(0xC38EAAE0CE12AD01, 0x0D9220AFD4DC0000) }; + yield return new object[] { new UInt128(0xD258000000000000, 0x000000000091C9EF), new UInt128(0x300A000000000000, 0x0000167364209915), new UInt128(0xD223D711ABE4492C, 0x8AD6F30498000000) }; + yield return new object[] { new UInt128(0x301C000000000000, 0x000000000439E2E9), new UInt128(0xB014000000000032, 0x708772352DE98CF2), new UInt128(0x3014000000000032, 0x708772DA43193A82) }; + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000064), new UInt128(0xB03A000000000000, 0x00003B2E17E3E88B), new UInt128(0x303A000000000000, 0x00003B2E17E261EB) }; + yield return new object[] { new UInt128(0xB044000000000000, 0x00000000023F5ABE), new UInt128(0xB03A000000006CA7, 0xBAF42939DF4E7BA0), new UInt128(0x303A000000006CA7, 0xBAF425CBF37850E0) }; + yield return new object[] { new UInt128(0xC6E6000000000000, 0x00000001B8D3E89E), new UInt128(0x301C000000000007, 0x0964D911A0BBD8F4), new UInt128(0xC6B76CA4E932F6AB, 0xE7ED87915E000000) }; + yield return new object[] { new UInt128(0x305E000000000000, 0x000000000000A71E), new UInt128(0x3026000000000000, 0x000000795A3C873F), new UInt128(0x30261517D8F9A995, 0x6CE3985685C378C1) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x0000000005C867C0), new UInt128(0x302E000000000000, 0x0000000000572AE6), new UInt128(0xB01A000000000000, 0x00CAF3EBF7DF3FC0) }; + yield return new object[] { new UInt128(0xB650000000D0224F, 0xFD6F80D1270DDE07), new UInt128(0xB010000000000000, 0x0000021670F50113), new UInt128(0xB6427C0EBBAA80B2, 0x896A25B17B2F1D80) }; + yield return new object[] { new UInt128(0xB02C000000000006, 0x909F1381718DE51E), new UInt128(0xB01E00000000002D, 0xCE88CD810A4F6601), new UInt128(0xB01E000003E9BA4B, 0x4B4A3800EAB2BCFF) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x00000000000018F2), new UInt128(0x303C000000000000, 0x000000000046CBDD), new UInt128(0xB01A00000000623F, 0xE9B2FED12E2218F2) }; + yield return new object[] { new UInt128(0x5F400000000005E8, 0x2C8923BCBDB9FD3E), new UInt128(0xB034000000000000, 0x00000000000C3C2B), new UInt128(0x5F2A89880B37B794, 0xE2748F76B8143000) }; + yield return new object[] { new UInt128(0xD95C0000000051B5, 0x62B3BD63621B218F), new UInt128(0xB01E000000000000, 0x0000000000000000), new UInt128(0xD948BE3E155B3E8C, 0x9858E4AB87085C00) }; + yield return new object[] { new UInt128(0xB0280000000006F8, 0x435BE30981A1CB95), new UInt128(0x54FE000000000000, 0x00015E8772D1DBC6), new UInt128(0xD4D8BE05AF221D3F, 0x366FDDE421700000) }; + yield return new object[] { new UInt128(0x5C72000037D35F7D, 0xF4E8427727658DEF), new UInt128(0x3042000000000000, 0x0001692E610A8ED8), new UInt128(0x5C68552EE79591D2, 0xE66B107D55B2CF60) }; + yield return new object[] { new UInt128(0x305C000000000000, 0x000000000000FE36), new UInt128(0x3014000000000000, 0x000000D1D108B375), new UInt128(0x302340DBFBE65F62, 0x49AC0F7DBFFE9FFC) }; + yield return new object[] { new UInt128(0xB042000000000000, 0x000000000000A932), new UInt128(0x303E000000000000, 0x000002503A034940), new UInt128(0xB03E000000000000, 0x000002503A4560C8) }; + yield return new object[] { new UInt128(0xB00E000000000000, 0x587FADD919E02966), new UInt128(0x3014000000000000, 0x0721E25C46337978), new UInt128(0xB00E00000000001C, 0x34DBE64B52F2A626) }; + yield return new object[] { new UInt128(0xAFFA4821D7034A02, 0xB8ACC25DF6F4CF86), new UInt128(0xB044000000000000, 0x00E1766220EE98D3), new UInt128(0x302338E458FDFD49, 0x227DE2BCB01BB6A7) }; + yield return new object[] { new UInt128(0x3024000000000000, 0x000000000007C72E), new UInt128(0x1758000000000000, 0x000000041F72D9AA), new UInt128(0x2FECFB527C560A41, 0x002750E0E0000000) }; + yield return new object[] { new UInt128(0x301A0000000061C8, 0xEEFC10C4D3DADBE3), new UInt128(0xB0100003A1125F48, 0x953E3B9231F49265), new UInt128(0x30100004364798F1, 0xF454915605B9D245) }; + yield return new object[] { new UInt128(0x301000004BB9B532, 0xE5CB89F41922BD32), new UInt128(0xDE64000000000000, 0x0000CCBC550D3404), new UInt128(0x5E3E6EFCC8409C2A, 0x0D9E80DD47A00000) }; + yield return new object[] { new UInt128(0xB024000000000000, 0x000000000116C396), new UInt128(0x3028000000005351, 0x729900C58D0BD408), new UInt128(0xB024000000208BD0, 0xC3C44D2B19B596B6) }; + yield return new object[] { new UInt128(0x300200000220753C, 0x8CA178C667F5ED50), new UInt128(0xD382000000000000, 0x0A69C7380DBC5B46), new UInt128(0x536371F3777B420E, 0x8D9DF2C029C60000) }; + yield return new object[] { new UInt128(0x300C045E788BFD23, 0xFDC576761B96F039), new UInt128(0xB01A000000000000, 0x0AE0ABB2D104776E), new UInt128(0x300C045E789278EF, 0x04E230FE251D1B39) }; + yield return new object[] { new UInt128(0xCD32000000000000, 0x00000062ADC4C04D), new UInt128(0xB040000000000000, 0xD235D4832BF52B34), new UInt128(0xCD06D0F5E02D285C, 0x39CE6BD79D400000) }; + yield return new object[] { new UInt128(0xB040000000000000, 0x00017F274F195D94), new UInt128(0x2FFA000000000000, 0x022F53F33F61565B), new UInt128(0xB01ACFB53C9B53FE, 0x7F30F46102200010) }; + yield return new object[] { new UInt128(0xB044000000000000, 0x0000000000000046), new UInt128(0x300A00000005E4A6, 0x3DBCC6C875ABB5B6), new UInt128(0xB00A00585A3E2E58, 0x2AEF483035ABB5B6) }; + yield return new object[] { new UInt128(0x3066000000000000, 0x0000000000000015), new UInt128(0x3014000000008E7B, 0xA150E858F5A1264C), new UInt128(0x30266789B9F65C81, 0xF72D419F5C879C79) }; + yield return new object[] { new UInt128(0x21A60000000048E8, 0xDC7FE586D3707752), new UInt128(0xB024000000000023, 0x2E0D31C20845EFD5), new UInt128(0x300B3FF58FC5004C, 0xC2033C5B68BF2000) }; + yield return new object[] { new UInt128(0xAFF403A1D73F5ACD, 0x96A762C7BAA70840), new UInt128(0x3042000000000000, 0x000000019FF4DE39), new UInt128(0xB013581237E839A6, 0x1243E09E4EBFBC50) }; + yield return new object[] { new UInt128(0x303C000000000000, 0x00113925FCD09CDA), new UInt128(0x3000000000007140, 0x43CE4908B69E66F9), new UInt128(0x3018EF0539CB08CE, 0x80E37FC0F5D2EC04) }; + yield return new object[] { new UInt128(0x3004000000000000, 0x00003C65D7E4774B), new UInt128(0x301825B99EFDAE69, 0x303766A98D4C34B5), new UInt128(0xB017794035E8D01B, 0xE22A029F84F90BAA) }; + yield return new object[] { new UInt128(0xB0220000004AE762, 0xF65E345A6F0E69F9), new UInt128(0x301A000000000000, 0x00000000000DC8E9), new UInt128(0xB01A000B6DEE89B7, 0xBFDD0C9223195779) }; + yield return new object[] { new UInt128(0x3052000000000000, 0x000000000000002A), new UInt128(0x300000001C3D6C1F, 0x26F3E82E5A4692F0), new UInt128(0x3012CF1373ECB903, 0x7515293A4E2C434B) }; + yield return new object[] { new UInt128(0xB048000000000000, 0x0000000000000280), new UInt128(0xAFFC000000000000, 0x0AE997D86F01F581), new UInt128(0xB00B3B8B5B5056E1, 0x6B3BE02DB120E13E) }; + yield return new object[] { new UInt128(0xB018000000120CBF, 0x23F4CD9940BD2B9A), new UInt128(0xDFCC005830554042, 0x149E930F0D337664), new UInt128(0x5FC7587CCD030220, 0x8B6E72CB910676A0) }; + yield return new object[] { new UInt128(0x300E000000000000, 0x0112E59C50FC31A5), new UInt128(0x2FEC16CD5779F833, 0xFD5BB8EB3F0C6FD3), new UInt128(0x2FED66B1A96D8A38, 0x59BB4B6874E5902D) }; + yield return new object[] { new UInt128(0x3D3E000000000000, 0x00000000E4F7F4D6), new UInt128(0xD270000000000000, 0x00000815ABADD29E), new UInt128(0x5247B64511B61A22, 0x8091E2DEA6C00000) }; + yield return new object[] { new UInt128(0x3034000000000000, 0x00181648272743F6), new UInt128(0x569600000002FBFF, 0x4C2693B7C7D3A38C), new UInt128(0xD684B1E1C8F82556, 0xA82AFFAEA5447800) }; + } + + [Theory] + [MemberData(nameof(op_Subtraction_TestData))] + public static void op_Subtraction(UInt128 left, UInt128 right, UInt128 expected) + { + Decimal128 result = Unsafe.BitCast(left) - Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } 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 index 58e313f54bccf0..29dd9d0e933999 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; @@ -548,5 +548,209 @@ public static void op_UnaryPlus(uint value) Decimal32 d = Unsafe.BitCast(value); Assert.Equal(value, Unsafe.BitCast(+d)); } + public static IEnumerable op_Addition_TestData() + { + yield return new object[] { 0xFC000000U, 0x32800001U, 0xFC000000U }; // NaN + 1 -> NaN + yield return new object[] { 0x32800001U, 0xFC000000U, 0xFC000000U }; // 1 + NaN -> NaN + yield return new object[] { 0x78000000U, 0x32800001U, 0x78000000U }; // +Inf + 1 -> +Inf + yield return new object[] { 0x32800001U, 0x78000000U, 0x78000000U }; // 1 + +Inf -> +Inf + yield return new object[] { 0x78000000U, 0xF8000000U, 0xFC000000U }; // +Inf + -Inf -> NaN + yield return new object[] { 0xF8000000U, 0x78000000U, 0xFC000000U }; // -Inf + +Inf -> NaN + yield return new object[] { 0x78000000U, 0x78000000U, 0x78000000U }; // +Inf + +Inf -> +Inf + yield return new object[] { 0xF8000000U, 0xF8000000U, 0xF8000000U }; // -Inf + -Inf -> -Inf + yield return new object[] { 0x32800000U, 0x32800000U, 0x32800000U }; // +0 + +0 -> +0 + yield return new object[] { 0xB2800000U, 0xB2800000U, 0xB2800000U }; // -0 + -0 -> -0 + yield return new object[] { 0x32800000U, 0xB2800000U, 0x32800000U }; // +0 + -0 -> +0 (round-half-even) + yield return new object[] { 0xB2800000U, 0x32800000U, 0x32800000U }; // -0 + +0 -> +0 + yield return new object[] { 0x32800001U, 0x32800000U, 0x32800001U }; // 1 + 0 -> 1 + yield return new object[] { 0x32800000U, 0x32800001U, 0x32800001U }; // 0 + 1 -> 1 + yield return new object[] { 0x78000002U, 0x32800001U, 0x78000000U }; // non-canonical +Inf + 1 -> canonical +Inf + yield return new object[] { 0x32800001U, 0xF8000005U, 0xF8000000U }; // 1 + non-canonical -Inf -> canonical -Inf + yield return new object[] { 0x7800000FU, 0xF8000003U, 0xFC000000U }; // non-canonical +Inf + non-canonical -Inf -> NaN + yield return new object[] { 0x32800001U, 0x32800002U, 0x32800003U }; // 1 + 2 -> 3 + yield return new object[] { 0x3200000FU, 0x32000019U, 0x32000028U }; // 1.5 + 2.5 -> 4.0 + yield return new object[] { 0x32000001U, 0x32000002U, 0x32000003U }; // 0.1 + 0.2 -> 0.3 + yield return new object[] { 0x32800001U, 0xB2800001U, 0x32800000U }; // 1 + -1 -> +0 + yield return new object[] { 0xB2800001U, 0x32800001U, 0x32800000U }; // -1 + 1 -> +0 + yield return new object[] { 0x6CB8967FU, 0x32800001U, 0x330F4240U }; // all-nines + 1 (carry/overflow to next magnitude) + yield return new object[] { 0x6CB8967FU, 0x6CB8967FU, 0x331E8480U }; // big + big (round) + yield return new object[] { 0x32800001U, 0x2F000001U, 0x2F8F4240U }; // 1 + 1e-P (alignment beyond precision) + yield return new object[] { 0x32800001U, 0x2C800001U, 0x2F8F4240U }; // 1 + tiny (sticky rounding) + yield return new object[] { 0x5F8F4240U, 0x5F8F4240U, 0x5F9E8480U }; // max-ish + max-ish (overflow to Inf) + yield return new object[] { 0x2F92D687U, 0x2FF4CBB1U, 0x6BE7A238U }; // cohort / preferred exponent + yield return new object[] { 0x32800064U, 0x31000001U, 0x310186A1U }; // 100 + 0.001 (exponent spread) + yield return new object[] { 0x6CB8967FU, 0xECB8967EU, 0x32800001U }; // cancellation leaving small + yield return new object[] { 0x2F2C8C35U, 0x2A00002DU, 0x2F2C8C35U }; + yield return new object[] { 0xB8002104U, 0xB98043D6U, 0xB89A82E5U }; + yield return new object[] { 0xA8800010U, 0x34802213U, 0x6CC51A38U }; + yield return new object[] { 0x318496C5U, 0xA71C20DCU, 0x312DE3B2U }; + yield return new object[] { 0x2E830F1EU, 0xA9000005U, 0x2E1E972CU }; + yield return new object[] { 0xAD00006CU, 0xB7011BA9U, 0xB66ECE04U }; + yield return new object[] { 0xAC000017U, 0x380B7DD1U, 0x37F2EA2AU }; + yield return new object[] { 0xBC800009U, 0xA600170BU, 0xEE695440U }; + yield return new object[] { 0x1C8C3773U, 0x4C800FA6U, 0x4B3D2070U }; + yield return new object[] { 0xB2000007U, 0xEAD57BF3U, 0xAF6ACFC0U }; + yield return new object[] { 0xBA000010U, 0x2F0ED152U, 0xB7986A00U }; + yield return new object[] { 0x6A41D272U, 0xA8800006U, 0x6A41D271U }; + yield return new object[] { 0xAC0000EBU, 0x28800027U, 0xAA23DBB0U }; + yield return new object[] { 0xAB8B81BBU, 0xB0000044U, 0xADE7C2CBU }; + yield return new object[] { 0x390001B3U, 0xB885B1AEU, 0xB885A0B0U }; + yield return new object[] { 0x3B800006U, 0x27007EF8U, 0x38DB8D80U }; + yield return new object[] { 0x27034AB4U, 0x3280024CU, 0x30D9B8C0U }; + yield return new object[] { 0x3B800094U, 0x3685C05CU, 0x39969540U }; + yield return new object[] { 0xC0001DD4U, 0x330000DDU, 0xBEF48420U }; + yield return new object[] { 0x378000FAU, 0x9A02AE5DU, 0x35A625A0U }; + yield return new object[] { 0x278014E4U, 0x3806111AU, 0x37BCAB04U }; + yield return new object[] { 0xB20ACE79U, 0x3204CA4CU, 0xB206042DU }; + yield return new object[] { 0xB98025B8U, 0xB2000002U, 0xEE1356C0U }; + yield return new object[] { 0xB780002BU, 0x3B80007DU, 0x399312D0U }; + yield return new object[] { 0xDA81A30AU, 0x38002124U, 0xDA105E64U }; + yield return new object[] { 0xB5800001U, 0x36000006U, 0x3580003BU }; + yield return new object[] { 0xB8800004U, 0x35297F01U, 0xB5B8E2B3U }; + yield return new object[] { 0xAF00252CU, 0xBF000006U, 0xBC5B8D80U }; + yield return new object[] { 0x92EA6215U, 0x38800002U, 0x359E8480U }; + yield return new object[] { 0xAE800008U, 0xAE00009FU, 0xAE0000EFU }; + yield return new object[] { 0x2A0025A7U, 0x35001CE0U, 0x33F0CB00U }; + yield return new object[] { 0x45800056U, 0x32000030U, 0x70C339C0U }; + yield return new object[] { 0x360DAA53U, 0xB20D6A8EU, 0x6D68A73EU }; + yield return new object[] { 0xAF800202U, 0xC000857DU, 0xBF3424D4U }; + yield return new object[] { 0xE1000004U, 0x2D00003DU, 0x2ADD1420U }; + yield return new object[] { 0xBF805674U, 0x6E377AE8U, 0xBEA1C550U }; + yield return new object[] { 0x2E0087F8U, 0x2F0E50D7U, 0x6BAF35FFU }; + yield return new object[] { 0x32800061U, 0x4D80016BU, 0x4BB763B0U }; + yield return new object[] { 0xB1FD9564U, 0x33800008U, 0xB1FC5CE4U }; + yield return new object[] { 0xA9000C89U, 0xC0000CD9U, 0xBEB22FA8U }; + yield return new object[] { 0x37001CE3U, 0x58095C61U, 0x57DD9BCAU }; + yield return new object[] { 0x2E8018F2U, 0xB98002B6U, 0xB7E9E560U }; + yield return new object[] { 0x400B2B74U, 0xAE0003ACU, 0x3FEFB288U }; + yield return new object[] { 0x2B000676U, 0xD1000009U, 0xF3895440U }; + yield return new object[] { 0xDD8E1313U, 0x33325AAEU, 0xF74CBEBEU }; + yield return new object[] { 0x3500008BU, 0x38800030U, 0x36493E01U }; + yield return new object[] { 0x338DB8E1U, 0xB9000023U, 0xB6B567DFU }; + yield return new object[] { 0xB700030FU, 0x2A807402U, 0xB57779F0U }; + yield return new object[] { 0xA68542F3U, 0x35000005U, 0x324C4B40U }; + yield return new object[] { 0x261B9CA7U, 0xB7000005U, 0xB44C4B40U }; + yield return new object[] { 0x33000007U, 0xB180033BU, 0x3180181DU }; + yield return new object[] { 0x0D80010EU, 0xBC000016U, 0xB9A191C0U }; + yield return new object[] { 0x310000DCU, 0xAC0583C6U, 0x2F2191C0U }; + yield return new object[] { 0x297CCD10U, 0x2A000006U, 0x297CCF68U }; + yield return new object[] { 0x15000001U, 0xA7010BBCU, 0xA6689570U }; + yield return new object[] { 0xAB8002D0U, 0xAA84EC44U, 0xAA860584U }; + yield return new object[] { 0xB4000180U, 0x38002093U, 0x36FF3E38U }; + yield return new object[] { 0x2A015FAAU, 0x3A000006U, 0x375B8D80U }; + yield return new object[] { 0x388002C5U, 0x2A04F49DU, 0x36EC2F50U }; + yield return new object[] { 0x35003DFEU, 0x35BB999DU, 0x35BB9FD0U }; + } + + [Theory] + [MemberData(nameof(op_Addition_TestData))] + public static void op_Addition(uint left, uint right, uint expected) + { + Decimal32 result = Unsafe.BitCast(left) + Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + public static IEnumerable op_Subtraction_TestData() + { + yield return new object[] { 0xFC000000U, 0x32800001U, 0xFC000000U }; // NaN + 1 -> NaN (sub) + yield return new object[] { 0x32800001U, 0xFC000000U, 0xFC000000U }; // 1 + NaN -> NaN (sub) + yield return new object[] { 0x78000000U, 0x32800001U, 0x78000000U }; // +Inf + 1 -> +Inf (sub) + yield return new object[] { 0x32800001U, 0x78000000U, 0xF8000000U }; // 1 + +Inf -> +Inf (sub) + yield return new object[] { 0x78000000U, 0xF8000000U, 0x78000000U }; // +Inf + -Inf -> NaN (sub) + yield return new object[] { 0xF8000000U, 0x78000000U, 0xF8000000U }; // -Inf + +Inf -> NaN (sub) + yield return new object[] { 0x78000000U, 0x78000000U, 0xFC000000U }; // +Inf + +Inf -> +Inf (sub) + yield return new object[] { 0xF8000000U, 0xF8000000U, 0xFC000000U }; // -Inf + -Inf -> -Inf (sub) + yield return new object[] { 0x32800000U, 0x32800000U, 0x32800000U }; // +0 + +0 -> +0 (sub) + yield return new object[] { 0xB2800000U, 0xB2800000U, 0x32800000U }; // -0 + -0 -> -0 (sub) + yield return new object[] { 0x32800000U, 0xB2800000U, 0x32800000U }; // +0 + -0 -> +0 (round-half-even) (sub) + yield return new object[] { 0xB2800000U, 0x32800000U, 0xB2800000U }; // -0 + +0 -> +0 (sub) + yield return new object[] { 0x32800001U, 0x32800000U, 0x32800001U }; // 1 + 0 -> 1 (sub) + yield return new object[] { 0x32800000U, 0x32800001U, 0xB2800001U }; // 0 + 1 -> 1 (sub) + yield return new object[] { 0x78000002U, 0x32800001U, 0x78000000U }; // non-canonical +Inf + 1 -> canonical +Inf (sub) + yield return new object[] { 0x32800001U, 0xF8000005U, 0x78000000U }; // 1 + non-canonical -Inf -> canonical -Inf (sub) + yield return new object[] { 0x7800000FU, 0xF8000003U, 0x78000000U }; // non-canonical +Inf + non-canonical -Inf -> NaN (sub) + yield return new object[] { 0x32800001U, 0x32800002U, 0xB2800001U }; // 1 + 2 -> 3 (sub) + yield return new object[] { 0x3200000FU, 0x32000019U, 0xB200000AU }; // 1.5 + 2.5 -> 4.0 (sub) + yield return new object[] { 0x32000001U, 0x32000002U, 0xB2000001U }; // 0.1 + 0.2 -> 0.3 (sub) + yield return new object[] { 0x32800001U, 0xB2800001U, 0x32800002U }; // 1 + -1 -> +0 (sub) + yield return new object[] { 0xB2800001U, 0x32800001U, 0xB2800002U }; // -1 + 1 -> +0 (sub) + yield return new object[] { 0x6CB8967FU, 0x32800001U, 0x6CB8967EU }; // all-nines + 1 (carry/overflow to next magnitude) (sub) + yield return new object[] { 0x6CB8967FU, 0x6CB8967FU, 0x32800000U }; // big + big (round) (sub) + yield return new object[] { 0x32800001U, 0x2F000001U, 0x6BD8967FU }; // 1 + 1e-P (alignment beyond precision) (sub) + yield return new object[] { 0x32800001U, 0x2C800001U, 0x2F8F4240U }; // 1 + tiny (sticky rounding) (sub) + yield return new object[] { 0x5F8F4240U, 0x5F8F4240U, 0x5F800000U }; // max-ish + max-ish (overflow to Inf) (sub) + yield return new object[] { 0x2F92D687U, 0x2FF4CBB1U, 0xAFE1F52AU }; // cohort / preferred exponent (sub) + yield return new object[] { 0x32800064U, 0x31000001U, 0x3101869FU }; // 100 + 0.001 (exponent spread) (sub) + yield return new object[] { 0x6CB8967FU, 0xECB8967EU, 0x331E8480U }; // cancellation leaving small (sub) + yield return new object[] { 0x2F2C8C35U, 0x2A00002DU, 0x2F2C8C35U }; + yield return new object[] { 0xB8002104U, 0xB98043D6U, 0x389A7C4BU }; + yield return new object[] { 0xA8800010U, 0x34802213U, 0xECC51A38U }; + yield return new object[] { 0x318496C5U, 0xA71C20DCU, 0x312DE3B2U }; + yield return new object[] { 0x2E830F1EU, 0xA9000005U, 0x2E1E972CU }; + yield return new object[] { 0xAD00006CU, 0xB7011BA9U, 0x366ECE04U }; + yield return new object[] { 0xAC000017U, 0x380B7DD1U, 0xB7F2EA2AU }; + yield return new object[] { 0xBC800009U, 0xA600170BU, 0xEE695440U }; + yield return new object[] { 0x1C8C3773U, 0x4C800FA6U, 0xCB3D2070U }; + yield return new object[] { 0xB2000007U, 0xEAD57BF3U, 0xAF6ACFC0U }; + yield return new object[] { 0xBA000010U, 0x2F0ED152U, 0xB7986A00U }; + yield return new object[] { 0x6A41D272U, 0xA8800006U, 0x6A41D273U }; + yield return new object[] { 0xAC0000EBU, 0x28800027U, 0xAA23DBB0U }; + yield return new object[] { 0xAB8B81BBU, 0xB0000044U, 0x2DE7C235U }; + yield return new object[] { 0x390001B3U, 0xB885B1AEU, 0x3885C2ACU }; + yield return new object[] { 0x3B800006U, 0x27007EF8U, 0x38DB8D80U }; + yield return new object[] { 0x27034AB4U, 0x3280024CU, 0xB0D9B8C0U }; + yield return new object[] { 0x3B800094U, 0x3685C05CU, 0x39969540U }; + yield return new object[] { 0xC0001DD4U, 0x330000DDU, 0xBEF48420U }; + yield return new object[] { 0x378000FAU, 0x9A02AE5DU, 0x35A625A0U }; + yield return new object[] { 0x278014E4U, 0x3806111AU, 0xB7BCAB04U }; + yield return new object[] { 0xB20ACE79U, 0x3204CA4CU, 0xB20F98C5U }; + yield return new object[] { 0xB98025B8U, 0xB2000002U, 0xEE1356C0U }; + yield return new object[] { 0xB780002BU, 0x3B80007DU, 0xB99312D0U }; + yield return new object[] { 0xDA81A30AU, 0x38002124U, 0xDA105E64U }; + yield return new object[] { 0xB5800001U, 0x36000006U, 0xB580003DU }; + yield return new object[] { 0xB8800004U, 0x35297F01U, 0xB5C12F4DU }; + yield return new object[] { 0xAF00252CU, 0xBF000006U, 0x3C5B8D80U }; + yield return new object[] { 0x92EA6215U, 0x38800002U, 0xB59E8480U }; + yield return new object[] { 0xAE800008U, 0xAE00009FU, 0x2E00004FU }; + yield return new object[] { 0x2A0025A7U, 0x35001CE0U, 0xB3F0CB00U }; + yield return new object[] { 0x45800056U, 0x32000030U, 0x70C339C0U }; + yield return new object[] { 0x360DAA53U, 0xB20D6A8EU, 0x6D68A73EU }; + yield return new object[] { 0xAF800202U, 0xC000857DU, 0x3F3424D4U }; + yield return new object[] { 0xE1000004U, 0x2D00003DU, 0xAADD1420U }; + yield return new object[] { 0xBF805674U, 0x6E377AE8U, 0xBEA1C550U }; + yield return new object[] { 0x2E0087F8U, 0x2F0E50D7U, 0xEBAF1ACDU }; + yield return new object[] { 0x32800061U, 0x4D80016BU, 0xCBB763B0U }; + yield return new object[] { 0xB1FD9564U, 0x33800008U, 0xB1FECDE4U }; + yield return new object[] { 0xA9000C89U, 0xC0000CD9U, 0x3EB22FA8U }; + yield return new object[] { 0x37001CE3U, 0x58095C61U, 0xD7DD9BCAU }; + yield return new object[] { 0x2E8018F2U, 0xB98002B6U, 0x37E9E560U }; + yield return new object[] { 0x400B2B74U, 0xAE0003ACU, 0x3FEFB288U }; + yield return new object[] { 0x2B000676U, 0xD1000009U, 0x73895440U }; + yield return new object[] { 0xDD8E1313U, 0x33325AAEU, 0xF74CBEBEU }; + yield return new object[] { 0x3500008BU, 0x38800030U, 0xB6493DFFU }; + yield return new object[] { 0x338DB8E1U, 0xB9000023U, 0x36B567E1U }; + yield return new object[] { 0xB700030FU, 0x2A807402U, 0xB57779F0U }; + yield return new object[] { 0xA68542F3U, 0x35000005U, 0xB24C4B40U }; + yield return new object[] { 0x261B9CA7U, 0xB7000005U, 0x344C4B40U }; + yield return new object[] { 0x33000007U, 0xB180033BU, 0x31801E93U }; + yield return new object[] { 0x0D80010EU, 0xBC000016U, 0x39A191C0U }; + yield return new object[] { 0x310000DCU, 0xAC0583C6U, 0x2F2191C0U }; + yield return new object[] { 0x297CCD10U, 0x2A000006U, 0x297CCAB8U }; + yield return new object[] { 0x15000001U, 0xA7010BBCU, 0x26689570U }; + yield return new object[] { 0xAB8002D0U, 0xAA84EC44U, 0x2A83D304U }; + yield return new object[] { 0xB4000180U, 0x38002093U, 0xB6FF3E38U }; + yield return new object[] { 0x2A015FAAU, 0x3A000006U, 0xB75B8D80U }; + yield return new object[] { 0x388002C5U, 0x2A04F49DU, 0x36EC2F50U }; + yield return new object[] { 0x35003DFEU, 0x35BB999DU, 0xB5BB936AU }; + } + + [Theory] + [MemberData(nameof(op_Subtraction_TestData))] + public static void op_Subtraction(uint left, uint right, uint expected) + { + Decimal32 result = Unsafe.BitCast(left) - Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } 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 index d39e29d2d41c25..4d6ce43c0fd982 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; @@ -554,5 +554,209 @@ public static void op_UnaryPlus(ulong value) Decimal64 d = Unsafe.BitCast(value); Assert.Equal(value, Unsafe.BitCast(+d)); } + public static IEnumerable op_Addition_TestData() + { + yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, 0xFC000000_00000000UL }; // NaN + 1 -> NaN + yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // 1 + NaN -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // +Inf + 1 -> +Inf + yield return new object[] { 0x31C00000_00000001UL, 0x78000000_00000000UL, 0x78000000_00000000UL }; // 1 + +Inf -> +Inf + yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // +Inf + -Inf -> NaN + yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // -Inf + +Inf -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0x78000000_00000000UL }; // +Inf + +Inf -> +Inf + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0xF8000000_00000000UL }; // -Inf + -Inf -> -Inf + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0x31C00000_00000000UL }; // +0 + +0 -> +0 + yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000000UL, 0xB1C00000_00000000UL }; // -0 + -0 -> -0 + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // +0 + -0 -> +0 (round-half-even) + yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000000UL, 0x31C00000_00000000UL }; // -0 + +0 -> +0 + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000000UL, 0x31C00000_00000001UL }; // 1 + 0 -> 1 + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000001UL, 0x31C00000_00000001UL }; // 0 + 1 -> 1 + yield return new object[] { 0x78000000_00000002UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // non-canonical +Inf + 1 -> canonical +Inf + yield return new object[] { 0x31C00000_00000001UL, 0xF8000000_00000005UL, 0xF8000000_00000000UL }; // 1 + non-canonical -Inf -> canonical -Inf + yield return new object[] { 0x78000000_0000000FUL, 0xF8000000_00000003UL, 0xFC000000_00000000UL }; // non-canonical +Inf + non-canonical -Inf -> NaN + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL, 0x31C00000_00000003UL }; // 1 + 2 -> 3 + yield return new object[] { 0x31A00000_0000000FUL, 0x31A00000_00000019UL, 0x31A00000_00000028UL }; // 1.5 + 2.5 -> 4.0 + yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_00000002UL, 0x31A00000_00000003UL }; // 0.1 + 0.2 -> 0.3 + yield return new object[] { 0x31C00000_00000001UL, 0xB1C00000_00000001UL, 0x31C00000_00000000UL }; // 1 + -1 -> +0 + yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000001UL, 0x31C00000_00000000UL }; // -1 + 1 -> +0 + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x31C00000_00000001UL, 0x31E38D7E_A4C68000UL }; // all-nines + 1 (carry/overflow to next magnitude) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFFUL, 0x31E71AFD_498D0000UL }; // big + big (round) + yield return new object[] { 0x31C00000_00000001UL, 0x2FC00000_00000001UL, 0x2FE38D7E_A4C68000UL }; // 1 + 1e-P (alignment beyond precision) + yield return new object[] { 0x31C00000_00000001UL, 0x2F200000_00000001UL, 0x2FE38D7E_A4C68000UL }; // 1 + tiny (sticky rounding) + yield return new object[] { 0x5FE38D7E_A4C68000UL, 0x5FE38D7E_A4C68000UL, 0x5FE71AFD_498D0000UL }; // max-ish + max-ish (overflow to Inf) + yield return new object[] { 0x31000000_0012D687UL, 0x31000000_0074CBB1UL, 0x31000000_0087A238UL }; // cohort / preferred exponent + yield return new object[] { 0x31C00000_00000064UL, 0x31600000_00000001UL, 0x31600000_000186A1UL }; // 100 + 0.001 (exponent spread) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0xEC7386F2_6FC0FFFEUL, 0x31C00000_00000001UL }; // cancellation leaving small + yield return new object[] { 0xAF200000_000023D2UL, 0xAFA34A58_43C82F35UL, 0xEBE0E772_A5D1D81BUL }; + yield return new object[] { 0x19C00000_0006B3C8UL, 0xB2400000_000014EDUL, 0xB0D30829_C20FD000UL }; + yield return new object[] { 0x31200054_42297730UL, 0x2F000063_A23BC84AUL, 0x30ACDB58_73BFC300UL }; + yield return new object[] { 0x2E60007D_E4F31691UL, 0x328000E1_F407BFE1UL, 0x6C827A4C_6EB74510UL }; + yield return new object[] { 0x95400091_BAB57FB9UL, 0x14600009_ED261C68UL, 0x94D63C8D_4F42A1D5UL }; + yield return new object[] { 0xB1E00000_173C0E97UL, 0x17C00575_228BEDE5UL, 0xB10DD951_783BC580UL }; + yield return new object[] { 0x31C00000_3838FC8EUL, 0x33800000_0008C901UL, 0x3254745E_CA115476UL }; + yield return new object[] { 0x33E00000_00000006UL, 0xB3000000_000738AFUL, 0x33000000_038C4E51UL }; + yield return new object[] { 0xB120007E_4A0EE845UL, 0xB1A2CD7D_00E19992UL, 0xB19C06E2_29247E66UL }; + yield return new object[] { 0xB1800000_003142FFUL, 0x2F4004A5_E17CD24DUL, 0xB06B7839_F143220AUL }; + yield return new object[] { 0x30A1AE18_98FE6B6EUL, 0xB2400000_00034D87UL, 0xB107B037_771252EFUL }; + yield return new object[] { 0x1040016E_281289C6UL, 0x2E417F41_004E3595UL, 0x2E2EF88A_030E17D2UL }; + yield return new object[] { 0x2F2160D1_63A6598CUL, 0xAD988DFF_DCC311D5UL, 0x2F0DC82D_E47F6478UL }; + yield return new object[] { 0xAF800000_000149F7UL, 0x82600001_9308E202UL, 0xAE3E0297_BAE1D800UL }; + yield return new object[] { 0xB3600000_0000230CUL, 0xB8600000_00000003UL, 0xB68AA87B_EE538000UL }; + yield return new object[] { 0xAD80816C_363173A8UL, 0x2FE00718_1926DD3EUL, 0x2F9BB622_3FD03A30UL }; + yield return new object[] { 0xB2800000_00000000UL, 0x2F000000_007AF558UL, 0x2F000000_007AF558UL }; + yield return new object[] { 0xAE836513_54C06345UL, 0x8D000000_0000002AUL, 0xEB99F2C1_4F83E0B2UL }; + yield return new object[] { 0xAE543A07_C082FDB3UL, 0x35200000_00000007UL, 0x3358DE76_816D8000UL }; + yield return new object[] { 0x32600000_0000B48AUL, 0x31400000_00015465UL, 0x31402A08_F77A3865UL }; + yield return new object[] { 0x33400000_000E8F6AUL, 0x33C00000_00000287UL, 0x33400000_007148DAUL }; + yield return new object[] { 0xAFE00000_0025FA34UL, 0x31C00000_000001BBUL, 0x302FBD0F_C05A7EC7UL }; + yield return new object[] { 0x31200015_EE481751UL, 0xB06385FA_ED15C9E7UL, 0x6C211C84_0F44FE09UL }; + yield return new object[] { 0xB3A00000_0001B644UL, 0x31400000_88C9241BUL, 0xB263FC6A_AB408FFEUL }; + yield return new object[] { 0xCE000000_2C155ADEUL, 0x2E400000_00000CC1UL, 0xCD3A4698_81BB8300UL }; + yield return new object[] { 0x2E0059A2_1C73F798UL, 0xAEC00000_0000038FUL, 0x2E0059A1_E62735D8UL }; + yield return new object[] { 0xB4400000_00000000UL, 0xB3C00000_00000002UL, 0xB3C00000_00000002UL }; + yield return new object[] { 0x14200000_1E148863UL, 0xAF600000_00000003UL, 0xAD8AA87B_EE538000UL }; + yield return new object[] { 0xB3000000_093826EBUL, 0x27600000_00000008UL, 0xB2257EC2_9E692780UL }; + yield return new object[] { 0xBDA00000_0054E714UL, 0xAEC00000_00083858UL, 0xBC93C497_9C5DC800UL }; + yield return new object[] { 0xBBE00000_00000008UL, 0xB1000000_00000008UL, 0xBA1C6BF5_26340000UL }; + yield return new object[] { 0xD8600000_72FA8C0BUL, 0x30404C87_C53B7F29UL, 0xD7A6DA6F_8B62D8C0UL }; + yield return new object[] { 0x31E00000_00011A74UL, 0x31EB8DA8_BD290BE9UL, 0x31EB8DA8_BD2A265DUL }; + yield return new object[] { 0xAE8000BE_61899933UL, 0x2E8E68EB_22D62BD8UL, 0x2E8E682C_C14C92A5UL }; + yield return new object[] { 0x30A00002_3C81B477UL, 0xB3400000_0000019EUL, 0xB1AEB54E_DD5EBFA0UL }; + yield return new object[] { 0xAFC00016_E7160E75UL, 0x32600000_00000013UL, 0x30A6C00A_39129993UL }; + yield return new object[] { 0x328000C6_0667F8FFUL, 0x437376D5_46AB54AAUL, 0x437376D5_46AB54AAUL }; + yield return new object[] { 0x316008E3_5687A600UL, 0x32C00000_000000BCUL, 0x316019FC_8DDA0600UL }; + yield return new object[] { 0x32600011_10EB5AFDUL, 0xB1200000_70F0F514UL, 0x31DA0A71_1FB6021CUL }; + yield return new object[] { 0x2F600000_0000018DUL, 0x34000000_000001F2UL, 0x3271B148_9AFB4000UL }; + yield return new object[] { 0x3260573E_35DB7C8EUL, 0x328018AA_7862D5CEUL, 0x32614DE6_E9B7D69AUL }; + yield return new object[] { 0x22C00000_00000005UL, 0xB0A00000_000004FDUL, 0xAF24896C_BB60D000UL }; + yield return new object[] { 0xD4200014_54458F18UL, 0xB2C00000_0000001DUL, 0xD39F0516_A377FF00UL }; + yield return new object[] { 0x30200000_0000004CUL, 0x324199F5_8C48AAF6UL, 0x32300397_7AD6AD9CUL }; + yield return new object[] { 0xEB90A2E6_7C00A0EDUL, 0xB2C00000_000020F5UL, 0xB15DF968_23F85000UL }; + yield return new object[] { 0xB282B0EE_E060C24AUL, 0x30600000_00000008UL, 0xB27AE954_C3C796E4UL }; + yield return new object[] { 0x2E20004A_73ED6947UL, 0x9C6F546E_573DB6B7UL, 0x2DAB5C50_69E06570UL }; + yield return new object[] { 0xB1600000_00000054UL, 0x30800000_00000500UL, 0xB0800000_32115D00UL }; + yield return new object[] { 0xAA4042B6_7AA7818BUL, 0x31800000_00000055UL, 0x2FDE32B4_78974000UL }; + yield return new object[] { 0xB2E00000_00000366UL, 0xB2C00000_00001F1DUL, 0xB2C00000_00004119UL }; + yield return new object[] { 0x33000000_00000002UL, 0x33200000_02DF7470UL, 0x33000000_1CBA8C62UL }; + yield return new object[] { 0xB062FAF8_87469CE0UL, 0x30200000_00000045UL, 0xB05DCDB5_48C220B9UL }; + yield return new object[] { 0xB0200000_000E661FUL, 0xB1000000_00076DFBUL, 0xB020046D_AB3E759FUL }; + yield return new object[] { 0xA9800000_01ECEE93UL, 0x2DC04A74_C2BEB838UL, 0x2D9D159C_127FF5E0UL }; + yield return new object[] { 0x29200001_1579BC2EUL, 0xAFC058BC_B025DCC1UL, 0xEBE2A9B4_CECA3B64UL }; + yield return new object[] { 0xB3C00000_00000012UL, 0xD5600000_0000001EUL, 0xD3AAA87B_EE538000UL }; + yield return new object[] { 0xAF400000_000003E0UL, 0xB2E00000_1892B9E7UL, 0xB20EA590_A3724D80UL }; + yield return new object[] { 0x32400000_1E910857UL, 0xB120000E_041E212AUL, 0x31723815_132D90B0UL }; + yield return new object[] { 0x2FE00000_36F3932DUL, 0x32000000_00003529UL, 0x30A4D5BB_39132B9AUL }; + yield return new object[] { 0x32C00001_B3BF92FFUL, 0x2FC00000_02D2E250UL, 0x3219F8FD_F0BB7DC0UL }; + } + + [Theory] + [MemberData(nameof(op_Addition_TestData))] + public static void op_Addition(ulong left, ulong right, ulong expected) + { + Decimal64 result = Unsafe.BitCast(left) + Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + public static IEnumerable op_Subtraction_TestData() + { + yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, 0xFC000000_00000000UL }; // NaN + 1 -> NaN (sub) + yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // 1 + NaN -> NaN (sub) + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // +Inf + 1 -> +Inf (sub) + yield return new object[] { 0x31C00000_00000001UL, 0x78000000_00000000UL, 0xF8000000_00000000UL }; // 1 + +Inf -> +Inf (sub) + yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0x78000000_00000000UL }; // +Inf + -Inf -> NaN (sub) + yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0xF8000000_00000000UL }; // -Inf + +Inf -> NaN (sub) + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // +Inf + +Inf -> +Inf (sub) + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // -Inf + -Inf -> -Inf (sub) + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0x31C00000_00000000UL }; // +0 + +0 -> +0 (sub) + yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // -0 + -0 -> -0 (sub) + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // +0 + -0 -> +0 (round-half-even) (sub) + yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000000UL, 0xB1C00000_00000000UL }; // -0 + +0 -> +0 (sub) + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000000UL, 0x31C00000_00000001UL }; // 1 + 0 -> 1 (sub) + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000001UL, 0xB1C00000_00000001UL }; // 0 + 1 -> 1 (sub) + yield return new object[] { 0x78000000_00000002UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // non-canonical +Inf + 1 -> canonical +Inf (sub) + yield return new object[] { 0x31C00000_00000001UL, 0xF8000000_00000005UL, 0x78000000_00000000UL }; // 1 + non-canonical -Inf -> canonical -Inf (sub) + yield return new object[] { 0x78000000_0000000FUL, 0xF8000000_00000003UL, 0x78000000_00000000UL }; // non-canonical +Inf + non-canonical -Inf -> NaN (sub) + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL, 0xB1C00000_00000001UL }; // 1 + 2 -> 3 (sub) + yield return new object[] { 0x31A00000_0000000FUL, 0x31A00000_00000019UL, 0xB1A00000_0000000AUL }; // 1.5 + 2.5 -> 4.0 (sub) + yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_00000002UL, 0xB1A00000_00000001UL }; // 0.1 + 0.2 -> 0.3 (sub) + yield return new object[] { 0x31C00000_00000001UL, 0xB1C00000_00000001UL, 0x31C00000_00000002UL }; // 1 + -1 -> +0 (sub) + yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000001UL, 0xB1C00000_00000002UL }; // -1 + 1 -> +0 (sub) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x31C00000_00000001UL, 0x6C7386F2_6FC0FFFEUL }; // all-nines + 1 (carry/overflow to next magnitude) (sub) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFFUL, 0x31C00000_00000000UL }; // big + big (round) (sub) + yield return new object[] { 0x31C00000_00000001UL, 0x2FC00000_00000001UL, 0x6BF386F2_6FC0FFFFUL }; // 1 + 1e-P (alignment beyond precision) (sub) + yield return new object[] { 0x31C00000_00000001UL, 0x2F200000_00000001UL, 0x2FE38D7E_A4C68000UL }; // 1 + tiny (sticky rounding) (sub) + yield return new object[] { 0x5FE38D7E_A4C68000UL, 0x5FE38D7E_A4C68000UL, 0x5FE00000_00000000UL }; // max-ish + max-ish (overflow to Inf) (sub) + yield return new object[] { 0x31000000_0012D687UL, 0x31000000_0074CBB1UL, 0xB1000000_0061F52AUL }; // cohort / preferred exponent (sub) + yield return new object[] { 0x31C00000_00000064UL, 0x31600000_00000001UL, 0x31600000_0001869FUL }; // 100 + 0.001 (exponent spread) (sub) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0xEC7386F2_6FC0FFFEUL, 0x31E71AFD_498D0000UL }; // cancellation leaving small (sub) + yield return new object[] { 0xAF200000_000023D2UL, 0xAFA34A58_43C82F35UL, 0x6BE0E772_A5D1D809UL }; + yield return new object[] { 0x19C00000_0006B3C8UL, 0xB2400000_000014EDUL, 0x30D30829_C20FD000UL }; + yield return new object[] { 0x31200054_42297730UL, 0x2F000063_A23BC84AUL, 0x30ACDB58_73BFC300UL }; + yield return new object[] { 0x2E60007D_E4F31691UL, 0x328000E1_F407BFE1UL, 0xEC827A4C_6EB74510UL }; + yield return new object[] { 0x95400091_BAB57FB9UL, 0x14600009_ED261C68UL, 0x94D63C8D_5457B34BUL }; + yield return new object[] { 0xB1E00000_173C0E97UL, 0x17C00575_228BEDE5UL, 0xB10DD951_783BC580UL }; + yield return new object[] { 0x31C00000_3838FC8EUL, 0x33800000_0008C901UL, 0xB254745E_CA0E738AUL }; + yield return new object[] { 0x33E00000_00000006UL, 0xB3000000_000738AFUL, 0x33000000_039ABFAFUL }; + yield return new object[] { 0xB120007E_4A0EE845UL, 0xB1A2CD7D_00E19992UL, 0x319C06E1_E87B8102UL }; + yield return new object[] { 0xB1800000_003142FFUL, 0x2F4004A5_E17CD24DUL, 0xB06B7839_F14349F6UL }; + yield return new object[] { 0x30A1AE18_98FE6B6EUL, 0xB2400000_00034D87UL, 0x3107B113_ACA02511UL }; + yield return new object[] { 0x1040016E_281289C6UL, 0x2E417F41_004E3595UL, 0xAE2EF88A_030E17D2UL }; + yield return new object[] { 0x2F2160D1_63A6598CUL, 0xAD988DFF_DCC311D5UL, 0x2F0DC82D_E47F9A78UL }; + yield return new object[] { 0xAF800000_000149F7UL, 0x82600001_9308E202UL, 0xAE3E0297_BAE1D800UL }; + yield return new object[] { 0xB3600000_0000230CUL, 0xB8600000_00000003UL, 0x368AA87B_EE538000UL }; + yield return new object[] { 0xAD80816C_363173A8UL, 0x2FE00718_1926DD3EUL, 0xAF9BB622_3FD03A30UL }; + yield return new object[] { 0xB2800000_00000000UL, 0x2F000000_007AF558UL, 0xAF000000_007AF558UL }; + yield return new object[] { 0xAE836513_54C06345UL, 0x8D000000_0000002AUL, 0xEB99F2C1_4F83E0B2UL }; + yield return new object[] { 0xAE543A07_C082FDB3UL, 0x35200000_00000007UL, 0xB358DE76_816D8000UL }; + yield return new object[] { 0x32600000_0000B48AUL, 0x31400000_00015465UL, 0x31402A08_F7778F9BUL }; + yield return new object[] { 0x33400000_000E8F6AUL, 0x33C00000_00000287UL, 0xB3400000_00542A06UL }; + yield return new object[] { 0xAFE00000_0025FA34UL, 0x31C00000_000001BBUL, 0xB02FBD0F_C05B4139UL }; + yield return new object[] { 0x31200015_EE481751UL, 0xB06385FA_ED15C9E7UL, 0x6C21D0E9_71E2F337UL }; + yield return new object[] { 0xB3A00000_0001B644UL, 0x31400000_88C9241BUL, 0xB263FC6A_AB409002UL }; + yield return new object[] { 0xCE000000_2C155ADEUL, 0x2E400000_00000CC1UL, 0xCD3A4698_81BB8300UL }; + yield return new object[] { 0x2E0059A2_1C73F798UL, 0xAEC00000_0000038FUL, 0x2E0059A2_52C0B958UL }; + yield return new object[] { 0xB4400000_00000000UL, 0xB3C00000_00000002UL, 0x33C00000_00000002UL }; + yield return new object[] { 0x14200000_1E148863UL, 0xAF600000_00000003UL, 0x2D8AA87B_EE538000UL }; + yield return new object[] { 0xB3000000_093826EBUL, 0x27600000_00000008UL, 0xB2257EC2_9E692780UL }; + yield return new object[] { 0xBDA00000_0054E714UL, 0xAEC00000_00083858UL, 0xBC93C497_9C5DC800UL }; + yield return new object[] { 0xBBE00000_00000008UL, 0xB1000000_00000008UL, 0xBA1C6BF5_26340000UL }; + yield return new object[] { 0xD8600000_72FA8C0BUL, 0x30404C87_C53B7F29UL, 0xD7A6DA6F_8B62D8C0UL }; + yield return new object[] { 0x31E00000_00011A74UL, 0x31EB8DA8_BD290BE9UL, 0xB1EB8DA8_BD27F175UL }; + yield return new object[] { 0xAE8000BE_61899933UL, 0x2E8E68EB_22D62BD8UL, 0xAE8E69A9_845FC50BUL }; + yield return new object[] { 0x30A00002_3C81B477UL, 0xB3400000_0000019EUL, 0x31AEB54E_DD5EC060UL }; + yield return new object[] { 0xAFC00016_E7160E75UL, 0x32600000_00000013UL, 0xB0A6C00A_3912E66DUL }; + yield return new object[] { 0x328000C6_0667F8FFUL, 0x437376D5_46AB54AAUL, 0xC37376D5_46AB54AAUL }; + yield return new object[] { 0x316008E3_5687A600UL, 0x32C00000_000000BCUL, 0xB1600835_E0CABA00UL }; + yield return new object[] { 0x32600011_10EB5AFDUL, 0xB1200000_70F0F514UL, 0x31DA0A71_1FB69624UL }; + yield return new object[] { 0x2F600000_0000018DUL, 0x34000000_000001F2UL, 0xB271B148_9AFB4000UL }; + yield return new object[] { 0x3260573E_35DB7C8EUL, 0x328018AA_7862D5CEUL, 0xB2609F6A_7E00DD7EUL }; + yield return new object[] { 0x22C00000_00000005UL, 0xB0A00000_000004FDUL, 0x2F24896C_BB60D000UL }; + yield return new object[] { 0xD4200014_54458F18UL, 0xB2C00000_0000001DUL, 0xD39F0516_A377FF00UL }; + yield return new object[] { 0x30200000_0000004CUL, 0x324199F5_8C48AAF6UL, 0xB2300397_7AD6AD9CUL }; + yield return new object[] { 0xEB90A2E6_7C00A0EDUL, 0xB2C00000_000020F5UL, 0x315DF968_23F85000UL }; + yield return new object[] { 0xB282B0EE_E060C24AUL, 0x30600000_00000008UL, 0xB27AE954_C3C796E4UL }; + yield return new object[] { 0x2E20004A_73ED6947UL, 0x9C6F546E_573DB6B7UL, 0x2DAB5C50_69E06570UL }; + yield return new object[] { 0xB1600000_00000054UL, 0x30800000_00000500UL, 0xB0800000_32116700UL }; + yield return new object[] { 0xAA4042B6_7AA7818BUL, 0x31800000_00000055UL, 0xAFDE32B4_78974000UL }; + yield return new object[] { 0xB2E00000_00000366UL, 0xB2C00000_00001F1DUL, 0xB2C00000_000002DFUL }; + yield return new object[] { 0x33000000_00000002UL, 0x33200000_02DF7470UL, 0xB3000000_1CBA8C5EUL }; + yield return new object[] { 0xB062FAF8_87469CE0UL, 0x30200000_00000045UL, 0xB05DCDB5_48C220C7UL }; + yield return new object[] { 0xB0200000_000E661FUL, 0xB1000000_00076DFBUL, 0x3020046D_AB21A961UL }; + yield return new object[] { 0xA9800000_01ECEE93UL, 0x2DC04A74_C2BEB838UL, 0xAD9D159C_127FF5E0UL }; + yield return new object[] { 0x29200001_1579BC2EUL, 0xAFC058BC_B025DCC1UL, 0x6BE2A9B4_CECA3B64UL }; + yield return new object[] { 0xB3C00000_00000012UL, 0xD5600000_0000001EUL, 0x53AAA87B_EE538000UL }; + yield return new object[] { 0xAF400000_000003E0UL, 0xB2E00000_1892B9E7UL, 0x320EA590_A3724D80UL }; + yield return new object[] { 0x32400000_1E910857UL, 0xB120000E_041E212AUL, 0x31723815_5AF0BA50UL }; + yield return new object[] { 0x2FE00000_36F3932DUL, 0x32000000_00003529UL, 0xB0A4D5BB_39132466UL }; + yield return new object[] { 0x32C00001_B3BF92FFUL, 0x2FC00000_02D2E250UL, 0x3219F8FD_F0BB7DC0UL }; + } + + [Theory] + [MemberData(nameof(op_Subtraction_TestData))] + public static void op_Subtraction(ulong left, ulong right, ulong expected) + { + Decimal64 result = Unsafe.BitCast(left) - Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } From b25a6649b4d13f4de4e533dae72f1cc01b86c553 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 9 Jul 2026 19:05:20 -0700 Subject: [PATCH 03/19] Implement equality/inequality for Decimal32/64/128 Add IEEE 754 == and != operators to Decimal32, Decimal64, and Decimal128. A new shared Number.EqualsDecimalIeee754 helper returns false when either operand is NaN (NaN is unordered and never equal, even to itself) and otherwise defers to the existing CompareDecimalIeee754 for value-based equality, so the two zeros compare equal and different members of a cohort compare equal. operator != is defined as !(left == right). The ref assembly also declares the Equals(object)/GetHashCode overrides that the concrete types already provide (required once == / != are present). Tests use bit-exact MemberData vectors produced by an independent Python decimal oracle, covering NaN, signed zero, cohort equality, and non-canonical infinity inputs, plus random equal-cohort and unequal pairs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 23 ++++ .../src/System/Numerics/Decimal128.cs | 18 +++ .../src/System/Numerics/Decimal32.cs | 18 +++ .../src/System/Numerics/Decimal64.cs | 18 +++ .../System.Runtime/ref/System.Runtime.cs | 12 ++ .../System/Decimal128Tests.cs | 103 ++++++++++++++++++ .../System/Decimal32Tests.cs | 103 ++++++++++++++++++ .../System/Decimal64Tests.cs | 103 ++++++++++++++++++ 8 files changed, 398 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index 3837556bcfdef4..aa597b6cfd4fd1 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -216,6 +216,29 @@ static int InternalInfinityCompare(TValue current, TValue other) } } + /// + /// Determines whether two IEEE 754 decimal values represented by their raw bit patterns + /// are numerically equal using the semantics required by the equality operator. + /// + /// + /// Unlike , which treats NaN as equal + /// to NaN so that equality is consistent with hashing and ordering, this method follows the + /// IEEE 754 equality-comparison rules used by operator ==: NaN is unordered and is + /// therefore never equal to any value, including itself. All other values (including the two + /// zeros and different members of the same cohort) compare by numeric value. + /// + internal static bool EqualsDecimalIeee754(TValue leftDecimalBits, TValue rightDecimalBits) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + if (TDecimal.IsNaN(leftDecimalBits) || TDecimal.IsNaN(rightDecimalBits)) + { + return false; + } + + return CompareDecimalIeee754(leftDecimalBits, rightDecimalBits) == 0; + } + private static TValue NumberToDecimalIeee754Bits(ref NumberBuffer number) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index 7dc35ed4af78a8..69b96e5f25cdf8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -283,6 +283,24 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return new Decimal128(result); } + /// Compares two values to determine equality. + /// The value to compare with . + /// The value to compare with . + /// true if is equal to ; otherwise, false. + public static bool operator ==(Decimal128 left, Decimal128 right) + { + return Number.EqualsDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); + } + + /// Compares two values to determine inequality. + /// The value to compare with . + /// The value to compare with . + /// true if is not equal to ; otherwise, false. + public static bool operator !=(Decimal128 left, Decimal128 right) + { + return !(left == right); + } + private static readonly UInt128[] UInt128Powers10 = [ new UInt128(0, 1), diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index 2bdc66a918c902..15ff2817498a64 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -286,6 +286,24 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return new Decimal32(Number.AddDecimalIeee754(left._value, right._value ^ SignMask)); } + /// Compares two values to determine equality. + /// The value to compare with . + /// The value to compare with . + /// true if is equal to ; otherwise, false. + public static bool operator ==(Decimal32 left, Decimal32 right) + { + return Number.EqualsDecimalIeee754(left._value, right._value); + } + + /// Compares two values to determine inequality. + /// The value to compare with . + /// The value to compare with . + /// true if is not equal to ; otherwise, false. + public static bool operator !=(Decimal32 left, Decimal32 right) + { + return !(left == right); + } + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal32NumberBufferLength; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index a896679c43bbb8..0f3b02b80d8f0f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -287,6 +287,24 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return new Decimal64(Number.AddDecimalIeee754(left._value, right._value ^ SignMask)); } + /// Compares two values to determine equality. + /// The value to compare with . + /// The value to compare with . + /// true if is equal to ; otherwise, false. + public static bool operator ==(Decimal64 left, Decimal64 right) + { + return Number.EqualsDecimalIeee754(left._value, right._value); + } + + /// Compares two values to determine inequality. + /// The value to compare with . + /// The value to compare with . + /// true if is not equal to ; otherwise, false. + public static bool operator !=(Decimal64 left, Decimal64 right) + { + return !(left == right); + } + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal64NumberBufferLength; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index b04e472d94d3c8..c063b41ad4c6b5 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11372,6 +11372,8 @@ public readonly struct Decimal32 public int CompareTo(object? value) { throw null; } public int CompareTo(Decimal32 other) { throw null; } public bool Equals(Decimal32 other) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override int GetHashCode() { throw null; } public static Decimal32 Parse(string s) { throw null; } public static Decimal32 Parse(string s, System.Globalization.NumberStyles style) { throw null; } @@ -11406,6 +11408,8 @@ public readonly struct Decimal32 public static Decimal32 operator -(Decimal32 value) { throw null; } public static Decimal32 operator +(Decimal32 left, Decimal32 right) { throw null; } public static Decimal32 operator -(Decimal32 left, Decimal32 right) { throw null; } + public static bool operator ==(Decimal32 left, Decimal32 right) { throw null; } + public static bool operator !=(Decimal32 left, Decimal32 right) { throw null; } } public readonly struct Decimal64 @@ -11418,6 +11422,8 @@ public readonly struct Decimal64 public int CompareTo(object? value) { throw null; } public int CompareTo(Decimal64 other) { throw null; } public bool Equals(Decimal64 other) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override int GetHashCode() { throw null; } public static Decimal64 Parse(string s) { throw null; } public static Decimal64 Parse(string s, System.Globalization.NumberStyles style) { throw null; } @@ -11452,6 +11458,8 @@ public readonly struct Decimal64 public static Decimal64 operator -(Decimal64 value) { throw null; } public static Decimal64 operator +(Decimal64 left, Decimal64 right) { throw null; } public static Decimal64 operator -(Decimal64 left, Decimal64 right) { throw null; } + public static bool operator ==(Decimal64 left, Decimal64 right) { throw null; } + public static bool operator !=(Decimal64 left, Decimal64 right) { throw null; } } public readonly struct Decimal128 @@ -11464,6 +11472,8 @@ public readonly struct Decimal128 public int CompareTo(object? value) { throw null; } public int CompareTo(Decimal128 other) { throw null; } public bool Equals(Decimal128 other) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public override int GetHashCode() { throw null; } public static Decimal128 Parse(string s) { throw null; } public static Decimal128 Parse(string s, System.Globalization.NumberStyles style) { throw null; } @@ -11498,6 +11508,8 @@ public readonly struct Decimal128 public static Decimal128 operator -(Decimal128 value) { throw null; } public static Decimal128 operator +(Decimal128 left, Decimal128 right) { throw null; } public static Decimal128 operator -(Decimal128 left, Decimal128 right) { throw null; } + public static bool operator ==(Decimal128 left, Decimal128 right) { throw null; } + public static bool operator !=(Decimal128 left, Decimal128 right) { throw null; } } public readonly partial struct BFloat16 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators 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 index 69804757a23c08..8174370e1c398c 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -752,5 +752,108 @@ public static void op_Subtraction(UInt128 left, UInt128 right, UInt128 expected) Assert.Equal(expected, Unsafe.BitCast(result)); } + public static IEnumerable op_Equality_TestData() + { + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000), false }; // NaN == NaN -> false + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), false }; // NaN == 1 -> false + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), false }; // 1 == NaN -> false + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), false }; // NaN == +Inf -> false + yield return new object[] { new UInt128(0xFE00000000000000, 0x0000000000000000), new UInt128(0xFE00000000000000, 0x0000000000000000), false }; // sNaN == sNaN -> false + yield return new object[] { new UInt128(0xFE00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), false }; // sNaN == 1 -> false + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), true }; // +Inf == +Inf -> true + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), true }; // -Inf == -Inf -> true + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), false }; // +Inf == -Inf -> false + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), false }; // +Inf == 1 -> false + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x7800000000000000, 0x0000000000000000), true }; // non-canonical +Inf == +Inf -> true + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000005), new UInt128(0xF800000000000000, 0x0000000000000000), true }; // non-canonical -Inf == -Inf -> true + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), true }; // +0 == -0 -> true + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), true }; // -0 == +0 -> true + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), true }; // +0 == +0 -> true + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000001), true }; // 1 == 1 -> true + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000001), false }; // 1 == -1 -> false + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000001), false }; // -1 == 1 -> false + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x000000000000000A), true }; // 1 == 1.0 -> true (cohort) + yield return new object[] { new UInt128(0x303E000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x0000000000000001), true }; // 1.0 == 1 -> true (cohort) + yield return new object[] { new UInt128(0x303C000000000000, 0x0000000000000064), new UInt128(0x303E000000000000, 0x000000000000000A), true }; // 1.00 == 1.0 -> true (cohort) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000064), new UInt128(0x3044000000000000, 0x0000000000000001), true }; // 100 == 1e2 -> true (cohort) + yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x0000000000000001), false }; // 10 == 1 -> false + yield return new object[] { new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), true }; // large == large -> true + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), true }; // all-nines == all-nines -> true + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE), false }; // all-nines != near -> false + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000001), true }; // 0.1 == 0.1 -> true + yield return new object[] { new UInt128(0xB03E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000001), false }; // -0.1 == 0.1 -> false + yield return new object[] { new UInt128(0x304A000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), true }; // +0 (exp 5) == +0 -> true (zero cohort) + yield return new object[] { new UInt128(0x301A000000000003, 0x9BCF4E2EDCF12EF7), new UInt128(0x3018000000000024, 0x16190DD4A16BD5A6), true }; + yield return new object[] { new UInt128(0xABF20063BD39CCEE, 0x436C8968918D808D), new UInt128(0x303E000000000000, 0x00230AA75BEE8E14), false }; + yield return new object[] { new UInt128(0x300C00002B873DBB, 0xBEBE1FC587469CE0), new UInt128(0x2FF00016B43C9B4F, 0x23150FD733E05182), false }; + yield return new object[] { new UInt128(0xB026000000000000, 0x0000076D2FCDB6B3), new UInt128(0x3000000000000000, 0x0003114CF7F46793), false }; + yield return new object[] { new UInt128(0x3030000000000030, 0x6A45B64B7AA1C513), new UInt128(0x302C0000000012E9, 0x833B357BE730FB6C), true }; + yield return new object[] { new UInt128(0x289C000000000000, 0x000000000004EDA8), new UInt128(0x2896000000000000, 0x0000000013405840), true }; + yield return new object[] { new UInt128(0xB008000000000000, 0x0AC06CB3BC9AF0A1), new UInt128(0xB004000000000004, 0x332A7635AC85FEE4), true }; + yield return new object[] { new UInt128(0xD3FA000000000000, 0x0000000000000F1B), new UInt128(0xB02800044597400B, 0x08FB25BCBB359B1B), false }; + yield return new object[] { new UInt128(0x3038000000000000, 0x0157E2AA499D25FA), new UInt128(0xB048000000000000, 0x000000000000003B), false }; + yield return new object[] { new UInt128(0x1A30000000000000, 0x0970216EDBCE4CB6), new UInt128(0x1A2C000000000003, 0xAFCD0F4DDC95F718), true }; + yield return new object[] { new UInt128(0xB032000002F2A152, 0x9B60F2A4EEAF5036), new UInt128(0x305E000000000000, 0x000000000003CF69), false }; + yield return new object[] { new UInt128(0xAFFC000000000000, 0x000119FF4AE64AB6), new UInt128(0xAFF6000000000000, 0x044D8D3C9393D6F0), true }; + yield return new object[] { new UInt128(0xB024000000000000, 0x0000000000383395), new UInt128(0xB01E000000000000, 0x00000000DB897E08), true }; + yield return new object[] { new UInt128(0x3016000000000000, 0x00000000025C222D), new UInt128(0xAFEE0000105989B6, 0x14E871DF7FB4E7FF), false }; + yield return new object[] { new UInt128(0xAFE4004BC3F60664, 0xA2D28316972FB16C), new UInt128(0x3024000000000000, 0x336683344D857468), false }; + yield return new object[] { new UInt128(0xB026000000000000, 0x0000000000000009), new UInt128(0xD38C000000000000, 0x0134998469438B15), false }; + yield return new object[] { new UInt128(0xB030000000000000, 0x00000006C1105459), new UInt128(0xB040000000000000, 0x0000000000000272), false }; + yield return new object[] { new UInt128(0x30360000000000BC, 0x438EB99271BCAFC8), new UInt128(0xB040000000000000, 0x00000000DBE2C593), false }; + yield return new object[] { new UInt128(0x9B3E004F2DD1B479, 0xFC21185B344542C4), new UInt128(0xB052000000000000, 0x00000000EFF4031C), false }; + yield return new object[] { new UInt128(0x3020000000000000, 0x0000009AA0434574), new UInt128(0x301C000000000000, 0x00003C669A472150), true }; + yield return new object[] { new UInt128(0x3048000000000000, 0x000000003A904488), new UInt128(0x3046000000000000, 0x0000000249A2AD50), true }; + yield return new object[] { new UInt128(0x303C0000000000E8, 0xFEE00455BE372834), new UInt128(0x3036000000038E23, 0x9B10EEEF07750B20), true }; + yield return new object[] { new UInt128(0xB012000000000000, 0x0000000002BA9121), new UInt128(0xB00C000000000000, 0x0000000AA8C6E8E8), true }; + yield return new object[] { new UInt128(0x3004000000000000, 0x0000BD66F041ECD5), new UInt128(0x3002000000000000, 0x0007660562934052), true }; + yield return new object[] { new UInt128(0xADE0000000000000, 0x0002A69155BE30BC), new UInt128(0xADDE000000000000, 0x001A81AD596DE758), true }; + yield return new object[] { new UInt128(0xDF96000000000000, 0x000000DF56F88A23), new UInt128(0xDF90000000000000, 0x0003686BBADB98B8), true }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x0000000000000034), new UInt128(0xB016000000000000, 0x0000000000001450), true }; + yield return new object[] { new UInt128(0x334C0000001FE2B8, 0xADA8450705F0175D), new UInt128(0x334800000C749023, 0xD5BAF6BE51C92054), true }; + yield return new object[] { new UInt128(0x8462000000000000, 0x00000000EAC878D0), new UInt128(0xB044000000000000, 0x0766DC043F42224C), false }; + yield return new object[] { new UInt128(0xB02400012DE50EC6, 0xBBBA78C47B74A0E0), new UInt128(0x302200000000012F, 0x1F7FF8F741AE5567), false }; + yield return new object[] { new UInt128(0x3038000000000000, 0x0000000000000057), new UInt128(0xB03C000000000000, 0x000000110FC8CD30), false }; + yield return new object[] { new UInt128(0x3030000000000000, 0x17E95893CB288412), new UInt128(0x004A00000000001C, 0x8D893304C576A4DE), false }; + yield return new object[] { new UInt128(0xB030000000000000, 0x0000000002CEAE78), new UInt128(0xB02A000000000000, 0x0000000AF75984C0), true }; + yield return new object[] { new UInt128(0xB004000000000000, 0x0000000FAFB5B374), new UInt128(0xDF860003F338CEEF, 0xDDFFE3EAF7A5A1FD), false }; + yield return new object[] { new UInt128(0xB026000000000000, 0x00009E4E44EAB8B0), new UInt128(0x9244000000000000, 0x000004781F6323F3), false }; + yield return new object[] { new UInt128(0xAFFA00000000002E, 0x3420D6865B6A6E4E), new UInt128(0xD258000000000000, 0x000000000091C9EF), false }; + yield return new object[] { new UInt128(0x300A000000000000, 0x0000167364209915), new UInt128(0x301C000000000000, 0x000000000439E2E9), false }; + yield return new object[] { new UInt128(0xB014000000000032, 0x708772352DE98CF2), new UInt128(0xB0120000000001F8, 0x654A7613CB1F8174), true }; + yield return new object[] { new UInt128(0xA7B80000000002FC, 0x39DED1B856E65FE1), new UInt128(0xB0240000000047EB, 0x1D33A9AEB44C6F89), false }; + yield return new object[] { new UInt128(0xB03A000000006CA7, 0xBAF42939DF4E7BA0), new UInt128(0x3012000000008D6C, 0x822E872520B2A4C8), false }; + yield return new object[] { new UInt128(0x1530000000000000, 0x0000000005217896), new UInt128(0x152C000000000000, 0x0000000201131A98), true }; + yield return new object[] { new UInt128(0xB042000000000000, 0x00000000002D231B), new UInt128(0xB040000000000000, 0x0000000001C35F0E), true }; + yield return new object[] { new UInt128(0x300800000041C9DA, 0x0AE55CDA1CDDE16F), new UInt128(0x300600000291E284, 0x6CF5A08520AACE56), true }; + yield return new object[] { new UInt128(0xB024000000000000, 0x00000000007F4CCA), new UInt128(0xB020000000000000, 0x0000000031B9FEE8), true }; + yield return new object[] { new UInt128(0x3050000000000000, 0x000000001572A68A), new UInt128(0x30040058AA3F10A7, 0x9EFB87DE8539A6CD), false }; + yield return new object[] { new UInt128(0xB006000000000000, 0x0000041BDB3C92D4), new UInt128(0xB004000000000000, 0x00002916905DBC48), true }; + yield return new object[] { new UInt128(0x3022000000000002, 0x18ABFEB8FC5D88D3), new UInt128(0x5F400000000005E8, 0x2C8923BCBDB9FD3E), false }; + yield return new object[] { new UInt128(0xB034000000000000, 0x00000000000C3C2B), new UInt128(0xB032000000000000, 0x00000000007A59AE), true }; + yield return new object[] { new UInt128(0xB0040315621B218F, 0xE4D69AF338BACC8F), new UInt128(0xB0021ED5D50F4F9E, 0xF0620D80374BFD96), true }; + yield return new object[] { new UInt128(0xB02E000000000000, 0x000000440D74407B), new UInt128(0xB02A000000000000, 0x00001A954169300C), true }; + yield return new object[] { new UInt128(0x302600393862E7E8, 0x73749E8BE815E588), new UInt128(0x3020DF844259E402, 0xFF8B528295889B40), true }; + yield return new object[] { new UInt128(0x3038000000000000, 0x096997BACFE09F53), new UInt128(0x3032000000000024, 0xC478B1BC056E5C38), true }; + yield return new object[] { new UInt128(0x3040000000000000, 0x00000000F4E84277), new UInt128(0x302E000000029939, 0x82A8BC0D5A4BB09F), false }; + yield return new object[] { new UInt128(0x305C000000000000, 0x000000000000FE36), new UInt128(0x3058000000000000, 0x0000000000634D18), true }; + yield return new object[] { new UInt128(0xB00E000000000000, 0x000000008DD9B33D), new UInt128(0xAFF80000004473DF, 0xDFFE9B3EC48B6A65), false }; + yield return new object[] { new UInt128(0xB012000000000000, 0x000000013AA4FC9F), new UInt128(0x3014000000000000, 0x00003245B752F717), false }; + yield return new object[] { new UInt128(0x3002000000000000, 0x081624F7721E25C3), new UInt128(0x2FFE000000000003, 0x28A670A893C6C02C), true }; + yield return new object[] { new UInt128(0xDDC8000000000000, 0x00855569DF51C986), new UInt128(0x3024000000000000, 0x000000000007C72E), false }; + yield return new object[] { new UInt128(0x1758000000000000, 0x000000041F72D9AA), new UInt128(0x301A0000000061C8, 0xEEFC10C4D3DADBE3), false }; + yield return new object[] { new UInt128(0xB0100003A1125F48, 0x953E3B9231F49265), new UInt128(0xB00C016AEB2D385A, 0x4C4F451B83892F74), true }; + } + + [Theory] + [MemberData(nameof(op_Equality_TestData))] + public static void op_Equality(UInt128 left, UInt128 right, bool expected) + { + Decimal128 l = Unsafe.BitCast(left); + Decimal128 r = Unsafe.BitCast(right); + Assert.Equal(expected, l == r); + Assert.Equal(!expected, l != r); + } + } } 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 index 29dd9d0e933999..acc55b65bb186d 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -752,5 +752,108 @@ public static void op_Subtraction(uint left, uint right, uint expected) Assert.Equal(expected, Unsafe.BitCast(result)); } + public static IEnumerable op_Equality_TestData() + { + yield return new object[] { 0xFC000000U, 0xFC000000U, false }; // NaN == NaN -> false + yield return new object[] { 0xFC000000U, 0x32800001U, false }; // NaN == 1 -> false + yield return new object[] { 0x32800001U, 0xFC000000U, false }; // 1 == NaN -> false + yield return new object[] { 0xFC000000U, 0x78000000U, false }; // NaN == +Inf -> false + yield return new object[] { 0xFE000000U, 0xFE000000U, false }; // sNaN == sNaN -> false + yield return new object[] { 0xFE000000U, 0x32800001U, false }; // sNaN == 1 -> false + yield return new object[] { 0x78000000U, 0x78000000U, true }; // +Inf == +Inf -> true + yield return new object[] { 0xF8000000U, 0xF8000000U, true }; // -Inf == -Inf -> true + yield return new object[] { 0x78000000U, 0xF8000000U, false }; // +Inf == -Inf -> false + yield return new object[] { 0x78000000U, 0x32800001U, false }; // +Inf == 1 -> false + yield return new object[] { 0x78000002U, 0x78000000U, true }; // non-canonical +Inf == +Inf -> true + yield return new object[] { 0xF8000005U, 0xF8000000U, true }; // non-canonical -Inf == -Inf -> true + yield return new object[] { 0x32800000U, 0xB2800000U, true }; // +0 == -0 -> true + yield return new object[] { 0xB2800000U, 0x32800000U, true }; // -0 == +0 -> true + yield return new object[] { 0x32800000U, 0x32800000U, true }; // +0 == +0 -> true + yield return new object[] { 0x32800001U, 0x32800001U, true }; // 1 == 1 -> true + yield return new object[] { 0x32800001U, 0xB2800001U, false }; // 1 == -1 -> false + yield return new object[] { 0xB2800001U, 0x32800001U, false }; // -1 == 1 -> false + yield return new object[] { 0x32800001U, 0x3200000AU, true }; // 1 == 1.0 -> true (cohort) + yield return new object[] { 0x3200000AU, 0x32800001U, true }; // 1.0 == 1 -> true (cohort) + yield return new object[] { 0x31800064U, 0x3200000AU, true }; // 1.00 == 1.0 -> true (cohort) + yield return new object[] { 0x32800064U, 0x33800001U, true }; // 100 == 1e2 -> true (cohort) + yield return new object[] { 0x3280000AU, 0x32800001U, false }; // 10 == 1 -> false + yield return new object[] { 0x5F8F4240U, 0x5F8F4240U, true }; // large == large -> true + yield return new object[] { 0x6CB8967FU, 0x6CB8967FU, true }; // all-nines == all-nines -> true + yield return new object[] { 0x6CB8967FU, 0x6CB8967EU, false }; // all-nines != near -> false + yield return new object[] { 0x32000001U, 0x32000001U, true }; // 0.1 == 0.1 -> true + yield return new object[] { 0xB2000001U, 0x32000001U, false }; // -0.1 == 0.1 -> false + yield return new object[] { 0x35000000U, 0x32800000U, true }; // +0 (exp 5) == +0 -> true (zero cohort) + yield return new object[] { 0x2F2C8C35U, 0xBD800001U, false }; + yield return new object[] { 0x2A80BFA3U, 0x2E80F146U, false }; + yield return new object[] { 0xEDA7AB71U, 0x33800054U, false }; + yield return new object[] { 0xA71C20DCU, 0x2C8C18CBU, false }; + yield return new object[] { 0xB48003B6U, 0xB3817318U, true }; + yield return new object[] { 0xB980002FU, 0x4CC66EF1U, false }; + yield return new object[] { 0x3171BC43U, 0x28803E6EU, false }; + yield return new object[] { 0xB9855F56U, 0xB935B95CU, true }; + yield return new object[] { 0xA80003C8U, 0xA7017A20U, true }; + yield return new object[] { 0x08000005U, 0x070001F4U, true }; + yield return new object[] { 0x3A000116U, 0xBA000010U, false }; + yield return new object[] { 0x2F0ED152U, 0x8E003D25U, false }; + yield return new object[] { 0x2E80ADBAU, 0x29800030U, false }; + yield return new object[] { 0xAD80005DU, 0xAC802454U, true }; + yield return new object[] { 0x33415C3BU, 0xB80006F3U, false }; + yield return new object[] { 0x3B800006U, 0x58802513U, false }; + yield return new object[] { 0x27034AB4U, 0x26A0EB08U, true }; + yield return new object[] { 0x30DA8988U, 0xB187B561U, false }; + yield return new object[] { 0xB308524FU, 0xB2D33716U, true }; + yield return new object[] { 0xB0001165U, 0xAF80ADF2U, true }; + yield return new object[] { 0x168020CDU, 0x158CD014U, true }; + yield return new object[] { 0x278014E4U, 0xAD00004EU, false }; + yield return new object[] { 0xB6F4EDE0U, 0xBB000007U, false }; + yield return new object[] { 0xB2000002U, 0xB10000C8U, true }; + yield return new object[] { 0xA987E16CU, 0xA94ECE38U, true }; + yield return new object[] { 0xA9006A4FU, 0xA8842716U, true }; + yield return new object[] { 0x28000036U, 0x27001518U, true }; + yield return new object[] { 0xAE800293U, 0xAD81016CU, true }; + yield return new object[] { 0x36000006U, 0xCA87FEFCU, false }; + yield return new object[] { 0x33011190U, 0xAC001D41U, false }; + yield return new object[] { 0x5C800033U, 0xAA006FD9U, false }; + yield return new object[] { 0xBA800003U, 0xB980012CU, true }; + yield return new object[] { 0x3A80001BU, 0x39006978U, true }; + yield return new object[] { 0x3409DBACU, 0x33E294B8U, true }; + yield return new object[] { 0x6DD212C6U, 0xB2002198U, false }; + yield return new object[] { 0x4652D371U, 0x2F010631U, false }; + yield return new object[] { 0xB48002F6U, 0xB3812818U, true }; + yield return new object[] { 0xB98003C0U, 0xB80EA600U, true }; + yield return new object[] { 0x49800395U, 0x48816634U, true }; + yield return new object[] { 0x34800368U, 0x2E800341U, false }; + yield return new object[] { 0xB80000CEU, 0xB7005078U, true }; + yield return new object[] { 0x39800003U, 0xB300195BU, false }; + yield return new object[] { 0x33800008U, 0x33000050U, true }; + yield return new object[] { 0x41800251U, 0x4080E7A4U, true }; + yield return new object[] { 0x358001C7U, 0xAF000009U, false }; + yield return new object[] { 0xB2000F58U, 0xB70EF86AU, false }; + yield return new object[] { 0x400B2B74U, 0x3FEFB288U, true }; + yield return new object[] { 0x28B41FEFU, 0xD1000009U, false }; + yield return new object[] { 0xDD8E1313U, 0x30001D6CU, false }; + yield return new object[] { 0xB06FD858U, 0xA888673CU, false }; + yield return new object[] { 0xB80004B6U, 0xB700011AU, false }; + yield return new object[] { 0x2A807402U, 0x3180012EU, false }; + yield return new object[] { 0x180002F8U, 0x17801DB0U, true }; + yield return new object[] { 0x94800019U, 0x938009C4U, true }; + yield return new object[] { 0xB000F1D7U, 0xAF5E77FCU, true }; + yield return new object[] { 0xAA800003U, 0xB7000051U, false }; + yield return new object[] { 0x3180001AU, 0xB60002EEU, false }; + yield return new object[] { 0x07D3490CU, 0x2B80D4D0U, false }; + yield return new object[] { 0xAF000001U, 0xAE000064U, true }; + yield return new object[] { 0xAB8002D0U, 0xB0800377U, false }; + } + + [Theory] + [MemberData(nameof(op_Equality_TestData))] + public static void op_Equality(uint left, uint right, bool expected) + { + Decimal32 l = Unsafe.BitCast(left); + Decimal32 r = Unsafe.BitCast(right); + Assert.Equal(expected, l == r); + Assert.Equal(!expected, l != r); + } + } } 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 index 4d6ce43c0fd982..3bde17e61c528d 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -758,5 +758,108 @@ public static void op_Subtraction(ulong left, ulong right, ulong expected) Assert.Equal(expected, Unsafe.BitCast(result)); } + public static IEnumerable op_Equality_TestData() + { + yield return new object[] { 0xFC000000_00000000UL, 0xFC000000_00000000UL, false }; // NaN == NaN -> false + yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, false }; // NaN == 1 -> false + yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, false }; // 1 == NaN -> false + yield return new object[] { 0xFC000000_00000000UL, 0x78000000_00000000UL, false }; // NaN == +Inf -> false + yield return new object[] { 0xFE000000_00000000UL, 0xFE000000_00000000UL, false }; // sNaN == sNaN -> false + yield return new object[] { 0xFE000000_00000000UL, 0x31C00000_00000001UL, false }; // sNaN == 1 -> false + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, true }; // +Inf == +Inf -> true + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, true }; // -Inf == -Inf -> true + yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, false }; // +Inf == -Inf -> false + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, false }; // +Inf == 1 -> false + yield return new object[] { 0x78000000_00000002UL, 0x78000000_00000000UL, true }; // non-canonical +Inf == +Inf -> true + yield return new object[] { 0xF8000000_00000005UL, 0xF8000000_00000000UL, true }; // non-canonical -Inf == -Inf -> true + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, true }; // +0 == -0 -> true + yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000000UL, true }; // -0 == +0 -> true + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, true }; // +0 == +0 -> true + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000001UL, true }; // 1 == 1 -> true + yield return new object[] { 0x31C00000_00000001UL, 0xB1C00000_00000001UL, false }; // 1 == -1 -> false + yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000001UL, false }; // -1 == 1 -> false + yield return new object[] { 0x31C00000_00000001UL, 0x31A00000_0000000AUL, true }; // 1 == 1.0 -> true (cohort) + yield return new object[] { 0x31A00000_0000000AUL, 0x31C00000_00000001UL, true }; // 1.0 == 1 -> true (cohort) + yield return new object[] { 0x31800000_00000064UL, 0x31A00000_0000000AUL, true }; // 1.00 == 1.0 -> true (cohort) + yield return new object[] { 0x31C00000_00000064UL, 0x32000000_00000001UL, true }; // 100 == 1e2 -> true (cohort) + yield return new object[] { 0x31C00000_0000000AUL, 0x31C00000_00000001UL, false }; // 10 == 1 -> false + yield return new object[] { 0x5FE38D7E_A4C68000UL, 0x5FE38D7E_A4C68000UL, true }; // large == large -> true + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFFUL, true }; // all-nines == all-nines -> true + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFEUL, false }; // all-nines != near -> false + yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_00000001UL, true }; // 0.1 == 0.1 -> true + yield return new object[] { 0xB1A00000_00000001UL, 0x31A00000_00000001UL, false }; // -0.1 == 0.1 -> false + yield return new object[] { 0x32600000_00000000UL, 0x31C00000_00000000UL, true }; // +0 (exp 5) == +0 -> true (zero cohort) + yield return new object[] { 0xB1A008C2_84977883UL, 0x2E800000_0088FC67UL, false }; + yield return new object[] { 0x32E00000_001FC878UL, 0x32C00000_013DD4B0UL, true }; + yield return new object[] { 0x2EA0391E_4F49DE8FUL, 0x9F400000_00001DF5UL, false }; + yield return new object[] { 0xAEC00000_001D0824UL, 0xAE800000_0B572E10UL, true }; + yield return new object[] { 0xAF800000_2C48DC3AUL, 0xAF600001_BAD89A44UL, true }; + yield return new object[] { 0xB2A00000_001E3059UL, 0xB2600000_0BCAE2C4UL, true }; + yield return new object[] { 0x33600000_00000006UL, 0x33200000_00000258UL, true }; + yield return new object[] { 0xB0600000_00010528UL, 0xB0400000_000A3390UL, true }; + yield return new object[] { 0x31A00000_000003D9UL, 0x31400000_000F07A8UL, true }; + yield return new object[] { 0xB1000000_000014DFUL, 0xA2A00000_000493D9UL, false }; + yield return new object[] { 0xB1A2045D_952633E9UL, 0xB1942BA7_D37E071AUL, true }; + yield return new object[] { 0x18C00000_0000AEAEUL, 0x31C00000_3838FC8EUL, false }; + yield return new object[] { 0x33800000_0008C901UL, 0x33E00000_00000006UL, false }; + yield return new object[] { 0xB3000000_000738AFUL, 0xB2C00000_02D2245CUL, true }; + yield return new object[] { 0xB1C03D4D_B6F5E4F8UL, 0xB197F25B_780D70E0UL, true }; + yield return new object[] { 0x30800000_000003D6UL, 0x2F400664_5DF1A27CUL, false }; + yield return new object[] { 0x30A1AE18_98FE6B6EUL, 0xB2400000_00034D87UL, false }; + yield return new object[] { 0x1040016E_281289C6UL, 0x30000000_00000005UL, false }; + yield return new object[] { 0x2F2160D1_63A6598CUL, 0x2F0DC82D_E47F7F78UL, true }; + yield return new object[] { 0x30E00000_00000C4AUL, 0xB2200000_000025CFUL, false }; + yield return new object[] { 0xB2203A12_8BBE5CE9UL, 0xB20244B9_756FA11AUL, true }; + yield return new object[] { 0xB0E00013_1E45601FUL, 0xB0C000BF_2EB5C136UL, true }; + yield return new object[] { 0x2FC00001_6D25307DUL, 0x34000000_00000007UL, false }; + yield return new object[] { 0x2E406792_5AAB2BB2UL, 0x30604337_B2B054C2UL, false }; + yield return new object[] { 0x6B7B4EB4_CA653CC8UL, 0x31600000_0000DC60UL, false }; + yield return new object[] { 0xB3400000_00000160UL, 0x2DC77033_1BF19A20UL, false }; + yield return new object[] { 0xCC001DE4_66BAF9CBUL, 0xAF600000_0023E448UL, false }; + yield return new object[] { 0x2FA00000_00000008UL, 0x33C00000_00000287UL, false }; + yield return new object[] { 0xAFE00000_0025FA34UL, 0xAF604373_91C90C36UL, false }; + yield return new object[] { 0xB3C00000_00000127UL, 0xA21F2123_A600AF10UL, false }; + yield return new object[] { 0xAEC369E6_4F6A2F25UL, 0xEBAA22FF_1A25D772UL, true }; + yield return new object[] { 0x2F800000_00004BEFUL, 0x2F400000_001DA95CUL, true }; + yield return new object[] { 0x21400000_000001A3UL, 0x21200000_0000105EUL, true }; + yield return new object[] { 0x2EC00000_000E96B9UL, 0x2E600000_38FCC2A8UL, true }; + yield return new object[] { 0xB22014D6_070C597FUL, 0x3169A927_D9913FE9UL, false }; + yield return new object[] { 0xAF600000_00000003UL, 0xAF000000_00000BB8UL, true }; + yield return new object[] { 0x30400011_8446412EUL, 0xBDA00000_0054E714UL, false }; + yield return new object[] { 0xAEC00000_00083858UL, 0xAEA00000_00523370UL, true }; + yield return new object[] { 0xC5E3830C_56EBAA74UL, 0xF1731E7B_6534A888UL, true }; + yield return new object[] { 0xD8600000_72FA8C0BUL, 0xD8400004_7DC9786EUL, true }; + yield return new object[] { 0xB1D51B94_C03688D2UL, 0xB0000000_0007D313UL, false }; + yield return new object[] { 0xB1C00000_000002AAUL, 0x2FA00000_05391305UL, false }; + yield return new object[] { 0xB0000000_038654B6UL, 0xAFE00000_233F4F1CUL, true }; + yield return new object[] { 0x2EE00001_EA1CAC6FUL, 0x2E80077A_80019198UL, true }; + yield return new object[] { 0x5660DA0F_B478C74AUL, 0x5648849D_0CB7C8E4UL, true }; + yield return new object[] { 0xAFC00000_0000DB6BUL, 0xAF600000_035919F8UL, true }; + yield return new object[] { 0x2F000000_000001EFUL, 0x2EA00000_00078D98UL, true }; + yield return new object[] { 0x2E62F469_D9824BA0UL, 0x4DF4BD37_15B124F2UL, false }; + yield return new object[] { 0xB1000000_00014173UL, 0xB0A00000_04E7A938UL, true }; + yield return new object[] { 0xAE6004BD_94F49D11UL, 0xAE402F67_D18E22AAUL, true }; + yield return new object[] { 0x33600000_00B80D1DUL, 0x3260573E_35DB7C8EUL, false }; + yield return new object[] { 0x328018AA_7862D5CEUL, 0x22C00000_00000005UL, false }; + yield return new object[] { 0xB0A00000_000004FDUL, 0xB0400000_00137C48UL, true }; + yield return new object[] { 0x31C00000_000BC1FDUL, 0x31800000_0497C6D4UL, true }; + yield return new object[] { 0x58C11505_F3B6E6F2UL, 0x324199F5_8C48AAF6UL, false }; + yield return new object[] { 0xEB90A2E6_7C00A0EDUL, 0xB27AFF08_C857C807UL, false }; + yield return new object[] { 0x30600000_00000008UL, 0x30200000_00000320UL, true }; + yield return new object[] { 0xB2E00000_00000057UL, 0xB2800000_000153D8UL, true }; + yield return new object[] { 0xB1600000_05307358UL, 0x1CA00000_3594B625UL, false }; + yield return new object[] { 0xAA4042B6_7AA7818BUL, 0xAA229B20_CA8B0F6EUL, true }; + } + + [Theory] + [MemberData(nameof(op_Equality_TestData))] + public static void op_Equality(ulong left, ulong right, bool expected) + { + Decimal64 l = Unsafe.BitCast(left); + Decimal64 r = Unsafe.BitCast(right); + Assert.Equal(expected, l == r); + Assert.Equal(!expected, l != r); + } + } } From 6c5b48f0e8f2b2b82af5d75cf401c037b0291b38 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 9 Jul 2026 19:29:46 -0700 Subject: [PATCH 04/19] Implement relational comparisons for Decimal32/64/128 Adds IEEE 754 relational operators (<, >, <=, >=) to Decimal32, Decimal64, and Decimal128. NaN is unordered, so any comparison involving NaN returns false; non-NaN operands use the existing CompareDecimalIeee754 value ordering (+0 == -0, cohort members equal, -Inf < finite < +Inf). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 46 ++++++++ .../src/System/Numerics/Decimal128.cs | 36 ++++++ .../src/System/Numerics/Decimal32.cs | 36 ++++++ .../src/System/Numerics/Decimal64.cs | 36 ++++++ .../System.Runtime/ref/System.Runtime.cs | 12 ++ .../System/Decimal128Tests.cs | 107 ++++++++++++++++++ .../System/Decimal32Tests.cs | 107 ++++++++++++++++++ .../System/Decimal64Tests.cs | 107 ++++++++++++++++++ 8 files changed, 487 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index aa597b6cfd4fd1..e1564ef1e56dd4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -239,6 +239,52 @@ internal static bool EqualsDecimalIeee754(TValue leftDecimalBi return CompareDecimalIeee754(leftDecimalBits, rightDecimalBits) == 0; } + /// + /// Determines the ordering of two IEEE 754 decimal values represented by their raw bit + /// patterns using the semantics required by the relational operators + /// (<, >, <=, >=). + /// + /// + /// A NaN operand is unordered: every relational comparison that involves one is + /// . When neither operand is NaN the values are ordered by numeric + /// value using , so the two zeros + /// compare equal and different members of the same cohort compare equal. + /// + internal static bool LessThanDecimalIeee754(TValue leftDecimalBits, TValue rightDecimalBits) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + return !TDecimal.IsNaN(leftDecimalBits) && !TDecimal.IsNaN(rightDecimalBits) + && CompareDecimalIeee754(leftDecimalBits, rightDecimalBits) < 0; + } + + /// + internal static bool GreaterThanDecimalIeee754(TValue leftDecimalBits, TValue rightDecimalBits) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + return !TDecimal.IsNaN(leftDecimalBits) && !TDecimal.IsNaN(rightDecimalBits) + && CompareDecimalIeee754(leftDecimalBits, rightDecimalBits) > 0; + } + + /// + internal static bool LessThanOrEqualDecimalIeee754(TValue leftDecimalBits, TValue rightDecimalBits) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + return !TDecimal.IsNaN(leftDecimalBits) && !TDecimal.IsNaN(rightDecimalBits) + && CompareDecimalIeee754(leftDecimalBits, rightDecimalBits) <= 0; + } + + /// + internal static bool GreaterThanOrEqualDecimalIeee754(TValue leftDecimalBits, TValue rightDecimalBits) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + return !TDecimal.IsNaN(leftDecimalBits) && !TDecimal.IsNaN(rightDecimalBits) + && CompareDecimalIeee754(leftDecimalBits, rightDecimalBits) >= 0; + } + private static TValue NumberToDecimalIeee754Bits(ref NumberBuffer number) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index 69b96e5f25cdf8..1929ba6c52a4ed 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -301,6 +301,42 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return !(left == right); } + /// Compares two values to determine which is less. + /// The value to compare with . + /// The value to compare with . + /// true if is less than ; otherwise, false. + public static bool operator <(Decimal128 left, Decimal128 right) + { + return Number.LessThanDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); + } + + /// Compares two values to determine which is greater. + /// The value to compare with . + /// The value to compare with . + /// true if is greater than ; otherwise, false. + public static bool operator >(Decimal128 left, Decimal128 right) + { + return Number.GreaterThanDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); + } + + /// Compares two values to determine which is less or equal. + /// The value to compare with . + /// The value to compare with . + /// true if is less than or equal to ; otherwise, false. + public static bool operator <=(Decimal128 left, Decimal128 right) + { + return Number.LessThanOrEqualDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); + } + + /// Compares two values to determine which is greater or equal. + /// The value to compare with . + /// The value to compare with . + /// true if is greater than or equal to ; otherwise, false. + public static bool operator >=(Decimal128 left, Decimal128 right) + { + return Number.GreaterThanOrEqualDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); + } + private static readonly UInt128[] UInt128Powers10 = [ new UInt128(0, 1), diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index 15ff2817498a64..5ebea21ff28a97 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -304,6 +304,42 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return !(left == right); } + /// Compares two values to determine which is less. + /// The value to compare with . + /// The value to compare with . + /// true if is less than ; otherwise, false. + public static bool operator <(Decimal32 left, Decimal32 right) + { + return Number.LessThanDecimalIeee754(left._value, right._value); + } + + /// Compares two values to determine which is greater. + /// The value to compare with . + /// The value to compare with . + /// true if is greater than ; otherwise, false. + public static bool operator >(Decimal32 left, Decimal32 right) + { + return Number.GreaterThanDecimalIeee754(left._value, right._value); + } + + /// Compares two values to determine which is less or equal. + /// The value to compare with . + /// The value to compare with . + /// true if is less than or equal to ; otherwise, false. + public static bool operator <=(Decimal32 left, Decimal32 right) + { + return Number.LessThanOrEqualDecimalIeee754(left._value, right._value); + } + + /// Compares two values to determine which is greater or equal. + /// The value to compare with . + /// The value to compare with . + /// true if is greater than or equal to ; otherwise, false. + public static bool operator >=(Decimal32 left, Decimal32 right) + { + return Number.GreaterThanOrEqualDecimalIeee754(left._value, right._value); + } + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal32NumberBufferLength; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index 0f3b02b80d8f0f..d54a15ffe7607a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -305,6 +305,42 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return !(left == right); } + /// Compares two values to determine which is less. + /// The value to compare with . + /// The value to compare with . + /// true if is less than ; otherwise, false. + public static bool operator <(Decimal64 left, Decimal64 right) + { + return Number.LessThanDecimalIeee754(left._value, right._value); + } + + /// Compares two values to determine which is greater. + /// The value to compare with . + /// The value to compare with . + /// true if is greater than ; otherwise, false. + public static bool operator >(Decimal64 left, Decimal64 right) + { + return Number.GreaterThanDecimalIeee754(left._value, right._value); + } + + /// Compares two values to determine which is less or equal. + /// The value to compare with . + /// The value to compare with . + /// true if is less than or equal to ; otherwise, false. + public static bool operator <=(Decimal64 left, Decimal64 right) + { + return Number.LessThanOrEqualDecimalIeee754(left._value, right._value); + } + + /// Compares two values to determine which is greater or equal. + /// The value to compare with . + /// The value to compare with . + /// true if is greater than or equal to ; otherwise, false. + public static bool operator >=(Decimal64 left, Decimal64 right) + { + return Number.GreaterThanOrEqualDecimalIeee754(left._value, right._value); + } + static int IDecimalIeee754ParseAndFormatInfo.Precision => Precision; static int IDecimalIeee754ParseAndFormatInfo.BufferLength => Number.Decimal64NumberBufferLength; diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index c063b41ad4c6b5..8211e372384df3 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11410,6 +11410,10 @@ public readonly struct Decimal32 public static Decimal32 operator -(Decimal32 left, Decimal32 right) { throw null; } public static bool operator ==(Decimal32 left, Decimal32 right) { throw null; } public static bool operator !=(Decimal32 left, Decimal32 right) { throw null; } + public static bool operator <(Decimal32 left, Decimal32 right) { throw null; } + public static bool operator >(Decimal32 left, Decimal32 right) { throw null; } + public static bool operator <=(Decimal32 left, Decimal32 right) { throw null; } + public static bool operator >=(Decimal32 left, Decimal32 right) { throw null; } } public readonly struct Decimal64 @@ -11460,6 +11464,10 @@ public readonly struct Decimal64 public static Decimal64 operator -(Decimal64 left, Decimal64 right) { throw null; } public static bool operator ==(Decimal64 left, Decimal64 right) { throw null; } public static bool operator !=(Decimal64 left, Decimal64 right) { throw null; } + public static bool operator <(Decimal64 left, Decimal64 right) { throw null; } + public static bool operator >(Decimal64 left, Decimal64 right) { throw null; } + public static bool operator <=(Decimal64 left, Decimal64 right) { throw null; } + public static bool operator >=(Decimal64 left, Decimal64 right) { throw null; } } public readonly struct Decimal128 @@ -11510,6 +11518,10 @@ public readonly struct Decimal128 public static Decimal128 operator -(Decimal128 left, Decimal128 right) { throw null; } public static bool operator ==(Decimal128 left, Decimal128 right) { throw null; } public static bool operator !=(Decimal128 left, Decimal128 right) { throw null; } + public static bool operator <(Decimal128 left, Decimal128 right) { throw null; } + public static bool operator >(Decimal128 left, Decimal128 right) { throw null; } + public static bool operator <=(Decimal128 left, Decimal128 right) { throw null; } + public static bool operator >=(Decimal128 left, Decimal128 right) { throw null; } } public readonly partial struct BFloat16 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators 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 index 8174370e1c398c..e0c5fdeace4289 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -855,5 +855,112 @@ public static void op_Equality(UInt128 left, UInt128 right, bool expected) Assert.Equal(!expected, l != r); } + public static IEnumerable op_Comparison_TestData() + { + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), false, false, false, false }; // NaN vs 1 -> unordered + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), false, false, false, false }; // 1 vs NaN -> unordered + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000), false, false, false, false }; // NaN vs NaN -> unordered + yield return new object[] { new UInt128(0xFE00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), false, false, false, false }; // sNaN vs 1 -> unordered + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), false, false, false, false }; // NaN vs +Inf -> unordered + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), false, true, false, true }; // +Inf vs 1 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000), true, false, true, false }; // 1 vs +Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), true, false, true, false }; // -Inf vs 1 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000000), false, true, false, true }; // 1 vs -Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), true, false, true, false }; // -Inf vs +Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), false, false, true, true }; // +Inf vs +Inf (equal) + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), false, false, true, true }; // -Inf vs -Inf (equal) + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x7800000000000000, 0x0000000000000000), false, false, true, true }; // non-canonical +Inf vs +Inf (equal) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), false, false, true, true }; // +0 vs -0 (equal) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), false, false, true, true }; // -0 vs +0 (equal) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), true, false, true, false }; // 0 vs 1 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000), true, false, true, false }; // -1 vs 0 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002), true, false, true, false }; // 1 vs 2 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001), false, true, false, true }; // 2 vs 1 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000001), true, false, true, false }; // -1 vs 1 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000001), false, true, false, true }; // 1 vs -1 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000002), false, true, false, true }; // -1 vs -2 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x000000000000000A), false, false, true, true }; // 1 vs 1.0 (cohort equal) + yield return new object[] { new UInt128(0x303E000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x0000000000000001), false, false, true, true }; // 1.0 vs 1 (cohort equal) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000064), new UInt128(0x3044000000000000, 0x0000000000000001), false, false, true, true }; // 100 vs 1e2 (cohort equal) + yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x0000000000000001), false, true, false, true }; // 10 vs 1 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000002), true, false, true, false }; // 0.1 vs 0.2 + yield return new object[] { new UInt128(0x3034000000000000, 0x000000000098967F), new UInt128(0x3034000000000000, 0x000000000098967E), false, true, false, true }; // close values + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE), false, true, false, true }; // all-nines vs near + yield return new object[] { new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE04EE2D6D415B, 0x85ACEF8100000000), false, true, false, true }; // large magnitudes + yield return new object[] { new UInt128(0xDFFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), true, false, true, false }; // -large vs +large + yield return new object[] { new UInt128(0x2FFE289FF22ABDCC, 0xB2563F2BA4A187B8), new UInt128(0x3032000000002117, 0xA8AB1AC92768DFCC), true, false, true, false }; + yield return new object[] { new UInt128(0xB02A000000000000, 0x0000000003E03EDC), new UInt128(0x3034000000000000, 0x5A65FF2A16E631E0), true, false, true, false }; + yield return new object[] { new UInt128(0xB032000000000A9F, 0x4028DF83A3FBDB37), new UInt128(0x3054000000000000, 0x000000002DF6D94F), true, false, true, false }; + yield return new object[] { new UInt128(0xB03E000000000000, 0x00000001A8B80287), new UInt128(0xB03C000000000000, 0x0000001097301946), false, false, true, true }; + yield return new object[] { new UInt128(0x305C000000000000, 0x00000000003673BF), new UInt128(0x3028000000000000, 0x00000007C09C1527), false, true, false, true }; + yield return new object[] { new UInt128(0xAFFE00000000019B, 0x341186BF3F1DA827), new UInt128(0xB03E000000000000, 0x0005DCB5F7ED6B34), false, true, false, true }; + yield return new object[] { new UInt128(0x300C000000000000, 0x000179DCB4BE3FF7), new UInt128(0xB012000000000000, 0x0000000002BA9121), false, true, false, true }; + yield return new object[] { new UInt128(0x0D04000000000000, 0x001C8C6C22C81050), new UInt128(0x304E000000000000, 0x0000000000001216), true, false, true, false }; + yield return new object[] { new UInt128(0xB008000000000000, 0x0000B26F886C7769), new UInt128(0xB01A000000000000, 0x0000000000000034), true, false, true, false }; + yield return new object[] { new UInt128(0x3036000000000000, 0x0000042026A018FC), new UInt128(0xB020000000000000, 0x000000000000CCF5), false, true, false, true }; + yield return new object[] { new UInt128(0x8462000000000000, 0x00000000EAC878D0), new UInt128(0xB044000000000000, 0x0766DC043F42224C), false, true, false, true }; + yield return new object[] { new UInt128(0xB02400012DE50EC6, 0xBBBA78C47B74A0E0), new UInt128(0x302200000000012F, 0x1F7FF8F741AE5567), true, false, true, false }; + yield return new object[] { new UInt128(0x3038000000000000, 0x0000000000000057), new UInt128(0xB03C000000000000, 0x000000110FC8CD30), false, true, false, true }; + yield return new object[] { new UInt128(0x3030000000000000, 0x17E95893CB288412), new UInt128(0x004A00000000001C, 0x8D893304C576A4DE), false, true, false, true }; + yield return new object[] { new UInt128(0xB030000000000000, 0x0000000002CEAE78), new UInt128(0x2FEA538FE26D7D1B, 0xD726661AA55DF804), true, false, true, false }; + yield return new object[] { new UInt128(0xDF8E0000001FA271, 0xF338CEEFDDFFE3EA), new UInt128(0xB040000000000000, 0x0B6848AD2793AAE4), true, false, true, false }; + yield return new object[] { new UInt128(0x9244000000000000, 0x000004781F6323F3), new UInt128(0x30220000000009CA, 0xBA5D608B3420D686), true, false, true, false }; + yield return new object[] { new UInt128(0x3034000000000000, 0x000000A44C289F0F), new UInt128(0xB01C000000000000, 0x0000000000023EFC), false, true, false, true }; + yield return new object[] { new UInt128(0xB02600000FD5551A, 0x94F228ED26B2F66C), new UInt128(0xB020003DD9546FD5, 0xD1EFDE5F2B1295E0), false, false, true, true }; + yield return new object[] { new UInt128(0x9D10000000000000, 0x7E51E27048F9F398), new UInt128(0xB04C000000000000, 0x000000000000ECB9), false, true, false, true }; + yield return new object[] { new UInt128(0xB044000000000000, 0x00000000023F5ABE), new UInt128(0xB0280000082CA932, 0x5E4B1837B8D3E89E), false, true, false, true }; + yield return new object[] { new UInt128(0x301C000000000007, 0x0964D911A0BBD8F4), new UInt128(0x305E000000000000, 0x000000000000A71E), true, false, true, false }; + yield return new object[] { new UInt128(0x3026000000000000, 0x000000795A3C873F), new UInt128(0x300800000041C9DA, 0x0AE55CDA1CDDE16F), false, true, false, true }; + yield return new object[] { new UInt128(0xB650000000D0224F, 0xFD6F80D1270DDE07), new UInt128(0x301800007AEF1309, 0xE6818A3F216A34FF), true, false, true, false }; + yield return new object[] { new UInt128(0x3006000A9EFB87DE, 0x8539A6CD7BF62403), new UInt128(0xB006000000000000, 0x0000041BDB3C92D4), false, true, false, true }; + yield return new object[] { new UInt128(0x301C000000000315, 0xFC5D88D34D795D12), new UInt128(0x5F400000000005E8, 0x2C8923BCBDB9FD3E), true, false, true, false }; + yield return new object[] { new UInt128(0xB034000000000000, 0x00000000000C3C2B), new UInt128(0xB02E000000000000, 0x00001C5D7E76C0D0), false, true, false, true }; + yield return new object[] { new UInt128(0x3028000000000000, 0x00000000155EEEE3), new UInt128(0xC63C000000000000, 0x0000000000000016), false, true, false, true }; + yield return new object[] { new UInt128(0x3010125D89F7C323, 0x37C02ED0435BE309), new UInt128(0xB012000000000000, 0x000000000009536A), false, true, false, true }; + yield return new object[] { new UInt128(0x3038000000000000, 0x096997BACFE09F53), new UInt128(0x3040000000000000, 0x00000000F4E84277), false, true, false, true }; + yield return new object[] { new UInt128(0x3042000000000000, 0x0001692E610A8ED8), new UInt128(0x303E000000000000, 0x008D161DE81FCC60), false, false, true, true }; + yield return new object[] { new UInt128(0x2A96000000000000, 0x0000000000398706), new UInt128(0xB042000000000000, 0x000000000000A932), false, true, false, true }; + yield return new object[] { new UInt128(0x303E000000000000, 0x000002503A034940), new UInt128(0xB026000000000000, 0x0000000000587FAD), false, true, false, true }; + yield return new object[] { new UInt128(0x3014000000000000, 0x0721E25C46337978), new UInt128(0xAFFA4821D7034A02, 0xB8ACC25DF6F4CF86), false, true, false, true }; + yield return new object[] { new UInt128(0xB044000000000000, 0x00E1766220EE98D3), new UInt128(0xB042000000000000, 0x08CE9FD54951F83E), false, false, true, true }; + yield return new object[] { new UInt128(0x8840000000000531, 0x049414502C46E143), new UInt128(0xB03C000000000000, 0x00000000005D61A7), false, true, false, true }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x0000031F76932CF2), new UInt128(0xB00A00000022492E, 0xC74AC5D28F7C2B9E), false, true, false, true }; + yield return new object[] { new UInt128(0x301A000000000000, 0x000000696040784C), new UInt128(0x3014000000000000, 0x00019B9FFBD5E8E0), false, false, true, true }; + yield return new object[] { new UInt128(0xB0040000000022D8, 0x1DA76F3AF6929B87), new UInt128(0x30240000F05E5FAC, 0x7D46A35E53517B13), true, false, true, false }; + yield return new object[] { new UInt128(0x300200000220753C, 0x8CA178C667F5ED50), new UInt128(0xB036000000000000, 0x0000000000010401), false, true, false, true }; + yield return new object[] { new UInt128(0xB050000000000000, 0x00000000001B96F0), new UInt128(0xC4DA000000000000, 0x002332BFAE0ABB23), false, true, false, true }; + yield return new object[] { new UInt128(0xCD32000000000000, 0x00000062ADC4C04D), new UInt128(0xD02A000000000000, 0x00000000000014F6), false, true, false, true }; + yield return new object[] { new UInt128(0xB040000000000000, 0x00017F274F195D94), new UInt128(0x2FFA000000000000, 0x022F53F33F61565B), true, false, true, false }; + yield return new object[] { new UInt128(0xB044000000000000, 0x0000000000000046), new UInt128(0x2FFA000000000000, 0x000ED37C5E4A6F24), true, false, true, false }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x000000148D08DAD9), new UInt128(0x2FFA000000000000, 0x1FBD55F356C051B7), true, false, true, false }; + yield return new object[] { new UInt128(0x304A000000000000, 0x00000003CD7FC505), new UInt128(0x3046000000000000, 0x0000017C45E8F5F4), false, false, true, true }; + yield return new object[] { new UInt128(0x46D4000000000000, 0x0000000014D6A755), new UInt128(0x2FE6000001F28868, 0xEDF78B1F515A1856), false, true, false, true }; + yield return new object[] { new UInt128(0xB02C000956B6687B, 0x36CEC180F9999542), new UInt128(0x2FFC001E242C92DD, 0xC816BBACBE275F77), true, false, true, false }; + yield return new object[] { new UInt128(0x2FEC879CB69E66F9, 0x5CF87F593585DB26), new UInt128(0x40CE000000000000, 0x0000000FC627A4F5), true, false, true, false }; + yield return new object[] { new UInt128(0xB02C000000000000, 0x0020BB76731068C7), new UInt128(0x3020000000007B27, 0xC72B0E98A7E6BDA9), true, false, true, false }; + yield return new object[] { new UInt128(0x823000000000002A, 0xA9473055B2F65315), new UInt128(0x300800363B6D91C5, 0x9ECF8577D4668EFE), true, false, true, false }; + yield return new object[] { new UInt128(0xAFE400013C7FC403, 0xE8DFAB61815BDD62), new UInt128(0xAFFC000000000000, 0x0AE997D86F01F581), false, true, false, true }; + yield return new object[] { new UInt128(0xB018000000120CBF, 0x23F4CD9940BD2B9A), new UInt128(0xDFCC005830554042, 0x149E930F0D337664), false, true, false, true }; + yield return new object[] { new UInt128(0x300E000000000000, 0x0112E59C50FC31A5), new UInt128(0xB0180000000007E1, 0xF8A6349B7A2C9257), false, true, false, true }; + yield return new object[] { new UInt128(0x300E000000000000, 0x0000001427C344A7), new UInt128(0x300C000000000000, 0x000000C98DA0AE86), false, false, true, true }; + yield return new object[] { new UInt128(0xD270000000000000, 0x00000815ABADD29E), new UInt128(0xD26C000000000000, 0x000328770FE645B8), false, false, true, true }; + yield return new object[] { new UInt128(0xAFE8000619369D80, 0xCF00A8D8FFBDB9B9), new UInt128(0xAFE6003CFC222708, 0x16069879FD69413A), false, false, true, true }; + yield return new object[] { new UInt128(0x3052000000000000, 0x0000001410258185), new UInt128(0x3050000000000000, 0x000000C8A1770F32), false, false, true, true }; + yield return new object[] { new UInt128(0x2FF6000000000000, 0x08D41FB605523418), new UInt128(0x304A000000000000, 0x00003055B53A1C43), true, false, true, false }; + yield return new object[] { new UInt128(0xB036000000000000, 0x0000000000000012), new UInt128(0xB00E000000000061, 0xC2DBBF40A99DB992), false, true, false, true }; + } + + [Theory] + [MemberData(nameof(op_Comparison_TestData))] + public static void op_Comparison(UInt128 left, UInt128 right, bool lessThan, bool greaterThan, bool lessThanOrEqual, bool greaterThanOrEqual) + { + Decimal128 l = Unsafe.BitCast(left); + Decimal128 r = Unsafe.BitCast(right); + Assert.Equal(lessThan, l < r); + Assert.Equal(greaterThan, l > r); + Assert.Equal(lessThanOrEqual, l <= r); + Assert.Equal(greaterThanOrEqual, l >= r); + } + } } 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 index acc55b65bb186d..646f8d1fa89e33 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -855,5 +855,112 @@ public static void op_Equality(uint left, uint right, bool expected) Assert.Equal(!expected, l != r); } + public static IEnumerable op_Comparison_TestData() + { + yield return new object[] { 0xFC000000U, 0x32800001U, false, false, false, false }; // NaN vs 1 -> unordered + yield return new object[] { 0x32800001U, 0xFC000000U, false, false, false, false }; // 1 vs NaN -> unordered + yield return new object[] { 0xFC000000U, 0xFC000000U, false, false, false, false }; // NaN vs NaN -> unordered + yield return new object[] { 0xFE000000U, 0x32800001U, false, false, false, false }; // sNaN vs 1 -> unordered + yield return new object[] { 0xFC000000U, 0x78000000U, false, false, false, false }; // NaN vs +Inf -> unordered + yield return new object[] { 0x78000000U, 0x32800001U, false, true, false, true }; // +Inf vs 1 + yield return new object[] { 0x32800001U, 0x78000000U, true, false, true, false }; // 1 vs +Inf + yield return new object[] { 0xF8000000U, 0x32800001U, true, false, true, false }; // -Inf vs 1 + yield return new object[] { 0x32800001U, 0xF8000000U, false, true, false, true }; // 1 vs -Inf + yield return new object[] { 0xF8000000U, 0x78000000U, true, false, true, false }; // -Inf vs +Inf + yield return new object[] { 0x78000000U, 0x78000000U, false, false, true, true }; // +Inf vs +Inf (equal) + yield return new object[] { 0xF8000000U, 0xF8000000U, false, false, true, true }; // -Inf vs -Inf (equal) + yield return new object[] { 0x78000002U, 0x78000000U, false, false, true, true }; // non-canonical +Inf vs +Inf (equal) + yield return new object[] { 0x32800000U, 0xB2800000U, false, false, true, true }; // +0 vs -0 (equal) + yield return new object[] { 0xB2800000U, 0x32800000U, false, false, true, true }; // -0 vs +0 (equal) + yield return new object[] { 0x32800000U, 0x32800001U, true, false, true, false }; // 0 vs 1 + yield return new object[] { 0xB2800001U, 0x32800000U, true, false, true, false }; // -1 vs 0 + yield return new object[] { 0x32800001U, 0x32800002U, true, false, true, false }; // 1 vs 2 + yield return new object[] { 0x32800002U, 0x32800001U, false, true, false, true }; // 2 vs 1 + yield return new object[] { 0xB2800001U, 0x32800001U, true, false, true, false }; // -1 vs 1 + yield return new object[] { 0x32800001U, 0xB2800001U, false, true, false, true }; // 1 vs -1 + yield return new object[] { 0xB2800001U, 0xB2800002U, false, true, false, true }; // -1 vs -2 + yield return new object[] { 0x32800001U, 0x3200000AU, false, false, true, true }; // 1 vs 1.0 (cohort equal) + yield return new object[] { 0x3200000AU, 0x32800001U, false, false, true, true }; // 1.0 vs 1 (cohort equal) + yield return new object[] { 0x32800064U, 0x33800001U, false, false, true, true }; // 100 vs 1e2 (cohort equal) + yield return new object[] { 0x3280000AU, 0x32800001U, false, true, false, true }; // 10 vs 1 + yield return new object[] { 0x32000001U, 0x32000002U, true, false, true, false }; // 0.1 vs 0.2 + yield return new object[] { 0x6BF8967FU, 0x6BF8967EU, false, true, false, true }; // close values + yield return new object[] { 0x6CB8967FU, 0x6CB8967EU, false, true, false, true }; // all-nines vs near + yield return new object[] { 0x5F8F4240U, 0x5F8186A0U, false, true, false, true }; // large magnitudes + yield return new object[] { 0xDF8F4240U, 0x5F8F4240U, true, false, true, false }; // -large vs +large + yield return new object[] { 0x2F2C8C35U, 0xBD800001U, false, true, false, true }; + yield return new object[] { 0x2A80BFA3U, 0x2E80F146U, true, false, true, false }; + yield return new object[] { 0xEDA7AB71U, 0x33800054U, true, false, true, false }; + yield return new object[] { 0xA71C20DCU, 0x2C8C18CBU, true, false, true, false }; + yield return new object[] { 0xB48003B6U, 0xB3817318U, false, false, true, true }; + yield return new object[] { 0xB980002FU, 0x4CC66EF1U, true, false, true, false }; + yield return new object[] { 0x3171BC43U, 0x28803E6EU, false, true, false, true }; + yield return new object[] { 0xB9855F56U, 0xB935B95CU, false, false, true, true }; + yield return new object[] { 0xA80003C8U, 0xA7017A20U, false, false, true, true }; + yield return new object[] { 0x08000005U, 0x39801340U, true, false, true, false }; + yield return new object[] { 0xCA000C33U, 0x29000379U, true, false, true, false }; + yield return new object[] { 0x33000001U, 0x31800012U, false, true, false, true }; + yield return new object[] { 0x36000003U, 0x340C7831U, true, false, true, false }; + yield return new object[] { 0xAE808F1FU, 0x3782B3D3U, true, false, true, false }; + yield return new object[] { 0xB885B1AEU, 0xB838F0CCU, false, false, true, true }; + yield return new object[] { 0x2A80014CU, 0x29800003U, false, true, false, true }; + yield return new object[] { 0x3280024CU, 0x3A083505U, true, false, true, false }; + yield return new object[] { 0x3685C05CU, 0x31000002U, false, true, false, true }; + yield return new object[] { 0x300090CAU, 0x6543374AU, false, true, false, true }; + yield return new object[] { 0xB4800019U, 0xAD847994U, true, false, true, false }; + yield return new object[] { 0xAD00004EU, 0x33001329U, true, false, true, false }; + yield return new object[] { 0xB98025B8U, 0xB88EBBE0U, false, false, true, true }; + yield return new object[] { 0xAF8E5E1FU, 0xEBCFAD36U, false, false, true, true }; + yield return new object[] { 0xA987E16CU, 0xA94ECE38U, false, false, true, true }; + yield return new object[] { 0xA9006A4FU, 0x30000002U, true, false, true, false }; + yield return new object[] { 0xB2950EABU, 0x27802665U, true, false, true, false }; + yield return new object[] { 0xBA800028U, 0xAA05B8ADU, true, false, true, false }; + yield return new object[] { 0xB8013769U, 0xB779A504U, false, false, true, true }; + yield return new object[] { 0xB680005AU, 0xB6000384U, false, false, true, true }; + yield return new object[] { 0x2B000471U, 0xAE800008U, false, true, false, true }; + yield return new object[] { 0xAE00009FU, 0xDD9AF653U, false, true, false, true }; + yield return new object[] { 0x3A800006U, 0x3A00003CU, false, false, true, true }; + yield return new object[] { 0xB2002198U, 0xB9000008U, false, true, false, true }; + yield return new object[] { 0xA8000116U, 0x2E80D42FU, true, false, true, false }; + yield return new object[] { 0x8C801CF2U, 0x8B711150U, false, false, true, true }; + yield return new object[] { 0x49800395U, 0x48816634U, false, false, true, true }; + yield return new object[] { 0x34800368U, 0x2E800341U, false, true, false, true }; + yield return new object[] { 0xB80000CEU, 0xB7005078U, false, false, true, true }; + yield return new object[] { 0x39800003U, 0xB300195BU, false, true, false, true }; + yield return new object[] { 0x33800008U, 0xB7000028U, false, true, false, true }; + yield return new object[] { 0xBF033649U, 0x35F38F19U, true, false, true, false }; + yield return new object[] { 0x58095C61U, 0xB0BD637DU, false, true, false, true }; + yield return new object[] { 0x6BB0F7CAU, 0x36013C4DU, true, false, true, false }; + yield return new object[] { 0x530001F8U, 0xD1000009U, false, true, false, true }; + yield return new object[] { 0xDD8E1313U, 0x30001D6CU, true, false, true, false }; + yield return new object[] { 0xB06FD858U, 0xA888673CU, true, false, true, false }; + yield return new object[] { 0xB80004B6U, 0xB700011AU, true, false, true, false }; + yield return new object[] { 0x2A807402U, 0x3180012EU, true, false, true, false }; + yield return new object[] { 0x180002F8U, 0x2E000002U, true, false, true, false }; + yield return new object[] { 0xCC801631U, 0xB180033BU, true, false, true, false }; + yield return new object[] { 0x0D80010EU, 0xBB800002U, false, true, false, true }; + yield return new object[] { 0x310000DCU, 0xB60002EEU, false, true, false, true }; + yield return new object[] { 0x07D3490CU, 0x2B0EF5D7U, true, false, true, false }; + yield return new object[] { 0xAF000001U, 0xAE000064U, false, false, true, true }; + yield return new object[] { 0xAB8002D0U, 0xB0800377U, false, true, false, true }; + yield return new object[] { 0xB6802125U, 0xEDA8258FU, false, true, false, true }; + yield return new object[] { 0xAC000001U, 0xAB0DD4BDU, false, true, false, true }; + yield return new object[] { 0x34011190U, 0x2A00159EU, false, true, false, true }; + yield return new object[] { 0xAD01FF37U, 0x37837CF9U, true, false, true, false }; + yield return new object[] { 0xB58001C6U, 0x2D000A26U, true, false, true, false }; + } + + [Theory] + [MemberData(nameof(op_Comparison_TestData))] + public static void op_Comparison(uint left, uint right, bool lessThan, bool greaterThan, bool lessThanOrEqual, bool greaterThanOrEqual) + { + Decimal32 l = Unsafe.BitCast(left); + Decimal32 r = Unsafe.BitCast(right); + Assert.Equal(lessThan, l < r); + Assert.Equal(greaterThan, l > r); + Assert.Equal(lessThanOrEqual, l <= r); + Assert.Equal(greaterThanOrEqual, l >= r); + } + } } 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 index 3bde17e61c528d..5c992b9cace633 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -861,5 +861,112 @@ public static void op_Equality(ulong left, ulong right, bool expected) Assert.Equal(!expected, l != r); } + public static IEnumerable op_Comparison_TestData() + { + yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, false, false, false, false }; // NaN vs 1 -> unordered + yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, false, false, false, false }; // 1 vs NaN -> unordered + yield return new object[] { 0xFC000000_00000000UL, 0xFC000000_00000000UL, false, false, false, false }; // NaN vs NaN -> unordered + yield return new object[] { 0xFE000000_00000000UL, 0x31C00000_00000001UL, false, false, false, false }; // sNaN vs 1 -> unordered + yield return new object[] { 0xFC000000_00000000UL, 0x78000000_00000000UL, false, false, false, false }; // NaN vs +Inf -> unordered + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, false, true, false, true }; // +Inf vs 1 + yield return new object[] { 0x31C00000_00000001UL, 0x78000000_00000000UL, true, false, true, false }; // 1 vs +Inf + yield return new object[] { 0xF8000000_00000000UL, 0x31C00000_00000001UL, true, false, true, false }; // -Inf vs 1 + yield return new object[] { 0x31C00000_00000001UL, 0xF8000000_00000000UL, false, true, false, true }; // 1 vs -Inf + yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, true, false, true, false }; // -Inf vs +Inf + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, false, false, true, true }; // +Inf vs +Inf (equal) + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, false, false, true, true }; // -Inf vs -Inf (equal) + yield return new object[] { 0x78000000_00000002UL, 0x78000000_00000000UL, false, false, true, true }; // non-canonical +Inf vs +Inf (equal) + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, false, false, true, true }; // +0 vs -0 (equal) + yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000000UL, false, false, true, true }; // -0 vs +0 (equal) + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000001UL, true, false, true, false }; // 0 vs 1 + yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000000UL, true, false, true, false }; // -1 vs 0 + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL, true, false, true, false }; // 1 vs 2 + yield return new object[] { 0x31C00000_00000002UL, 0x31C00000_00000001UL, false, true, false, true }; // 2 vs 1 + yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000001UL, true, false, true, false }; // -1 vs 1 + yield return new object[] { 0x31C00000_00000001UL, 0xB1C00000_00000001UL, false, true, false, true }; // 1 vs -1 + yield return new object[] { 0xB1C00000_00000001UL, 0xB1C00000_00000002UL, false, true, false, true }; // -1 vs -2 + yield return new object[] { 0x31C00000_00000001UL, 0x31A00000_0000000AUL, false, false, true, true }; // 1 vs 1.0 (cohort equal) + yield return new object[] { 0x31A00000_0000000AUL, 0x31C00000_00000001UL, false, false, true, true }; // 1.0 vs 1 (cohort equal) + yield return new object[] { 0x31C00000_00000064UL, 0x32000000_00000001UL, false, false, true, true }; // 100 vs 1e2 (cohort equal) + yield return new object[] { 0x31C00000_0000000AUL, 0x31C00000_00000001UL, false, true, false, true }; // 10 vs 1 + yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_00000002UL, true, false, true, false }; // 0.1 vs 0.2 + yield return new object[] { 0x31000000_0098967FUL, 0x31000000_0098967EUL, false, true, false, true }; // close values + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFEUL, false, true, false, true }; // all-nines vs near + yield return new object[] { 0x5FE38D7E_A4C68000UL, 0x5FE05AF3_107A4000UL, false, true, false, true }; // large magnitudes + yield return new object[] { 0xDFE38D7E_A4C68000UL, 0x5FE38D7E_A4C68000UL, true, false, true, false }; // -large vs +large + yield return new object[] { 0xB2400000_000014EDUL, 0x31C00000_002A5135UL, true, false, true, false }; + yield return new object[] { 0x2F000063_A23BC84AUL, 0x2E60007D_E4F31691UL, false, true, false, true }; + yield return new object[] { 0x328000E1_F407BFE1UL, 0xB1000000_000014DFUL, false, true, false, true }; + yield return new object[] { 0xA2200001_2C5C9406UL, 0xB1A2045D_952633E9UL, false, true, false, true }; + yield return new object[] { 0x2F600004_B07A97FAUL, 0x31C00000_3838FC8EUL, true, false, true, false }; + yield return new object[] { 0x33800000_0008C901UL, 0x33E00000_00000006UL, false, true, false, true }; + yield return new object[] { 0xB3000000_000738AFUL, 0xEC2B57F6_AEDFA2F6UL, true, false, true, false }; + yield return new object[] { 0xB1A2CD7D_00E19992UL, 0xB1800000_003142FFUL, true, false, true, false }; + yield return new object[] { 0x2F4004A5_E17CD24DUL, 0x30A1AE18_98FE6B6EUL, true, false, true, false }; + yield return new object[] { 0xB2400000_00034D87UL, 0xB0A00000_000000C7UL, true, false, true, false }; + yield return new object[] { 0x30000000_00000005UL, 0x2F2160D1_63A6598CUL, true, false, true, false }; + yield return new object[] { 0xAD988DFF_DCC311D5UL, 0xB0000691_D863649AUL, false, true, false, true }; + yield return new object[] { 0x30627FCF_B909892FUL, 0x3058FE1D_3A5F5BD6UL, false, false, true, true }; + yield return new object[] { 0xB3600000_0000230CUL, 0xB3400000_00015E78UL, false, false, true, true }; + yield return new object[] { 0x2FC00001_6D25307DUL, 0x34000000_00000007UL, true, false, true, false }; + yield return new object[] { 0x2E406792_5AAB2BB2UL, 0x30604337_B2B054C2UL, true, false, true, false }; + yield return new object[] { 0x6B7B4EB4_CA653CC8UL, 0xAF400000_0000008FUL, false, true, false, true }; + yield return new object[] { 0xB3C00000_00000001UL, 0x2DC77033_1BF19A20UL, true, false, true, false }; + yield return new object[] { 0xCC001DE4_66BAF9CBUL, 0xAF600000_0023E448UL, true, false, true, false }; + yield return new object[] { 0x2FA00000_00000008UL, 0x33C00000_00000287UL, true, false, true, false }; + yield return new object[] { 0xAFE00000_0025FA34UL, 0xAF604373_91C90C36UL, false, true, false, true }; + yield return new object[] { 0xB3C00000_00000127UL, 0xA21F2123_A600AF10UL, true, false, true, false }; + yield return new object[] { 0xAEC369E6_4F6A2F25UL, 0xCE000000_2C155ADEUL, false, true, false, true }; + yield return new object[] { 0x2E400000_00000CC1UL, 0x2EE00000_006399AAUL, true, false, true, false }; + yield return new object[] { 0xAE2000E8_7DB2F031UL, 0xB22014D6_070C597FUL, false, true, false, true }; + yield return new object[] { 0x14200000_1E148863UL, 0x13E0000B_C00546ACUL, false, false, true, true }; + yield return new object[] { 0xB3E00000_00000004UL, 0xB3800000_00000FA0UL, false, false, true, true }; + yield return new object[] { 0x30600000_00000192UL, 0x2DB14774_E29D36EFUL, false, true, false, true }; + yield return new object[] { 0xBA404693_038D50F9UL, 0xB040009D_38654929UL, true, false, true, false }; + yield return new object[] { 0xB4000000_00000009UL, 0x30C00000_000001EBUL, true, false, true, false }; + yield return new object[] { 0x32800029_6783D33DUL, 0xB1C00000_000002AAUL, false, true, false, true }; + yield return new object[] { 0x2FA00000_05391305UL, 0x2F400014_66F24B88UL, false, false, true, true }; + yield return new object[] { 0xB3800000_00006C63UL, 0xB3600000_00043BDEUL, false, false, true, true }; + yield return new object[] { 0x2EE00001_EA1CAC6FUL, 0x5660DA0F_B478C74AUL, true, false, true, false }; + yield return new object[] { 0xB7C00000_03E9AF8BUL, 0x328000C6_0667F8FFUL, true, false, true, false }; + yield return new object[] { 0x437376D5_46AB54AAUL, 0x2E62F469_D9824BA0UL, false, true, false, true }; + yield return new object[] { 0x09200001_FEBFFAC0UL, 0xB1200000_70F0F514UL, false, true, false, true }; + yield return new object[] { 0x2F600000_0000018DUL, 0x2F200000_00009B14UL, false, false, true, true }; + yield return new object[] { 0x31C00000_0093B3B1UL, 0x32200000_03650DEAUL, true, false, true, false }; + yield return new object[] { 0x32C00000_0072C76FUL, 0x22C00000_00000005UL, false, true, false, true }; + yield return new object[] { 0xB0A00000_000004FDUL, 0x31C00000_000BC1FDUL, true, false, true, false }; + yield return new object[] { 0xB2000000_03255E52UL, 0xB1A0000C_49F87050UL, false, false, true, true }; + yield return new object[] { 0xB2600000_00012FDAUL, 0x31200000_007A3E03UL, true, false, true, false }; + yield return new object[] { 0xB18041EB_25020508UL, 0x2FE00040_A971BF9CUL, true, false, true, false }; + yield return new object[] { 0x33800000_0000B296UL, 0x30E00000_0000034FUL, false, true, false, true }; + yield return new object[] { 0xB1600000_05307358UL, 0x1CA00000_3594B625UL, true, false, true, false }; + yield return new object[] { 0xAA4042B6_7AA7818BUL, 0xAA229B20_CA8B0F6EUL, false, false, true, true }; + yield return new object[] { 0xB3000000_38B20AF3UL, 0xAE800003_9C24FB35UL, true, false, true, false }; + yield return new object[] { 0xB0A00000_2F4E733BUL, 0x31800000_00013536UL, true, false, true, false }; + yield return new object[] { 0xB062FAF8_87469CE0UL, 0x30200000_00000045UL, true, false, true, false }; + yield return new object[] { 0xB0200000_000E661FUL, 0xAFE3054C_CC540881UL, false, true, false, true }; + yield return new object[] { 0xA9800000_01ECEE93UL, 0x25200000_0412E566UL, true, false, true, false }; + yield return new object[] { 0xAF800002_24F7B6CAUL, 0x2E800000_2F26BC28UL, true, false, true, false }; + yield return new object[] { 0x18000001_F084CCA2UL, 0xADE00A2A_BF8CFA22UL, false, true, false, true }; + yield return new object[] { 0xB2E00000_1892B9E7UL, 0x31726749_CD4380CAUL, true, false, true, false }; + yield return new object[] { 0xB120000E_041E212AUL, 0x34000000_00000336UL, true, false, true, false }; + yield return new object[] { 0x32000000_00003529UL, 0x32A00000_25E00AC4UL, true, false, true, false }; + yield return new object[] { 0x2FC00000_02D2E250UL, 0x30A00000_00000001UL, false, true, false, true }; + yield return new object[] { 0xB0200000_045F9E3BUL, 0xC8A00002_48B351F0UL, false, true, false, true }; + yield return new object[] { 0xAE305989_14E871DFUL, 0xB1400000_00000035UL, false, true, false, true }; + } + + [Theory] + [MemberData(nameof(op_Comparison_TestData))] + public static void op_Comparison(ulong left, ulong right, bool lessThan, bool greaterThan, bool lessThanOrEqual, bool greaterThanOrEqual) + { + Decimal64 l = Unsafe.BitCast(left); + Decimal64 r = Unsafe.BitCast(right); + Assert.Equal(lessThan, l < r); + Assert.Equal(greaterThan, l > r); + Assert.Equal(lessThanOrEqual, l <= r); + Assert.Equal(greaterThanOrEqual, l >= r); + } + } } From a7261e15860821855cdbb74bb1403fcbc8b9e6bb Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 9 Jul 2026 20:04:37 -0700 Subject: [PATCH 05/19] Implement multiplication for Decimal32/64/128 Adds `operator *` for the IEEE 754 decimal types. Coefficients are multiplied exactly in base 10 (a product can span up to twice the format precision, exceeding UInt128 for Decimal128, so digit spans are used rather than the underlying integer width). The product exponent is the sum of the operand exponents, which is also the IEEE 754 preferred exponent since the product's trailing zeros are retained; the exact digit string is then rounded through the shared NumberToDecimalIeee754Bits pipeline. Also fixes two latent issues in the shared encoding pipeline that only a multiply product can reach: * NumberToDecimalIeee754Bits underflow branch assumed at most Precision significant digits. A wide product whose quantum falls just below the minimum adjusted exponent can carry more, so the digit count is now capped to Precision (a single correct rounding of the full product; a no-op for <=Precision inputs from parse/add/sub). * DecimalIeee754FiniteNumberBinaryEncoding clamped a zero coefficient's exponent to MaxExponent instead of MaxAdjustedExponent. A zero result with a preferred exponent above the maximum quantum (e.g. zero times a large-exponent value) overflowed the biased exponent field. Add/sub never reach this because their zero-path exponents are bounded by the operand quanta. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 161 +++++++++++++++++- .../src/System/Numerics/Decimal128.cs | 10 ++ .../src/System/Numerics/Decimal32.cs | 9 + .../src/System/Numerics/Decimal64.cs | 9 + .../System.Runtime/ref/System.Runtime.cs | 3 + .../System/Decimal128Tests.cs | 116 +++++++++++++ .../System/Decimal32Tests.cs | 116 +++++++++++++ .../System/Decimal64Tests.cs | 116 +++++++++++++ 8 files changed, 537 insertions(+), 3 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index e1564ef1e56dd4..6da2b1f199af96 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -323,7 +323,17 @@ private static TValue NumberToDecimalIeee754Bits(ref NumberBuf else if (numberDigitsRemove < number.DigitsCount) { int numberDigitsRemain = number.DigitsCount - numberDigitsRemove; - Debug.Assert(numberDigitsRemain <= TDecimal.Precision); + + if (numberDigitsRemain > TDecimal.Precision) + { + // The coefficient still exceeds the format precision after shifting to the + // minimum quantum, so the value is actually in the normal range rather than + // subnormal. Round the full (exact) digit string to the format precision in a + // single step; this avoids a double rounding and yields a quantum that is at or + // above the minimum adjusted exponent. + numberDigitsRemain = TDecimal.Precision; + } + return DecimalIeee754Rounding(ref number, numberDigitsRemain); } else @@ -410,9 +420,9 @@ private static TValue DecimalIeee754FiniteNumberBinaryEncoding { exponent = TDecimal.MinAdjustedExponent; } - else if (exponent > TDecimal.MaxExponent) + else if (exponent > TDecimal.MaxAdjustedExponent) { - exponent = TDecimal.MaxExponent; + exponent = TDecimal.MaxAdjustedExponent; } } @@ -761,6 +771,109 @@ static int WriteDigits(TValue value, Span destination, int length) } } + /// + /// Multiplies two IEEE 754 decimal values represented by their raw bit patterns and returns the + /// bit pattern of the correctly rounded (round-to-nearest, ties-to-even) product. + /// + /// + /// The operands are decoded and their coefficients multiplied exactly in base 10 (the product can + /// require up to twice the format precision, so it is computed on digit spans rather than the + /// underlying integer width). The exact product exponent is the sum of the operand exponents, which + /// is also the IEEE 754 preferred exponent because trailing zeros of the product are retained. The + /// exact digit string is then fed into the shared rounding path. This mirrors the mathematical + /// behavior of the Intel reference implementation while remaining independent of the integer width. + /// + internal static TValue MultiplyDecimalIeee754(TValue left, TValue right) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `bid32_mul`, `bid64_mul`, and `bid128_mul` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (TDecimal.IsNaN(left) || TDecimal.IsNaN(right)) + { + return TDecimal.NaN; + } + + // The sign of a product is always the exclusive-or of the operand signs, including zeros. + bool resultSign = TDecimal.IsNegative(left) ^ TDecimal.IsNegative(right); + + bool leftInfinity = TDecimal.IsInfinity(left); + bool rightInfinity = TDecimal.IsInfinity(right); + + if (leftInfinity || rightInfinity) + { + // Infinity multiplied by zero is invalid (NaN); any other product involving an infinity is + // an infinity carrying the exclusive-or sign. + bool otherZero = (leftInfinity && !rightInfinity && TValue.IsZero(UnpackDecimalIeee754(right).Significand)) + || (rightInfinity && !leftInfinity && TValue.IsZero(UnpackDecimalIeee754(left).Significand)); + + if (otherZero) + { + return TDecimal.NaN; + } + + return resultSign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(left); + DecodedDecimalIeee754 b = UnpackDecimalIeee754(right); + + int productExponent = a.UnbiasedExponent + b.UnbiasedExponent; + + if (TValue.IsZero(a.Significand) || TValue.IsZero(b.Significand)) + { + // Zero times a finite value is zero. The preferred exponent is the sum of the operand + // exponents (clamped to the representable range by the encoder). + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, TValue.Zero, productExponent); + } + + int capacity = (2 * TDecimal.Precision) + 4; + Span leftDigits = stackalloc byte[capacity]; + Span rightDigits = stackalloc byte[capacity]; + Span productDigits = stackalloc byte[capacity]; + + int leftLength = WriteDigits(a.Significand, leftDigits, TDecimal.CountDigits(a.Significand)); + int rightLength = WriteDigits(b.Significand, rightDigits, TDecimal.CountDigits(b.Significand)); + + int productLength = BigMultiply(leftDigits.Slice(0, leftLength), rightDigits.Slice(0, rightLength), productDigits); + + int start = 0; + while (start < productLength && productDigits[start] == (byte)'0') + { + start++; + } + int digitsCount = productLength - start; + Debug.Assert(digitsCount > 0); + + Span numberDigits = stackalloc byte[capacity + 2]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, numberDigits); + productDigits.Slice(start, digitsCount).CopyTo(number.Digits); + number.Digits[digitsCount] = 0; + number.DigitsCount = digitsCount; + number.Scale = digitsCount + productExponent; + number.IsNegative = resultSign; + number.CheckConsistency(); + + return NumberToDecimalIeee754Bits(ref number); + + static int WriteDigits(TValue value, Span destination, int length) + { + TValue ten = TValue.CreateTruncating(10); + + for (int i = length - 1; i >= 0; i--) + { + (value, TValue remainder) = TValue.DivRem(value, ten); + destination[i] = (byte)('0' + int.CreateTruncating(remainder)); + } + + return length; + } + } + private static ReadOnlySpan TrimLeadingZeros(ReadOnlySpan digits) { int start = 0; @@ -784,6 +897,48 @@ private static int BigCompare(ReadOnlySpan left, ReadOnlySpan right) return left.SequenceCompareTo(right); } + // Multiplies two base-10 digit spans (big-endian, '0'..'9') using schoolbook multiplication. + // The product is written to `result` (which must have room for `left.Length + right.Length` + // digits) and may carry a single leading zero, which the caller trims. + private static int BigMultiply(ReadOnlySpan left, ReadOnlySpan right, Span result) + { + left = TrimLeadingZeros(left); + right = TrimLeadingZeros(right); + + int leftLength = left.Length; + int rightLength = right.Length; + + if (leftLength == 0 || rightLength == 0) + { + return 0; + } + + int length = leftLength + rightLength; + + for (int i = 0; i < length; i++) + { + result[i] = (byte)'0'; + } + + for (int i = leftLength - 1; i >= 0; i--) + { + int carry = 0; + int leftDigit = left[i] - '0'; + + for (int j = rightLength - 1; j >= 0; j--) + { + int index = i + j + 1; + int product = (leftDigit * (right[j] - '0')) + (result[index] - '0') + carry; + result[index] = (byte)('0' + (product % 10)); + carry = product / 10; + } + + result[i] = (byte)('0' + carry); + } + + return length; + } + private static int BigAdd(ReadOnlySpan left, ReadOnlySpan right, Span result) { int leftLength = left.Length; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index 1929ba6c52a4ed..eb66ff675881a8 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -283,6 +283,16 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return new Decimal128(result); } + /// Multiplies two values together to compute their product. + /// The value which multiplies. + /// The value which multiplies . + /// The product of and . + public static Decimal128 operator *(Decimal128 left, Decimal128 right) + { + UInt128 result = Number.MultiplyDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); + return new Decimal128(result); + } + /// Compares two values to determine equality. /// The value to compare with . /// The value to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index 5ebea21ff28a97..aa45e60b4219c3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -286,6 +286,15 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return new Decimal32(Number.AddDecimalIeee754(left._value, right._value ^ SignMask)); } + /// Multiplies two values together to compute their product. + /// The value which multiplies. + /// The value which multiplies . + /// The product of and . + public static Decimal32 operator *(Decimal32 left, Decimal32 right) + { + return new Decimal32(Number.MultiplyDecimalIeee754(left._value, right._value)); + } + /// Compares two values to determine equality. /// The value to compare with . /// The value to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index d54a15ffe7607a..ee180fd8b6bba3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -287,6 +287,15 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return new Decimal64(Number.AddDecimalIeee754(left._value, right._value ^ SignMask)); } + /// Multiplies two values together to compute their product. + /// The value which multiplies. + /// The value which multiplies . + /// The product of and . + public static Decimal64 operator *(Decimal64 left, Decimal64 right) + { + return new Decimal64(Number.MultiplyDecimalIeee754(left._value, right._value)); + } + /// Compares two values to determine equality. /// The value to compare with . /// The value to compare with . diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 8211e372384df3..694921042c7667 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11408,6 +11408,7 @@ public readonly struct Decimal32 public static Decimal32 operator -(Decimal32 value) { throw null; } public static Decimal32 operator +(Decimal32 left, Decimal32 right) { throw null; } public static Decimal32 operator -(Decimal32 left, Decimal32 right) { throw null; } + public static Decimal32 operator *(Decimal32 left, Decimal32 right) { throw null; } public static bool operator ==(Decimal32 left, Decimal32 right) { throw null; } public static bool operator !=(Decimal32 left, Decimal32 right) { throw null; } public static bool operator <(Decimal32 left, Decimal32 right) { throw null; } @@ -11462,6 +11463,7 @@ public readonly struct Decimal64 public static Decimal64 operator -(Decimal64 value) { throw null; } public static Decimal64 operator +(Decimal64 left, Decimal64 right) { throw null; } public static Decimal64 operator -(Decimal64 left, Decimal64 right) { throw null; } + public static Decimal64 operator *(Decimal64 left, Decimal64 right) { throw null; } public static bool operator ==(Decimal64 left, Decimal64 right) { throw null; } public static bool operator !=(Decimal64 left, Decimal64 right) { throw null; } public static bool operator <(Decimal64 left, Decimal64 right) { throw null; } @@ -11516,6 +11518,7 @@ public readonly struct Decimal128 public static Decimal128 operator -(Decimal128 value) { throw null; } public static Decimal128 operator +(Decimal128 left, Decimal128 right) { throw null; } public static Decimal128 operator -(Decimal128 left, Decimal128 right) { throw null; } + public static Decimal128 operator *(Decimal128 left, Decimal128 right) { throw null; } public static bool operator ==(Decimal128 left, Decimal128 right) { throw null; } public static bool operator !=(Decimal128 left, Decimal128 right) { throw null; } public static bool operator <(Decimal128 left, Decimal128 right) { throw null; } 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 index e0c5fdeace4289..4eefaa8751a0b3 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -962,5 +962,121 @@ public static void op_Comparison(UInt128 left, UInt128 right, bool lessThan, boo Assert.Equal(greaterThanOrEqual, l >= r); } + public static IEnumerable op_Multiply_TestData() + { + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN * 1 -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // 1 * NaN -> NaN + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN * +Inf -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf * +0 -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +0 * +Inf -> NaN + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf * +0 -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf * -0 -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf * 1 -> +Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000000) }; // +Inf * -1 -> -Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf * 2 -> -Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000002), new UInt128(0x7800000000000000, 0x0000000000000000) }; // -Inf * -2 -> +Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf * +Inf -> +Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // +Inf * -Inf -> -Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // -Inf * -Inf -> +Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf * 1 -> canonical +Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000005), new UInt128(0xF800000000000000, 0x0000000000000000) }; // 1 * non-canonical -Inf -> canonical -Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 * +0 -> +0 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -0 * -0 -> +0 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000) }; // +0 * -0 -> -0 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000) }; // 1 * -0 -> -0 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000) }; // -1 * +0 -> -0 + yield return new object[] { new UInt128(0x304A000000000000, 0x0000000000000000), new UInt128(0x3046000000000000, 0x0000000000000000), new UInt128(0x3050000000000000, 0x0000000000000000) }; // 0e5 * 0e3 -> 0e8 (preferred exp) + yield return new object[] { new UInt128(0x3036000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x000000000000000C), new UInt128(0x3036000000000000, 0x0000000000000000) }; // 0e-5 * 12 -> 0e-5 (preferred exp) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x000000000000000A) }; // 2 * 5 -> 10 (exp 0, trailing zero retained) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x303E000000000000, 0x0000000000000005), new UInt128(0x303E000000000000, 0x000000000000000A) }; // 2 * 0.5 -> 1.0 + yield return new object[] { new UInt128(0x303E000000000000, 0x000000000000000F), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x303E000000000000, 0x000000000000001E) }; // 1.5 * 2 -> 3.0 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303C000000000000, 0x0000000000000001) }; // 0.1 * 0.1 -> 0.01 + yield return new object[] { new UInt128(0x303E000000000000, 0x000000000000000C), new UInt128(0x303E000000000000, 0x000000000000000C), new UInt128(0x303C000000000000, 0x0000000000000090) }; // 1.2 * 1.2 -> 1.44 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000003), new UInt128(0x3040000000000000, 0x0000000000000007), new UInt128(0xB040000000000000, 0x0000000000000015) }; // -3 * 7 -> -21 + yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000C), new UInt128(0x3040000000000000, 0x000000000000000C), new UInt128(0x3040000000000000, 0x0000000000000090) }; // 12 * 12 -> 144 + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3085ED09BEAD87C0, 0x378D8E63FFFFFFFE) }; // all-nines squared (round) + yield return new object[] { new UInt128(0x2FFE41BD085B676E, 0xF657240D55555555), new UInt128(0x2FFE57A6B5CF3493, 0xF31EDABC71C71C71), new UInt128(0x2FFE74DE47BEF0C5, 0x442923A5ED097B41) }; // full-precision * full-precision (round) + yield return new object[] { new UInt128(0x3040314DC6448D93, 0x38C15B0A00000000), new UInt128(0x3040314DC6448D93, 0x38C15B0A00000000), new UInt128(0x3082314DC6448D93, 0x38C15B0A00000000) }; // 10^(P-1) * 10^(P-1) (trailing zeros beyond precision) + yield return new object[] { new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // huge * huge -> +Inf (overflow) + yield return new object[] { new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -huge * huge -> -Inf (overflow) + yield return new object[] { new UInt128(0x0040000000000000, 0x0000000000000001), new UInt128(0x0040000000000000, 0x0000000000000001), new UInt128(0x0000000000000000, 0x0000000000000000) }; // tiny * tiny -> underflow + yield return new object[] { new UInt128(0x0042000000000000, 0x0000000000000001), new UInt128(0x3036000000000000, 0x0000000000000001), new UInt128(0x0038000000000000, 0x0000000000000001) }; // small * small (subnormal-ish) + yield return new object[] { new UInt128(0x181DED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x181DED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x003DED09BEAD87C0, 0x378D8E63FFFFFFFE) }; // wide product just below min quantum (normal after single rounding) + yield return new object[] { new UInt128(0x17FBED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x17FDED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x0000007E37BE2022, 0xC0914B2680000000) }; // wide product deep in the subnormal range + yield return new object[] { new UInt128(0x0041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x0041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x0000000000000000, 0x0000000000000000) }; // wide product underflow to zero/epsilon + yield return new object[] { new UInt128(0x5FFE000000000000, 0x0000000000000000), new UInt128(0x5FFE000000000000, 0x0000000000000005), new UInt128(0x5FFE000000000000, 0x0000000000000000) }; // zero * finite, preferred exponent far above max quantum (clamped) + yield return new object[] { new UInt128(0xDFFE000000000000, 0x0000000000000000), new UInt128(0x5FFE000000000000, 0x0000000000000005), new UInt128(0xDFFE000000000000, 0x0000000000000000) }; // -zero * finite, preferred exponent clamped (sign preserved) + yield return new object[] { new UInt128(0x3042000000000000, 0x0000000000000000), new UInt128(0x5FFE000000000000, 0x0000000000000005), new UInt128(0x5FFE000000000000, 0x0000000000000000) }; // zero * finite, preferred exponent one above max quantum (clamped) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x5FFE000000000000, 0x0000000000000005), new UInt128(0x5FFE000000000000, 0x0000000000000000) }; // zero * finite, preferred exponent exactly at max quantum (no clamp) + yield return new object[] { new UInt128(0x300C000000000000, 0x0386DF690C6EF5BE), new UInt128(0xB018000000000000, 0x00022FCFF31AA0B9), new UInt128(0xAFE407B65F3E6C11, 0x97E447D9A512564E) }; + yield return new object[] { new UInt128(0x3016000000000000, 0x00000000025C222D), new UInt128(0xAFEE0000105989B6, 0x14E871DF7FB4E7FF), new UInt128(0xAFC862C63C9DC466, 0xEEA20CD21BC0A580) }; + yield return new object[] { new UInt128(0xAFE4004BC3F60664, 0xA2D28316972FB16C), new UInt128(0x3024000000000000, 0x336683344D857468), new UInt128(0xAFE86D9DF48A21EF, 0xA1A8BB1C44333B9B) }; + yield return new object[] { new UInt128(0xB026000000000000, 0x0000000000000009), new UInt128(0xD36C269369438B15, 0x4317B7B7F941F96B), new UInt128(0x53535B2EB35FE3BF, 0x5BD57577C351C4C3) }; + yield return new object[] { new UInt128(0xB030000000000000, 0x00000006C1105459), new UInt128(0xB01E100AA3FBDB37, 0x590C5A8BE72C6FA0), new UInt128(0x3021D1590788E3E1, 0xE8D4D9049C75FA8E) }; + yield return new object[] { new UInt128(0x4592000000000000, 0x0125EB92A4B6C61E), new UInt128(0xB03E000000000000, 0x00000001A8B80287), new UInt128(0xC590000001E7A16F, 0x190B7B1E5F7EB5D2) }; + yield return new object[] { new UInt128(0xB018000000000000, 0x000016E8FC21185B), new UInt128(0xB052000000000000, 0x00000000EFF4031C), new UInt128(0x302A000000001579, 0x59BA71B822A3BAF4) }; + yield return new object[] { new UInt128(0x3020000000000000, 0x0000009AA0434574), new UInt128(0x3048000000000000, 0x0000000000001ED7), new UInt128(0x3028000000000000, 0x0012A0A47AA2EC6C) }; + yield return new object[] { new UInt128(0xB058000000000000, 0x00000000000003DF), new UInt128(0x300C000000000000, 0x000179DCB4BE3FF7), new UInt128(0xB024000000000000, 0x05B6BD5FAC799D29) }; + yield return new object[] { new UInt128(0x9EE405C739C662F4, 0x5752242E1DB1EC20), new UInt128(0x3004000000000000, 0x0000BD66F041ECD5), new UInt128(0x9EC27854D8F6C60F, 0x6949AA79C4640D7C) }; + yield return new object[] { new UInt128(0xAFE6000119C10D0F, 0xC98968772E7E6EF7), new UInt128(0xB0020000000002DA, 0x7B7ACAB57EE654B5), new UInt128(0x2FCC39EEA2704D09, 0x18025365B6B0A095) }; + yield return new object[] { new UInt128(0xB00E000000000000, 0x00000000004E6990), new UInt128(0x3852000000000000, 0x000000000000031D), new UInt128(0xB820000000000000, 0x00000000F41EA550) }; + yield return new object[] { new UInt128(0x3008000000000000, 0x000000014204609D), new UInt128(0xB020000000000000, 0x000000000000CCF5), new UInt128(0xAFE8000000000000, 0x000101CFAB2D9241) }; + yield return new object[] { new UInt128(0x8462000000000000, 0x00000000EAC878D0), new UInt128(0xB034000000000000, 0x0000000000000110), new UInt128(0x0456000000000000, 0x000000F975005D00) }; + yield return new object[] { new UInt128(0xB02400012DE50EC6, 0xBBBA78C47B74A0E0), new UInt128(0x302200000000012F, 0x1F7FF8F741AE5567), new UInt128(0xB0290194CFA11AFA, 0x7F52F9881B76CEAC) }; + yield return new object[] { new UInt128(0x3038000000000000, 0x0000000000000057), new UInt128(0xB03C000000000000, 0x000000110FC8CD30), new UInt128(0xB034000000000000, 0x000005CC5D3DBB50) }; + yield return new object[] { new UInt128(0x3030000000000000, 0x17E95893CB288412), new UInt128(0xC6E2000000000000, 0x52ABCE0A9DE14E35), new UInt128(0xC6DA329B18E3DD29, 0xFE1B6F4B3BC384C8) }; + yield return new object[] { new UInt128(0x0870000000000000, 0x000000DD250AAC57), new UInt128(0xB00000000000092C, 0x7754F6DC4C1D7D08), new UInt128(0x8832CADF15269ABF, 0x15479B9D126F162C) }; + yield return new object[] { new UInt128(0xB036000000000000, 0x000000130041C7D7), new UInt128(0xB016000000000000, 0x002262E6BEFE8EBF), new UInt128(0x300C0000028D5FF6, 0x213D8511873F5B69) }; + yield return new object[] { new UInt128(0x3036000000000000, 0x00000001B6848ADB), new UInt128(0x301E000000000000, 0x00000000000A8089), new UInt128(0x3014000000000000, 0x0011FD5A5EE9CF33) }; + yield return new object[] { new UInt128(0xC3AE000000000000, 0x04CF4E8A302A8BDC), new UInt128(0xAFFA00000000002E, 0x3420D6865B6A6E4E), new UInt128(0x437291A3FAE81214, 0x39CAD6C0814BA89C) }; + yield return new object[] { new UInt128(0xD258000000000000, 0x000000000091C9EF), new UInt128(0x300A000000000000, 0x0000167364209915), new UInt128(0xD22200000000000C, 0xC91145CEC785679B) }; + yield return new object[] { new UInt128(0x301C000000000000, 0x000000000439E2E9), new UInt128(0xB014000000000032, 0x708772352DE98CF2), new UInt128(0xAFF00000D529E131, 0x40A8C42321E0EC42) }; + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000064), new UInt128(0xB03A000000000000, 0x00003B2E17E3E88B), new UInt128(0x303A000000000000, 0x00171E015506D64C) }; + yield return new object[] { new UInt128(0xB044000000000000, 0x00000000023F5ABE), new UInt128(0xB03A000000006CA7, 0xBAF42939DF4E7BA0), new UInt128(0x303E00F43340869F, 0x30B182BD001600C0) }; + yield return new object[] { new UInt128(0xC6E6000000000000, 0x00000001B8D3E89E), new UInt128(0x301C000000000007, 0x0964D911A0BBD8F4), new UInt128(0xC6C2000C1DF87810, 0x66C04A42A1A90698) }; + yield return new object[] { new UInt128(0x305E000000000000, 0x000000000000A71E), new UInt128(0x3026000000000000, 0x000000795A3C873F), new UInt128(0x3044000000000000, 0x004F38160F51F262) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x0000000005C867C0), new UInt128(0x302E000000000000, 0x0000000000572AE6), new UInt128(0xB008000000000000, 0x0001F8135552B680) }; + yield return new object[] { new UInt128(0xB650000000D0224F, 0xFD6F80D1270DDE07), new UInt128(0xB010000000000000, 0x0000021670F50113), new UInt128(0x362B1CC33D11D8E8, 0x94F27B03FC307F88) }; + yield return new object[] { new UInt128(0xB02C000000000006, 0x909F1381718DE51E), new UInt128(0xB01E00000000002D, 0xCE88CD810A4F6601), new UInt128(0x301A3273BF0089BE, 0x1BF92234A5C2B6C3) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x00000000000018F2), new UInt128(0x303C000000000000, 0x000000000046CBDD), new UInt128(0xB016000000000000, 0x00000006E6096EEA) }; + yield return new object[] { new UInt128(0x5F400000000005E8, 0x2C8923BCBDB9FD3E), new UInt128(0xB034000000000000, 0x00000000000C3C2B), new UInt128(0xDF34000048457EDD, 0x4C4221E35480116A) }; + yield return new object[] { new UInt128(0xD95C0000000051B5, 0x62B3BD63621B218F), new UInt128(0xB01E000000000000, 0x0000000000000000), new UInt128(0x593A000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xB0280000000006F8, 0x435BE30981A1CB95), new UInt128(0x54FE000000000000, 0x00015E8772D1DBC6), new UInt128(0xD4EE3E8B2A46B875, 0x59B96E9577A90909) }; + yield return new object[] { new UInt128(0x5C72000037D35F7D, 0xF4E8427727658DEF), new UInt128(0x3042000000000000, 0x0001692E610A8ED8), new UInt128(0x5C87524835F626B9, 0xBFC37B63EBCC127C) }; + yield return new object[] { new UInt128(0x305C000000000000, 0x000000000000FE36), new UInt128(0x3014000000000000, 0x000000D1D108B375), new UInt128(0x3030000000000000, 0x00D059A8B9E3F0AE) }; + yield return new object[] { new UInt128(0xB042000000000000, 0x000000000000A932), new UInt128(0x303E000000000000, 0x000002503A034940), new UInt128(0xB040000000000000, 0x018769F77FFF8E80) }; + yield return new object[] { new UInt128(0xB00E000000000000, 0x587FADD919E02966), new UInt128(0x3014000000000000, 0x0721E25C46337978), new UInt128(0xAFE8A196A59F5076, 0x7DBBCD2DBAAE1214) }; + yield return new object[] { new UInt128(0xAFFA4821D7034A02, 0xB8ACC25DF6F4CF86), new UInt128(0xB044000000000000, 0x00E1766220EE98D3), new UInt128(0x301FC9C3F9A45974, 0xEDCE05153B8B06AB) }; + yield return new object[] { new UInt128(0x3024000000000000, 0x000000000007C72E), new UInt128(0x1758000000000000, 0x000000041F72D9AA), new UInt128(0x173C000000000000, 0x00201153E17C428C) }; + yield return new object[] { new UInt128(0x301A0000000061C8, 0xEEFC10C4D3DADBE3), new UInt128(0xB0100003A1125F48, 0x953E3B9231F49265), new UInt128(0xB0124176B78A0C87, 0x2603353137A081D0) }; + yield return new object[] { new UInt128(0x301000004BB9B532, 0xE5CB89F41922BD32), new UInt128(0xDE64000000000000, 0x0000CCBC550D3404), new UInt128(0xDE47041BDCF88924, 0x3862B211EE51A96E) }; + yield return new object[] { new UInt128(0xB024000000000000, 0x000000000116C396), new UInt128(0x3028000000005351, 0x729900C58D0BD408), new UInt128(0xB00C005ABA1A4E2A, 0x87C41908E82054B0) }; + yield return new object[] { new UInt128(0x300200000220753C, 0x8CA178C667F5ED50), new UInt128(0xD382000000000000, 0x0A69C7380DBC5B46), new UInt128(0xD35AF381555D3933, 0xA140AE1DFC803624) }; + yield return new object[] { new UInt128(0x300C045E788BFD23, 0xFDC576761B96F039), new UInt128(0xB01A000000000000, 0x0AE0ABB2D104776E), new UInt128(0xB0075673706B72C0, 0xB77852E5EF9F5ABF) }; + yield return new object[] { new UInt128(0xCD32000000000000, 0x00000062ADC4C04D), new UInt128(0xB040000000000000, 0xD235D4832BF52B34), new UInt128(0x4D320051074B49F1, 0xC6902C3B50F4FEA4) }; + yield return new object[] { new UInt128(0xB040000000000000, 0x00017F274F195D94), new UInt128(0x2FFA000000000000, 0x022F53F33F61565B), new UInt128(0xAFFA0345247B8835, 0x00326DAB8487FB9C) }; + yield return new object[] { new UInt128(0xB044000000000000, 0x0000000000000046), new UInt128(0x300A00000005E4A6, 0x3DBCC6C875ABB5B6), new UInt128(0xB00E0000019C8574, 0xE19E5AD02CF3AFC4) }; + yield return new object[] { new UInt128(0x3066000000000000, 0x0000000000000015), new UInt128(0x3014000000008E7B, 0xA150E858F5A1264C), new UInt128(0x303A0000000BB024, 0x3BA30F4C2638243C) }; + yield return new object[] { new UInt128(0x21A60000000048E8, 0xDC7FE586D3707752), new UInt128(0xB024000000000023, 0x2E0D31C20845EFD5), new UInt128(0xA1A06E29F26C86CE, 0x9B6E54620B903F9B) }; + yield return new object[] { new UInt128(0xAFF403A1D73F5ACD, 0x96A762C7BAA70840), new UInt128(0x3042000000000000, 0x000000019FF4DE39), new UInt128(0xB006FD79CCA0632A, 0x1CF2022B79252C0A) }; + yield return new object[] { new UInt128(0x303C000000000000, 0x00113925FCD09CDA), new UInt128(0x3000000000007140, 0x43CE4908B69E66F9), new UInt128(0x30087FD4C96325E5, 0x83F8D90398C3CF2C) }; + yield return new object[] { new UInt128(0x3004000000000000, 0x00003C65D7E4774B), new UInt128(0x301825B99EFDAE69, 0x303766A98D4C34B5), new UInt128(0x2FF6FA8670030F05, 0xFFC0D3475619D770) }; + yield return new object[] { new UInt128(0xB0220000004AE762, 0xF65E345A6F0E69F9), new UInt128(0x301A000000000000, 0x00000000000DC8E9), new UInt128(0xAFFC040888F86C54, 0x7D3BF0CDB88DFBA1) }; + yield return new object[] { new UInt128(0x3052000000000000, 0x000000000000002A), new UInt128(0x300000001C3D6C1F, 0x26F3E82E5A4692F0), new UInt128(0x30120004A213BD1C, 0x6404179ACF941B60) }; + yield return new object[] { new UInt128(0xB048000000000000, 0x0000000000000280), new UInt128(0xAFFC000000000000, 0x0AE997D86F01F581), new UInt128(0x300400000000001B, 0x47FB9D1584E5C280) }; + yield return new object[] { new UInt128(0xB018000000120CBF, 0x23F4CD9940BD2B9A), new UInt128(0xDFCC005830554042, 0x149E930F0D337664), new UInt128(0x5FD24B2B8DCF44E4, 0xF9328AD297D2D135) }; + yield return new object[] { new UInt128(0x300E000000000000, 0x0112E59C50FC31A5), new UInt128(0x2FEC16CD5779F833, 0xFD5BB8EB3F0C6FD3), new UInt128(0x2FDAB06F5E049A66, 0x55F91067350E15D9) }; + yield return new object[] { new UInt128(0x3D3E000000000000, 0x00000000E4F7F4D6), new UInt128(0xD270000000000000, 0x00000815ABADD29E), new UInt128(0xDF6E00000000073B, 0x218ADE0A1E7EA814) }; + yield return new object[] { new UInt128(0x3034000000000000, 0x00181648272743F6), new UInt128(0x569600000002FBFF, 0x4C2693B7C7D3A38C), new UInt128(0x5698789A280DEAE7, 0x8FCFA699FD055CA3) }; + } + + [Theory] + [MemberData(nameof(op_Multiply_TestData))] + public static void op_Multiply(UInt128 left, UInt128 right, UInt128 expected) + { + Decimal128 result = Unsafe.BitCast(left) * Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } 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 index 646f8d1fa89e33..c84a8960b8f383 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -962,5 +962,121 @@ public static void op_Comparison(uint left, uint right, bool lessThan, bool grea Assert.Equal(greaterThanOrEqual, l >= r); } + public static IEnumerable op_Multiply_TestData() + { + yield return new object[] { 0xFC000000U, 0x32800001U, 0xFC000000U }; // NaN * 1 -> NaN + yield return new object[] { 0x32800001U, 0xFC000000U, 0xFC000000U }; // 1 * NaN -> NaN + yield return new object[] { 0xFC000000U, 0x78000000U, 0xFC000000U }; // NaN * +Inf -> NaN + yield return new object[] { 0x78000000U, 0x32800000U, 0xFC000000U }; // +Inf * +0 -> NaN + yield return new object[] { 0x32800000U, 0x78000000U, 0xFC000000U }; // +0 * +Inf -> NaN + yield return new object[] { 0xF8000000U, 0x32800000U, 0xFC000000U }; // -Inf * +0 -> NaN + yield return new object[] { 0x78000000U, 0xB2800000U, 0xFC000000U }; // +Inf * -0 -> NaN + yield return new object[] { 0x78000000U, 0x32800001U, 0x78000000U }; // +Inf * 1 -> +Inf + yield return new object[] { 0x78000000U, 0xB2800001U, 0xF8000000U }; // +Inf * -1 -> -Inf + yield return new object[] { 0xF8000000U, 0x32800002U, 0xF8000000U }; // -Inf * 2 -> -Inf + yield return new object[] { 0xF8000000U, 0xB2800002U, 0x78000000U }; // -Inf * -2 -> +Inf + yield return new object[] { 0x78000000U, 0x78000000U, 0x78000000U }; // +Inf * +Inf -> +Inf + yield return new object[] { 0x78000000U, 0xF8000000U, 0xF8000000U }; // +Inf * -Inf -> -Inf + yield return new object[] { 0xF8000000U, 0xF8000000U, 0x78000000U }; // -Inf * -Inf -> +Inf + yield return new object[] { 0x78000002U, 0x32800001U, 0x78000000U }; // non-canonical +Inf * 1 -> canonical +Inf + yield return new object[] { 0x32800001U, 0xF8000005U, 0xF8000000U }; // 1 * non-canonical -Inf -> canonical -Inf + yield return new object[] { 0x32800000U, 0x32800000U, 0x32800000U }; // +0 * +0 -> +0 + yield return new object[] { 0xB2800000U, 0xB2800000U, 0x32800000U }; // -0 * -0 -> +0 + yield return new object[] { 0x32800000U, 0xB2800000U, 0xB2800000U }; // +0 * -0 -> -0 + yield return new object[] { 0x32800001U, 0xB2800000U, 0xB2800000U }; // 1 * -0 -> -0 + yield return new object[] { 0xB2800001U, 0x32800000U, 0xB2800000U }; // -1 * +0 -> -0 + yield return new object[] { 0x35000000U, 0x34000000U, 0x36800000U }; // 0e5 * 0e3 -> 0e8 (preferred exp) + yield return new object[] { 0x30000000U, 0x3280000CU, 0x30000000U }; // 0e-5 * 12 -> 0e-5 (preferred exp) + yield return new object[] { 0x32800002U, 0x32800005U, 0x3280000AU }; // 2 * 5 -> 10 (exp 0, trailing zero retained) + yield return new object[] { 0x32800002U, 0x32000005U, 0x3200000AU }; // 2 * 0.5 -> 1.0 + yield return new object[] { 0x3200000FU, 0x32800002U, 0x3200001EU }; // 1.5 * 2 -> 3.0 + yield return new object[] { 0x32000001U, 0x32000001U, 0x31800001U }; // 0.1 * 0.1 -> 0.01 + yield return new object[] { 0x3200000CU, 0x3200000CU, 0x31800090U }; // 1.2 * 1.2 -> 1.44 + yield return new object[] { 0xB2800003U, 0x32800007U, 0xB2800015U }; // -3 * 7 -> -21 + yield return new object[] { 0x3280000CU, 0x3280000CU, 0x32800090U }; // 12 * 12 -> 144 + yield return new object[] { 0x6CB8967FU, 0x6CB8967FU, 0x6D98967EU }; // all-nines squared (round) + yield return new object[] { 0x2F945855U, 0x2F9B2071U, 0x2FA42B41U }; // full-precision * full-precision (round) + yield return new object[] { 0x328F4240U, 0x328F4240U, 0x358F4240U }; // 10^(P-1) * 10^(P-1) (trailing zeros beyond precision) + yield return new object[] { 0x77E95440U, 0x77E95440U, 0x78000000U }; // huge * huge -> +Inf (overflow) + yield return new object[] { 0xF7E95440U, 0x77E95440U, 0xF8000000U }; // -huge * huge -> -Inf (overflow) + yield return new object[] { 0x02800001U, 0x02800001U, 0x00000000U }; // tiny * tiny -> underflow + yield return new object[] { 0x03000001U, 0x30000001U, 0x00800001U }; // small * small (subnormal-ish) + yield return new object[] { 0x6618967FU, 0x6638967FU, 0x6078967EU }; // wide product just below min quantum (normal after single rounding) + yield return new object[] { 0x65B8967FU, 0x65D8967FU, 0x00002710U }; // wide product deep in the subnormal range + yield return new object[] { 0x60B8967FU, 0x60B8967FU, 0x00000000U }; // wide product underflow to zero/epsilon + yield return new object[] { 0x5F800000U, 0x5F800005U, 0x5F800000U }; // zero * finite, preferred exponent far above max quantum (clamped) + yield return new object[] { 0xDF800000U, 0x5F800005U, 0xDF800000U }; // -zero * finite, preferred exponent clamped (sign preserved) + yield return new object[] { 0x33000000U, 0x5F800005U, 0x5F800000U }; // zero * finite, preferred exponent one above max quantum (clamped) + yield return new object[] { 0x32800000U, 0x5F800005U, 0x5F800000U }; // zero * finite, preferred exponent exactly at max quantum (no clamp) + yield return new object[] { 0x2F2C8C35U, 0x2A00002DU, 0x27940BE5U }; + yield return new object[] { 0xB8002104U, 0xB98043D6U, 0x4016657EU }; + yield return new object[] { 0xA8800010U, 0x34802213U, 0xAA822130U }; + yield return new object[] { 0x318496C5U, 0xA71C20DCU, 0xA8D497F0U }; + yield return new object[] { 0x2E830F1EU, 0xA9000005U, 0xA50F4B96U }; + yield return new object[] { 0xAD00006CU, 0xB7011BA9U, 0x31F7AB4CU }; + yield return new object[] { 0xAC000017U, 0x380B7DD1U, 0xB21A6E2EU }; + yield return new object[] { 0xBC800009U, 0xA600170BU, 0x3000CF63U }; + yield return new object[] { 0x1C8C3773U, 0x4C800FA6U, 0x3830F090U }; + yield return new object[] { 0xB2000007U, 0xEAD57BF3U, 0x2B68A390U }; + yield return new object[] { 0xBA000010U, 0x2F0ED152U, 0xB717B550U }; + yield return new object[] { 0x6A41D272U, 0xA8800006U, 0x9FCDE4ABU }; + yield return new object[] { 0xAC0000EBU, 0x28800027U, 0xA20023CDU }; + yield return new object[] { 0xAB8B81BBU, 0xB0000044U, 0x29CE3EF8U }; + yield return new object[] { 0x390001B3U, 0xB885B1AEU, 0xC018C4E8U }; + yield return new object[] { 0x3B800006U, 0x27007EF8U, 0x3002F9D0U }; + yield return new object[] { 0x27034AB4U, 0x3280024CU, 0x28135B18U }; + yield return new object[] { 0x3B800094U, 0x3685C05CU, 0x40551EEBU }; + yield return new object[] { 0xC0001DD4U, 0x330000DDU, 0xC099C004U }; + yield return new object[] { 0x378000FAU, 0x9A02AE5DU, 0x9FC30715U }; + yield return new object[] { 0x278014E4U, 0x3806111AU, 0x2EA071FDU }; + yield return new object[] { 0xB20ACE79U, 0x3204CA4CU, 0xB421ECD8U }; + yield return new object[] { 0xB98025B8U, 0xB2000002U, 0x39004B70U }; + yield return new object[] { 0xB780002BU, 0x3B80007DU, 0xC08014FFU }; + yield return new object[] { 0xDA81A30AU, 0x38002124U, 0xF8000000U }; + yield return new object[] { 0xB5800001U, 0x36000006U, 0xB9000006U }; + yield return new object[] { 0xB8800004U, 0x35297F01U, 0xBB909934U }; + yield return new object[] { 0xAF00252CU, 0xBF000006U, 0x3B80DF08U }; + yield return new object[] { 0x92EA6215U, 0x38800002U, 0x991546D1U }; + yield return new object[] { 0xAE800008U, 0xAE00009FU, 0x2A0004F8U }; + yield return new object[] { 0x2A0025A7U, 0x35001CE0U, 0x2D6CB89DU }; + yield return new object[] { 0x45800056U, 0x32000030U, 0x45001020U }; + yield return new object[] { 0x360DAA53U, 0xB20D6A8EU, 0xB87826E0U }; + yield return new object[] { 0xAF800202U, 0xC000857DU, 0x3D9ACD4CU }; + yield return new object[] { 0xE1000004U, 0x2D00003DU, 0x8007CED9U }; + yield return new object[] { 0xBF805674U, 0x6E377AE8U, 0xC821868CU }; + yield return new object[] { 0x2E0087F8U, 0x2F0E50D7U, 0x2CB1D493U }; + yield return new object[] { 0x32800061U, 0x4D80016BU, 0x4D80898BU }; + yield return new object[] { 0xB1FD9564U, 0x33800008U, 0xB3647783U }; + yield return new object[] { 0xA9000C89U, 0xC0000CD9U, 0x37101AD0U }; + yield return new object[] { 0x37001CE3U, 0x58095C61U, 0x5E453939U }; + yield return new object[] { 0x2E8018F2U, 0xB98002B6U, 0xB5C3A00CU }; + yield return new object[] { 0x400B2B74U, 0xAE0003ACU, 0xBCE8FEDCU }; + yield return new object[] { 0x2B000676U, 0xD1000009U, 0xC9803A26U }; + yield return new object[] { 0xDD8E1313U, 0x33325AAEU, 0xF8000000U }; + yield return new object[] { 0x3500008BU, 0x38800030U, 0x3B001A10U }; + yield return new object[] { 0x338DB8E1U, 0xB9000023U, 0xBAB00714U }; + yield return new object[] { 0xB700030FU, 0x2A807402U, 0xAFA37B69U }; + yield return new object[] { 0xA68542F3U, 0x35000005U, 0xA91A4EBFU }; + yield return new object[] { 0x261B9CA7U, 0xB7000005U, 0xEAAA0F43U }; + yield return new object[] { 0x33000007U, 0xB180033BU, 0xB200169DU }; + yield return new object[] { 0x0D80010EU, 0xBC000016U, 0x97001734U }; + yield return new object[] { 0x310000DCU, 0xAC0583C6U, 0xAB795304U }; + yield return new object[] { 0x297CCD10U, 0x2A000006U, 0x214AE170U }; + yield return new object[] { 0x15000001U, 0xA7010BBCU, 0x89810BBCU }; + yield return new object[] { 0xAB8002D0U, 0xAA84EC44U, 0x24A371EAU }; + yield return new object[] { 0xB4000180U, 0x38002093U, 0xB9B0DC80U }; + yield return new object[] { 0x2A015FAAU, 0x3A000006U, 0x31883DFCU }; + yield return new object[] { 0x388002C5U, 0x2A04F49DU, 0x31232278U }; + yield return new object[] { 0x35003DFEU, 0x35BB999DU, 0x3A5E95D5U }; + } + + [Theory] + [MemberData(nameof(op_Multiply_TestData))] + public static void op_Multiply(uint left, uint right, uint expected) + { + Decimal32 result = Unsafe.BitCast(left) * Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } 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 index 5c992b9cace633..ba8fa4469208c2 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -968,5 +968,121 @@ public static void op_Comparison(ulong left, ulong right, bool lessThan, bool gr Assert.Equal(greaterThanOrEqual, l >= r); } + public static IEnumerable op_Multiply_TestData() + { + yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, 0xFC000000_00000000UL }; // NaN * 1 -> NaN + yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // 1 * NaN -> NaN + yield return new object[] { 0xFC000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // NaN * +Inf -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000000UL, 0xFC000000_00000000UL }; // +Inf * +0 -> NaN + yield return new object[] { 0x31C00000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // +0 * +Inf -> NaN + yield return new object[] { 0xF8000000_00000000UL, 0x31C00000_00000000UL, 0xFC000000_00000000UL }; // -Inf * +0 -> NaN + yield return new object[] { 0x78000000_00000000UL, 0xB1C00000_00000000UL, 0xFC000000_00000000UL }; // +Inf * -0 -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // +Inf * 1 -> +Inf + yield return new object[] { 0x78000000_00000000UL, 0xB1C00000_00000001UL, 0xF8000000_00000000UL }; // +Inf * -1 -> -Inf + yield return new object[] { 0xF8000000_00000000UL, 0x31C00000_00000002UL, 0xF8000000_00000000UL }; // -Inf * 2 -> -Inf + yield return new object[] { 0xF8000000_00000000UL, 0xB1C00000_00000002UL, 0x78000000_00000000UL }; // -Inf * -2 -> +Inf + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0x78000000_00000000UL }; // +Inf * +Inf -> +Inf + yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0xF8000000_00000000UL }; // +Inf * -Inf -> -Inf + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0x78000000_00000000UL }; // -Inf * -Inf -> +Inf + yield return new object[] { 0x78000000_00000002UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // non-canonical +Inf * 1 -> canonical +Inf + yield return new object[] { 0x31C00000_00000001UL, 0xF8000000_00000005UL, 0xF8000000_00000000UL }; // 1 * non-canonical -Inf -> canonical -Inf + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0x31C00000_00000000UL }; // +0 * +0 -> +0 + yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // -0 * -0 -> +0 + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, 0xB1C00000_00000000UL }; // +0 * -0 -> -0 + yield return new object[] { 0x31C00000_00000001UL, 0xB1C00000_00000000UL, 0xB1C00000_00000000UL }; // 1 * -0 -> -0 + yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000000UL, 0xB1C00000_00000000UL }; // -1 * +0 -> -0 + yield return new object[] { 0x32600000_00000000UL, 0x32200000_00000000UL, 0x32C00000_00000000UL }; // 0e5 * 0e3 -> 0e8 (preferred exp) + yield return new object[] { 0x31200000_00000000UL, 0x31C00000_0000000CUL, 0x31200000_00000000UL }; // 0e-5 * 12 -> 0e-5 (preferred exp) + yield return new object[] { 0x31C00000_00000002UL, 0x31C00000_00000005UL, 0x31C00000_0000000AUL }; // 2 * 5 -> 10 (exp 0, trailing zero retained) + yield return new object[] { 0x31C00000_00000002UL, 0x31A00000_00000005UL, 0x31A00000_0000000AUL }; // 2 * 0.5 -> 1.0 + yield return new object[] { 0x31A00000_0000000FUL, 0x31C00000_00000002UL, 0x31A00000_0000001EUL }; // 1.5 * 2 -> 3.0 + yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_00000001UL, 0x31800000_00000001UL }; // 0.1 * 0.1 -> 0.01 + yield return new object[] { 0x31A00000_0000000CUL, 0x31A00000_0000000CUL, 0x31800000_00000090UL }; // 1.2 * 1.2 -> 1.44 + yield return new object[] { 0xB1C00000_00000003UL, 0x31C00000_00000007UL, 0xB1C00000_00000015UL }; // -3 * 7 -> -21 + yield return new object[] { 0x31C00000_0000000CUL, 0x31C00000_0000000CUL, 0x31C00000_00000090UL }; // 12 * 12 -> 144 + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFFUL, 0x6CF386F2_6FC0FFFEUL }; // all-nines squared (round) + yield return new object[] { 0x2FE4BCA8_DBB35555UL, 0x2FE650E1_24EF1C71UL, 0x2FE86BD6_DBE97B41UL }; // full-precision * full-precision (round) + yield return new object[] { 0x31C38D7E_A4C68000UL, 0x31C38D7E_A4C68000UL, 0x33A38D7E_A4C68000UL }; // 10^(P-1) * 10^(P-1) (trailing zeros beyond precision) + yield return new object[] { 0x5FFFF973_CAFA8000UL, 0x5FFFF973_CAFA8000UL, 0x78000000_00000000UL }; // huge * huge -> +Inf (overflow) + yield return new object[] { 0xDFFFF973_CAFA8000UL, 0x5FFFF973_CAFA8000UL, 0xF8000000_00000000UL }; // -huge * huge -> -Inf (overflow) + yield return new object[] { 0x01C00000_00000001UL, 0x01C00000_00000001UL, 0x00000000_00000000UL }; // tiny * tiny -> underflow + yield return new object[] { 0x01E00000_00000001UL, 0x31200000_00000001UL, 0x01400000_00000001UL }; // small * small (subnormal-ish) + yield return new object[] { 0x662B86F2_6FC0FFFFUL, 0x662B86F2_6FC0FFFFUL, 0x606386F2_6FC0FFFEUL }; // wide product just below min quantum (normal after single rounding) + yield return new object[] { 0x65EB86F2_6FC0FFFFUL, 0x65F386F2_6FC0FFFFUL, 0x00000918_4E72A000UL }; // wide product deep in the subnormal range + yield return new object[] { 0x607386F2_6FC0FFFFUL, 0x607386F2_6FC0FFFFUL, 0x00000000_00000000UL }; // wide product underflow to zero/epsilon + yield return new object[] { 0x5FE00000_00000000UL, 0x5FE00000_00000005UL, 0x5FE00000_00000000UL }; // zero * finite, preferred exponent far above max quantum (clamped) + yield return new object[] { 0xDFE00000_00000000UL, 0x5FE00000_00000005UL, 0xDFE00000_00000000UL }; // -zero * finite, preferred exponent clamped (sign preserved) + yield return new object[] { 0x31E00000_00000000UL, 0x5FE00000_00000005UL, 0x5FE00000_00000000UL }; // zero * finite, preferred exponent one above max quantum (clamped) + yield return new object[] { 0x31C00000_00000000UL, 0x5FE00000_00000005UL, 0x5FE00000_00000000UL }; // zero * finite, preferred exponent exactly at max quantum (no clamp) + yield return new object[] { 0xAF200000_000023D2UL, 0xAFA34A58_43C82F35UL, 0x2D7E2C4D_3A5C54BAUL }; + yield return new object[] { 0x19C00000_0006B3C8UL, 0xB2400000_000014EDUL, 0x9A400000_8C401028UL }; + yield return new object[] { 0x31200054_42297730UL, 0x2F000063_A23BC84AUL, 0x2F658071_C3516735UL }; + yield return new object[] { 0x2E60007D_E4F31691UL, 0x328000E1_F407BFE1UL, 0x3032A47C_68E29A50UL }; + yield return new object[] { 0x95400091_BAB57FB9UL, 0x14600009_ED261C68UL, 0x80000000_00000000UL }; + yield return new object[] { 0xB1E00000_173C0E97UL, 0x17C00575_228BEDE5UL, 0x98A84F6B_C6DCC819UL }; + yield return new object[] { 0x31C00000_3838FC8EUL, 0x33800000_0008C901UL, 0x3381EDEC_DAF47A8EUL }; + yield return new object[] { 0x33E00000_00000006UL, 0xB3000000_000738AFUL, 0xB5200000_002B541AUL }; + yield return new object[] { 0xB120007E_4A0EE845UL, 0xB1A2CD7D_00E19992UL, 0x326F33B7_70DDF2ECUL }; + yield return new object[] { 0xB1800000_003142FFUL, 0x2F4004A5_E17CD24DUL, 0xAF85DC8E_E7E2C5B1UL }; + yield return new object[] { 0x30A1AE18_98FE6B6EUL, 0xB2400000_00034D87UL, 0xB1C3A2F6_E824594EUL }; + yield return new object[] { 0x1040016E_281289C6UL, 0x2E417F41_004E3595UL, 0x0E378B2A_D1798B46UL }; + yield return new object[] { 0x2F2160D1_63A6598CUL, 0xAD988DFF_DCC311D5UL, 0xACC98682_F248B25DUL }; + yield return new object[] { 0xAF800000_000149F7UL, 0x82600001_9308E202UL, 0x0022077B_4806A1EEUL }; + yield return new object[] { 0xB3600000_0000230CUL, 0xB8600000_00000003UL, 0x3A000000_00006924UL }; + yield return new object[] { 0xAD80816C_363173A8UL, 0x2FE00718_1926DD3EUL, 0xAD23F181_FCCBE5B2UL }; + yield return new object[] { 0xB2800000_00000000UL, 0x2F000000_007AF558UL, 0xAFC00000_00000000UL }; + yield return new object[] { 0xAE836513_54C06345UL, 0x8D000000_0000002AUL, 0x09EE421D_FD8E6DBBUL }; + yield return new object[] { 0xAE543A07_C082FDB3UL, 0x35200000_00000007UL, 0xB1CE289F_06C217FDUL }; + yield return new object[] { 0x32600000_0000B48AUL, 0x31400000_00015465UL, 0x31E00000_F00E8272UL }; + yield return new object[] { 0x33400000_000E8F6AUL, 0x33C00000_00000287UL, 0x35400000_24CC74E6UL }; + yield return new object[] { 0xAFE00000_0025FA34UL, 0x31C00000_000001BBUL, 0xAFE00000_41B7F7FCUL }; + yield return new object[] { 0x31200015_EE481751UL, 0xB06385FA_ED15C9E7UL, 0xEC412FEE_DE7C30F6UL }; + yield return new object[] { 0xB3A00000_0001B644UL, 0x31400000_88C9241BUL, 0xB320EA2C_7933C92CUL }; + yield return new object[] { 0xCE000000_2C155ADEUL, 0x2E400000_00000CC1UL, 0xCA800232_3C5BE95EUL }; + yield return new object[] { 0x2E0059A2_1C73F798UL, 0xAEC00000_0000038FUL, 0xAB3FE596_B9AB0231UL }; + yield return new object[] { 0xB4400000_00000000UL, 0xB3C00000_00000002UL, 0x36400000_00000000UL }; + yield return new object[] { 0x14200000_1E148863UL, 0xAF600000_00000003UL, 0x91C00000_5A3D9929UL }; + yield return new object[] { 0xB3000000_093826EBUL, 0x27600000_00000008UL, 0xA8A00000_49C13758UL }; + yield return new object[] { 0xBDA00000_0054E714UL, 0xAEC00000_00083858UL, 0x3AA002B9_E85BCEE0UL }; + yield return new object[] { 0xBBE00000_00000008UL, 0xB1000000_00000008UL, 0x3B200000_00000040UL }; + yield return new object[] { 0xD8600000_72FA8C0BUL, 0x30404C87_C53B7F29UL, 0xD7E5C449_A7AF6E2BUL }; + yield return new object[] { 0x31E00000_00011A74UL, 0x31EB8DA8_BD290BE9UL, 0x32A85A9F_C97EAEF6UL }; + yield return new object[] { 0xAE8000BE_61899933UL, 0x2E8E68EB_22D62BD8UL, 0xACCBC85B_083C3191UL }; + yield return new object[] { 0x30A00002_3C81B477UL, 0xB3400000_0000019EUL, 0xB220039D_D9C1D872UL }; + yield return new object[] { 0xAFC00016_E7160E75UL, 0x32600000_00000013UL, 0xB06001B3_26A312AFUL }; + yield return new object[] { 0x328000C6_0667F8FFUL, 0x437376D5_46AB54AAUL, 0x45B08DF4_524121DDUL }; + yield return new object[] { 0x316008E3_5687A600UL, 0x32C00000_000000BCUL, 0x326686F3_8B9DE800UL }; + yield return new object[] { 0x32600011_10EB5AFDUL, 0xB1200000_70F0F514UL, 0xB264EF2F_0CD8F588UL }; + yield return new object[] { 0x2F600000_0000018DUL, 0x34000000_000001F2UL, 0x31A00000_0003044AUL }; + yield return new object[] { 0x3260573E_35DB7C8EUL, 0x328018AA_7862D5CEUL, 0x34A93E11_AF666CB4UL }; + yield return new object[] { 0x22C00000_00000005UL, 0xB0A00000_000004FDUL, 0xA1A00000_000018F1UL }; + yield return new object[] { 0xD4200014_54458F18UL, 0xB2C00000_0000001DUL, 0x5520024D_8BE135B8UL }; + yield return new object[] { 0x30200000_0000004CUL, 0x324199F5_8C48AAF6UL, 0x30CC2BB0_908EACE7UL }; + yield return new object[] { 0xEB90A2E6_7C00A0EDUL, 0xB2C00000_000020F5UL, 0x2FDB8907_874A27EDUL }; + yield return new object[] { 0xB282B0EE_E060C24AUL, 0x30600000_00000008UL, 0xB1358777_03061250UL }; + yield return new object[] { 0x2E20004A_73ED6947UL, 0x9C6F546E_573DB6B7UL, 0x9A44E6EC_D9584235UL }; + yield return new object[] { 0xB1600000_00000054UL, 0x30800000_00000500UL, 0xB0200000_0001A400UL }; + yield return new object[] { 0xAA4042B6_7AA7818BUL, 0x31800000_00000055UL, 0xAA162696_B99E0327UL }; + yield return new object[] { 0xB2E00000_00000366UL, 0xB2C00000_00001F1DUL, 0x33E00000_0069BC8EUL }; + yield return new object[] { 0x33000000_00000002UL, 0x33200000_02DF7470UL, 0x34600000_05BEE8E0UL }; + yield return new object[] { 0xB062FAF8_87469CE0UL, 0x30200000_00000045UL, 0xAEF4907F_A5673A70UL }; + yield return new object[] { 0xB0200000_000E661FUL, 0xB1000000_00076DFBUL, 0x2F60006A_FA725365UL }; + yield return new object[] { 0xA9800000_01ECEE93UL, 0x2DC04A74_C2BEB838UL, 0xA6496549_D0B127DBUL }; + yield return new object[] { 0x29200001_1579BC2EUL, 0xAFC058BC_B025DCC1UL, 0xA83022F2_73C51F9FUL }; + yield return new object[] { 0xB3C00000_00000012UL, 0xD5600000_0000001EUL, 0x57600000_0000021CUL }; + yield return new object[] { 0xAF400000_000003E0UL, 0xB2E00000_1892B9E7UL, 0x3060005F_38905F20UL }; + yield return new object[] { 0x32400000_1E910857UL, 0xB120000E_041E212AUL, 0xB22AF7B6_BA92F217UL }; + yield return new object[] { 0x2FE00000_36F3932DUL, 0x32000000_00003529UL, 0x30200B69_3A7AE335UL }; + yield return new object[] { 0x32C00001_B3BF92FFUL, 0x2FC00000_02D2E250UL, 0x310C4DF4_84C9C542UL }; + } + + [Theory] + [MemberData(nameof(op_Multiply_TestData))] + public static void op_Multiply(ulong left, ulong right, ulong expected) + { + Decimal64 result = Unsafe.BitCast(left) * Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } From fe33fe0a9cafe89727d6d54a53fde6d10eefbcf5 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 9 Jul 2026 20:44:56 -0700 Subject: [PATCH 06/19] Implement division for Decimal32/64/128 Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 244 ++++++++++++++++++ .../src/System/Numerics/Decimal128.cs | 10 + .../src/System/Numerics/Decimal32.cs | 9 + .../src/System/Numerics/Decimal64.cs | 9 + .../System.Runtime/ref/System.Runtime.cs | 3 + .../System/Decimal128Tests.cs | 130 ++++++++++ .../System/Decimal32Tests.cs | 130 ++++++++++ .../System/Decimal64Tests.cs | 130 ++++++++++ 8 files changed, 665 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index 6da2b1f199af96..61b763a24e3b1f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -874,6 +874,151 @@ static int WriteDigits(TValue value, Span destination, int length) } } + /// + /// Divides two IEEE 754 decimal values represented by their raw bit patterns and returns the + /// bit pattern of the correctly rounded (round-to-nearest, ties-to-even) quotient. + /// + /// + /// The dividend coefficient is scaled up by a power of ten large enough to expose more than the + /// format precision in significant quotient digits, then long-divided by the divisor coefficient + /// in base 10 (the intermediate values can exceed the underlying integer width, so the arithmetic + /// is performed on digit spans). The remainder determines whether the result is exact; an inexact + /// result carries a sticky flag into the shared rounding path, while an exact result has its + /// exponent raised toward the IEEE 754 preferred exponent (the difference of the operand exponents) + /// by discarding trailing zeros. This mirrors the mathematical behavior of the Intel reference + /// implementation while remaining independent of the integer width. + /// + internal static TValue DivideDecimalIeee754(TValue left, TValue right) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `bid32_div`, `bid64_div`, and `bid128_div` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (TDecimal.IsNaN(left) || TDecimal.IsNaN(right)) + { + return TDecimal.NaN; + } + + // The sign of a quotient is always the exclusive-or of the operand signs, including zeros. + bool resultSign = TDecimal.IsNegative(left) ^ TDecimal.IsNegative(right); + + if (TDecimal.IsInfinity(left)) + { + // Infinity divided by infinity is invalid (NaN); infinity divided by any finite value is + // an infinity carrying the exclusive-or sign. + if (TDecimal.IsInfinity(right)) + { + return TDecimal.NaN; + } + + return resultSign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + if (TDecimal.IsInfinity(right)) + { + // A finite value divided by infinity is zero with the exclusive-or sign; the preferred + // exponent is the minimum (the encoder clamps the sub-minimum exponent up to it). + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, TValue.Zero, TDecimal.MinAdjustedExponent); + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(left); + DecodedDecimalIeee754 b = UnpackDecimalIeee754(right); + + if (TValue.IsZero(b.Significand)) + { + // Zero divided by zero is invalid (NaN); any other value divided by zero is an infinity + // carrying the exclusive-or sign (division by zero). + if (TValue.IsZero(a.Significand)) + { + return TDecimal.NaN; + } + + return resultSign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + if (TValue.IsZero(a.Significand)) + { + // Zero divided by a finite non-zero value is zero. The preferred exponent is the difference + // of the operand exponents (clamped to the representable range by the encoder). + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, TValue.Zero, a.UnbiasedExponent - b.UnbiasedExponent); + } + + int dividendDigits = TDecimal.CountDigits(a.Significand); + int divisorDigits = TDecimal.CountDigits(b.Significand); + + // Scale the dividend up so the quotient exposes at least Precision + 1 significant digits. + // Because both coefficients have at most Precision digits, this shift is always positive. + int shift = divisorDigits - dividendDigits + TDecimal.Precision + 1; + Debug.Assert(shift > 0); + + int quotientExponent = a.UnbiasedExponent - b.UnbiasedExponent - shift; + + int capacity = (2 * TDecimal.Precision) + 4; + Span divisorSpan = stackalloc byte[capacity]; + Span numeratorSpan = stackalloc byte[capacity]; + Span quotientSpan = stackalloc byte[capacity]; + Span remainderSpan = stackalloc byte[capacity]; + + int divisorLength = WriteDigits(b.Significand, divisorSpan, divisorDigits); + + int numeratorLength = WriteDigits(a.Significand, numeratorSpan, dividendDigits); + for (int i = 0; i < shift; i++) + { + numeratorSpan[numeratorLength++] = (byte)'0'; + } + + BigDivRem(numeratorSpan.Slice(0, numeratorLength), divisorSpan.Slice(0, divisorLength), quotientSpan, remainderSpan, out bool remainderNonZero); + + int start = 0; + while (start < numeratorLength && quotientSpan[start] == (byte)'0') + { + start++; + } + int digitsCount = numeratorLength - start; + Debug.Assert(digitsCount > 0); + + if (!remainderNonZero) + { + // The quotient is exact, so raise its exponent toward the preferred exponent (the difference + // of the operand exponents) by discarding trailing zeros. + int idealExponent = a.UnbiasedExponent - b.UnbiasedExponent; + while (digitsCount > 1 && quotientExponent < idealExponent && quotientSpan[start + digitsCount - 1] == (byte)'0') + { + digitsCount--; + quotientExponent++; + } + } + + Span numberDigits = stackalloc byte[capacity + 2]; + NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, numberDigits); + quotientSpan.Slice(start, digitsCount).CopyTo(number.Digits); + number.Digits[digitsCount] = 0; + number.DigitsCount = digitsCount; + number.Scale = digitsCount + quotientExponent; + number.IsNegative = resultSign; + number.HasNonZeroTail = remainderNonZero; + number.CheckConsistency(); + + return NumberToDecimalIeee754Bits(ref number); + + static int WriteDigits(TValue value, Span destination, int length) + { + TValue ten = TValue.CreateTruncating(10); + + for (int i = length - 1; i >= 0; i--) + { + (value, TValue remainder) = TValue.DivRem(value, ten); + destination[i] = (byte)('0' + int.CreateTruncating(remainder)); + } + + return length; + } + } + private static ReadOnlySpan TrimLeadingZeros(ReadOnlySpan digits) { int start = 0; @@ -939,6 +1084,105 @@ private static int BigMultiply(ReadOnlySpan left, ReadOnlySpan right return length; } + private static void BigDivRem(ReadOnlySpan dividend, ReadOnlySpan divisor, Span quotient, Span remainder, out bool remainderNonZero) + { + // Schoolbook base-10 long division. Each dividend digit is brought down into the running + // remainder (which multiplies it by ten and adds the digit), then the quotient digit is found + // by repeatedly subtracting the divisor. The remainder is stored right-aligned so the subtract + // and compare helpers operate on the least-significant digits regardless of leading zeros. + int dividendLength = dividend.Length; + int divisorLength = divisor.Length; + int remainderLength = 0; + + for (int i = 0; i < dividendLength; i++) + { + remainder[remainderLength++] = dividend[i]; + + int digit = 0; + while (CompareRightAligned(remainder, remainderLength, divisor, divisorLength) >= 0) + { + SubtractRightAligned(remainder, remainderLength, divisor, divisorLength); + digit++; + } + + quotient[i] = (byte)('0' + digit); + } + + remainderNonZero = false; + + for (int i = 0; i < remainderLength; i++) + { + if (remainder[i] != (byte)'0') + { + remainderNonZero = true; + break; + } + } + } + + private static int CompareRightAligned(ReadOnlySpan left, int leftLength, ReadOnlySpan right, int rightLength) + { + int leftStart = 0; + while (leftStart < leftLength && left[leftStart] == (byte)'0') + { + leftStart++; + } + + int rightStart = 0; + while (rightStart < rightLength && right[rightStart] == (byte)'0') + { + rightStart++; + } + + int leftDigits = leftLength - leftStart; + int rightDigits = rightLength - rightStart; + + if (leftDigits != rightDigits) + { + return leftDigits < rightDigits ? -1 : 1; + } + + for (int i = 0; i < leftDigits; i++) + { + int difference = left[leftStart + i] - right[rightStart + i]; + + if (difference != 0) + { + return difference < 0 ? -1 : 1; + } + } + + return 0; + } + + private static void SubtractRightAligned(Span left, int leftLength, ReadOnlySpan right, int rightLength) + { + int borrow = 0; + + for (int i = 0; i < leftLength; i++) + { + int leftIndex = leftLength - 1 - i; + int digit = (left[leftIndex] - '0') - borrow; + + if (i < rightLength) + { + digit -= right[rightLength - 1 - i] - '0'; + } + + if (digit < 0) + { + digit += 10; + borrow = 1; + } + else + { + borrow = 0; + } + + left[leftIndex] = (byte)('0' + digit); + } + } + private static int BigAdd(ReadOnlySpan left, ReadOnlySpan right, Span result) { int leftLength = left.Length; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index eb66ff675881a8..6eca24ff8bbb4d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -293,6 +293,16 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return new Decimal128(result); } + /// Divides two values together to compute their quotient. + /// The value which divides. + /// The value which divides . + /// The quotient of divided by . + public static Decimal128 operator /(Decimal128 left, Decimal128 right) + { + UInt128 result = Number.DivideDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); + return new Decimal128(result); + } + /// Compares two values to determine equality. /// The value to compare with . /// The value to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index aa45e60b4219c3..912133e435afa6 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -295,6 +295,15 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return new Decimal32(Number.MultiplyDecimalIeee754(left._value, right._value)); } + /// Divides two values together to compute their quotient. + /// The value which divides. + /// The value which divides . + /// The quotient of divided by . + public static Decimal32 operator /(Decimal32 left, Decimal32 right) + { + return new Decimal32(Number.DivideDecimalIeee754(left._value, right._value)); + } + /// Compares two values to determine equality. /// The value to compare with . /// The value to compare with . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index ee180fd8b6bba3..8747a8a36409c4 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -296,6 +296,15 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin return new Decimal64(Number.MultiplyDecimalIeee754(left._value, right._value)); } + /// Divides two values together to compute their quotient. + /// The value which divides. + /// The value which divides . + /// The quotient of divided by . + public static Decimal64 operator /(Decimal64 left, Decimal64 right) + { + return new Decimal64(Number.DivideDecimalIeee754(left._value, right._value)); + } + /// Compares two values to determine equality. /// The value to compare with . /// The value to compare with . diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 694921042c7667..27d559ca80c20d 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11409,6 +11409,7 @@ public readonly struct Decimal32 public static Decimal32 operator +(Decimal32 left, Decimal32 right) { throw null; } public static Decimal32 operator -(Decimal32 left, Decimal32 right) { throw null; } public static Decimal32 operator *(Decimal32 left, Decimal32 right) { throw null; } + public static Decimal32 operator /(Decimal32 left, Decimal32 right) { throw null; } public static bool operator ==(Decimal32 left, Decimal32 right) { throw null; } public static bool operator !=(Decimal32 left, Decimal32 right) { throw null; } public static bool operator <(Decimal32 left, Decimal32 right) { throw null; } @@ -11464,6 +11465,7 @@ public readonly struct Decimal64 public static Decimal64 operator +(Decimal64 left, Decimal64 right) { throw null; } public static Decimal64 operator -(Decimal64 left, Decimal64 right) { throw null; } public static Decimal64 operator *(Decimal64 left, Decimal64 right) { throw null; } + public static Decimal64 operator /(Decimal64 left, Decimal64 right) { throw null; } public static bool operator ==(Decimal64 left, Decimal64 right) { throw null; } public static bool operator !=(Decimal64 left, Decimal64 right) { throw null; } public static bool operator <(Decimal64 left, Decimal64 right) { throw null; } @@ -11519,6 +11521,7 @@ public readonly struct Decimal128 public static Decimal128 operator +(Decimal128 left, Decimal128 right) { throw null; } public static Decimal128 operator -(Decimal128 left, Decimal128 right) { throw null; } public static Decimal128 operator *(Decimal128 left, Decimal128 right) { throw null; } + public static Decimal128 operator /(Decimal128 left, Decimal128 right) { throw null; } public static bool operator ==(Decimal128 left, Decimal128 right) { throw null; } public static bool operator !=(Decimal128 left, Decimal128 right) { throw null; } public static bool operator <(Decimal128 left, Decimal128 right) { throw null; } 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 index 4eefaa8751a0b3..add5c123174099 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -1078,5 +1078,135 @@ public static void op_Multiply(UInt128 left, UInt128 right, UInt128 expected) Assert.Equal(expected, Unsafe.BitCast(result)); } + public static IEnumerable op_Division_TestData() + { + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN / 1 -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // 1 / NaN -> NaN + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN / +Inf -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf / +Inf -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf / -Inf -> NaN + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf / +Inf -> NaN + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf / -Inf -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf / 1 -> +Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000000) }; // +Inf / -1 -> -Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf / 2 -> -Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000002), new UInt128(0x7800000000000000, 0x0000000000000000) }; // -Inf / -2 -> +Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf / +0 -> +Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf / +0 -> -Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // +Inf / -0 -> -Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x0000000000000000, 0x0000000000000000) }; // 1 / +Inf -> +0 (Etiny) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x8000000000000000, 0x0000000000000000) }; // -1 / +Inf -> -0 (Etiny) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x8000000000000000, 0x0000000000000000) }; // 5 / -Inf -> -0 (Etiny) + yield return new object[] { new UInt128(0x3054000000000000, 0x0000000000000005), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x0000000000000000, 0x0000000000000000) }; // 5e10 / +Inf -> +0 (Etiny, dividend exp ignored) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x0000000000000000, 0x0000000000000000) }; // +0 / +Inf -> +0 (Etiny) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x8000000000000000, 0x0000000000000000) }; // -0 / +Inf -> -0 (Etiny) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // 5 / +0 -> +Inf + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -5 / +0 -> -Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // 5 / -0 -> -Inf + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000005), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // -5 / -0 -> +Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +0 / +0 -> NaN + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -0 / -0 -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +0 / -0 -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 / 5 -> +0 (ideal exp) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0xB040000000000000, 0x0000000000000000) }; // -0 / 5 -> -0 (ideal exp, sign xor) + yield return new object[] { new UInt128(0x3044000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3044000000000000, 0x0000000000000000) }; // 0e2 / 5 -> 0e2 (ideal exp) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3046000000000000, 0x0000000000000005), new UInt128(0x303A000000000000, 0x0000000000000000) }; // 0 / 5e3 -> 0e-3 (ideal exp) + yield return new object[] { new UInt128(0x304A000000000000, 0x0000000000000000), new UInt128(0x3036000000000000, 0x0000000000000005), new UInt128(0x3054000000000000, 0x0000000000000000) }; // 0e5 / 5e-5 -> 0e10 (ideal exp) + yield return new object[] { new UInt128(0x5FFE000000000000, 0x0000000000000000), new UInt128(0x3036000000000000, 0x0000000000000005), new UInt128(0x5FFE000000000000, 0x0000000000000000) }; // zero dividend, ideal exp far above max quantum (clamped) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x303E000000000000, 0x0000000000000005) }; // 1 / 2 -> 0.5 (exact) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000008), new UInt128(0x303A000000000000, 0x000000000000007D) }; // 1 / 8 -> 0.125 (exact) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000064), new UInt128(0x3040000000000000, 0x0000000000000004), new UInt128(0x3040000000000000, 0x0000000000000019) }; // 100 / 4 -> 25 (exact) + yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000005) }; // 10 / 2 -> 5 (exact) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000006), new UInt128(0x3040000000000000, 0x0000000000000003), new UInt128(0x3040000000000000, 0x0000000000000002) }; // 6 / 3 -> 2 (exact) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 5 / 5 -> 1 (exact) + yield return new object[] { new UInt128(0x303C000000000000, 0x0000000000000064), new UInt128(0x3040000000000000, 0x0000000000000004), new UInt128(0x303C000000000000, 0x0000000000000019) }; // 1.00 / 4 -> 0.25 (exact, ideal exp preserved) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000007), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x303E000000000000, 0x0000000000000023) }; // 7 / 2 -> 3.5 (exact) + yield return new object[] { new UInt128(0xB040000000000000, 0x000000000000000F), new UInt128(0x3040000000000000, 0x0000000000000004), new UInt128(0xB03C000000000000, 0x0000000000000177) }; // -15 / 4 -> -3.75 (exact, sign) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000003), new UInt128(0x2FFCA45894E48295, 0x67D9DA2155555555) }; // 1 / 3 -> repeating (round) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000003), new UInt128(0x2FFD48B129C9052A, 0xCFB3B442AAAAAAAB) }; // 2 / 3 -> repeating (round) + yield return new object[] { new UInt128(0x3040000000000000, 0x00000000000F4240), new UInt128(0x3040000000000000, 0x0000000000000007), new UInt128(0x3008466F1B3D5C89, 0x2C81EFC524924925) }; // 1000000 / 7 (round) + yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x0000000000000003), new UInt128(0x2FFEA45894E48295, 0x67D9DA2155555555) }; // 10 / 3 -> repeating (round) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3040000000000000, 0x0000000000000007), new UInt128(0x3040466F1B3D5C89, 0x2C81EFC524924924) }; // all-nines / 7 (round) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x2FBA314DC6448D93, 0x38C15B0A00000000) }; // 1 / all-nines (round) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000007), new UInt128(0x2FFC8CDE367AB912, 0x5903DF8A49249249) }; // 2 / 7 (round) + yield return new object[] { new UInt128(0x2FFE41BD085B676E, 0xF657240D55555555), new UInt128(0x2FFE57A6B5CF3493, 0xF31EDABC71C71C71), new UInt128(0x2FFD71C74F0225D0, 0x29AA2ACB00000001) }; // full-precision / full-precision (round) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x2FFE36C831A180DC, 0x77F348B5C71C71C7), new UInt128(0x3082000000000000, 0x0000000000000009) }; // all-nines / near-one full precision (round) + yield return new object[] { new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0x3036000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // huge / tiny -> +Inf (overflow) + yield return new object[] { new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0x3036000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -huge / tiny -> -Inf (overflow) + yield return new object[] { new UInt128(0x0040000000000000, 0x0000000000000001), new UInt128(0x304A000000000000, 0x0000000000000001), new UInt128(0x0036000000000000, 0x0000000000000001) }; // tiny / large -> underflow + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x5FFC000000000000, 0x0000000000000003), new UInt128(0x0040A45894E48295, 0x67D9DA2155555555) }; // 1 / huge -> tiny subnormal (round) + yield return new object[] { new UInt128(0x0042000000000000, 0x0000000000000001), new UInt128(0x304A000000000000, 0x0000000000000001), new UInt128(0x0038000000000000, 0x0000000000000001) }; // small / large (subnormal-ish) + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf / 1 -> canonical +Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000005), new UInt128(0x8000000000000000, 0x0000000000000000) }; // 1 / non-canonical -Inf -> canonical -0 (Etiny) + yield return new object[] { new UInt128(0x300C000000000000, 0x0386DF690C6EF5BE), new UInt128(0xB018000000000000, 0x00022FCFF31AA0B9), new UInt128(0xAFF6CB90CE781B14, 0x60367827E211020A) }; + yield return new object[] { new UInt128(0x3016000000000000, 0x00000000025C222D), new UInt128(0xAFEE0000105989B6, 0x14E871DF7FB4E7FF), new UInt128(0xAFFD81C848A85A9B, 0x8A95F9D9E2B714E3) }; + yield return new object[] { new UInt128(0xAFE4004BC3F60664, 0xA2D28316972FB16C), new UInt128(0x3024000000000000, 0x336683344D857468), new UInt128(0xAFD64FE830A0E8A9, 0x51A5EFA9F2EE31EA) }; + yield return new object[] { new UInt128(0xB026000000000000, 0x0000000000000009), new UInt128(0xD36C269369438B15, 0x4317B7B7F941F96B), new UInt128(0x0C7838B6B6D1A502, 0x7FE0879DCC18152B) }; + yield return new object[] { new UInt128(0xB030000000000000, 0x00000006C1105459), new UInt128(0xB01E100AA3FBDB37, 0x590C5A8BE72C6FA0), new UInt128(0x2FE3B79655E99EE6, 0xC67B9FCF86368529) }; + yield return new object[] { new UInt128(0x4592000000000000, 0x0125EB92A4B6C61E), new UInt128(0xB03E000000000000, 0x00000001A8B80287), new UInt128(0xC560393E687AE539, 0x7B7E802B3D5289EF) }; + yield return new object[] { new UInt128(0xB018000000000000, 0x000016E8FC21185B), new UInt128(0xB052000000000000, 0x00000000EFF4031C), new UInt128(0x2FCB348106F6193F, 0x6654C91C46250F08) }; + yield return new object[] { new UInt128(0x3020000000000000, 0x0000009AA0434574), new UInt128(0x3048000000000000, 0x0000000000001ED7), new UInt128(0x2FE59EBC2E5756FC, 0x9ACF1030DDA3964B) }; + yield return new object[] { new UInt128(0xB058000000000000, 0x00000000000003DF), new UInt128(0x300C000000000000, 0x000179DCB4BE3FF7), new UInt128(0xB032759A8AB12BE9, 0xCFE4870EFACD0EAF) }; + yield return new object[] { new UInt128(0x9EE405C739C662F4, 0x5752242E1DB1EC20), new UInt128(0x3004000000000000, 0x0000BD66F041ECD5), new UInt128(0x9F0115774EEFB4D0, 0xFE3D466FFB652EAD) }; + yield return new object[] { new UInt128(0xAFE6000119C10D0F, 0xC98968772E7E6EF7), new UInt128(0xB0020000000002DA, 0x7B7ACAB57EE654B5), new UInt128(0x2FEF3F0D3347DCD8, 0x95957D6691C04392) }; + yield return new object[] { new UInt128(0xB00E000000000000, 0x00000000004E6990), new UInt128(0x3852000000000000, 0x000000000000031D), new UInt128(0xA7C13DE5A78B5C8A, 0x03E165F3CC9B7686) }; + yield return new object[] { new UInt128(0x3008000000000000, 0x000000014204609D), new UInt128(0xB020000000000000, 0x000000000000CCF5), new UInt128(0xAFF032C433C139C8, 0xF02601142C32D705) }; + yield return new object[] { new UInt128(0x8462000000000000, 0x00000000EAC878D0), new UInt128(0xB034000000000000, 0x0000000000000110), new UInt128(0x043A476666F4A173, 0x9A4CE8D8AE5A5A5A) }; + yield return new object[] { new UInt128(0xB02400012DE50EC6, 0xBBBA78C47B74A0E0), new UInt128(0x302200000000012F, 0x1F7FF8F741AE5567), new UInt128(0xB00E5262044C38C4, 0xFEF19903FCA8CB97) }; + yield return new object[] { new UInt128(0x3038000000000000, 0x0000000000000057), new UInt128(0xB03C000000000000, 0x000000110FC8CD30), new UInt128(0xAFE83A891034CE03, 0xC2DCA5C65F32C6C8) }; + yield return new object[] { new UInt128(0x3030000000000000, 0x17E95893CB288412), new UInt128(0xC6E2000000000000, 0x52ABCE0A9DE14E35), new UInt128(0x994A8E9AC9234B50, 0x505AA880878F3D7F) }; + yield return new object[] { new UInt128(0x0870000000000000, 0x000000DD250AAC57), new UInt128(0xB00000000000092C, 0x7754F6DC4C1D7D08), new UInt128(0x88586C18C7716200, 0xD6EFD4695899BC44) }; + yield return new object[] { new UInt128(0xB036000000000000, 0x000000130041C7D7), new UInt128(0xB016000000000000, 0x002262E6BEFE8EBF), new UInt128(0x30139FB5EFDD0FB7, 0xF49B951DA6A4F2B4) }; + yield return new object[] { new UInt128(0x3036000000000000, 0x00000001B6848ADB), new UInt128(0x301E000000000000, 0x00000000000A8089), new UInt128(0x301E34B3DBEFDA00, 0x228A6FF58A1CF2E8) }; + yield return new object[] { new UInt128(0xC3AE000000000000, 0x04CF4E8A302A8BDC), new UInt128(0xAFFA00000000002E, 0x3420D6865B6A6E4E), new UInt128(0x43AAC87D34C14517, 0x02E1E1BF053FE3E4) }; + yield return new object[] { new UInt128(0xD258000000000000, 0x000000000091C9EF), new UInt128(0x300A000000000000, 0x0000167364209915), new UInt128(0xD23EBED54CBE4A75, 0x1E37535387AACE6D) }; + yield return new object[] { new UInt128(0x301C000000000000, 0x000000000439E2E9), new UInt128(0xB014000000000032, 0x708772352DE98CF2), new UInt128(0xAFEB77B55CD376ED, 0x4CAC31E2635348AD) }; + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000064), new UInt128(0xB03A000000000000, 0x00003B2E17E3E88B), new UInt128(0x2FEC4BC57A20CF80, 0xFB85C69A5C979594) }; + yield return new object[] { new UInt128(0xB044000000000000, 0x00000000023F5ABE), new UInt128(0xB03A000000006CA7, 0xBAF42939DF4E7BA0), new UInt128(0x2FE76A508107AB67, 0x9C4270C148A575DA) }; + yield return new object[] { new UInt128(0xC6E6000000000000, 0x00000001B8D3E89E), new UInt128(0x301C000000000007, 0x0964D911A0BBD8F4), new UInt128(0xC6B318EB37D2AA50, 0x5FE753A62F2E7B02) }; + yield return new object[] { new UInt128(0x305E000000000000, 0x000000000000A71E), new UInt128(0x3026000000000000, 0x000000795A3C873F), new UInt128(0x302794B3266BDB84, 0x37128AFACE65D3A5) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x0000000005C867C0), new UInt128(0x302E000000000000, 0x0000000000572AE6), new UInt128(0xAFEC53BC1D980FB6, 0xE421BC3CC6718D7B) }; + yield return new object[] { new UInt128(0xB650000000D0224F, 0xFD6F80D1270DDE07), new UInt128(0xB010000000000000, 0x0000021670F50113), new UInt128(0x365A360BC5384886, 0x1E398FB62F6118F3) }; + yield return new object[] { new UInt128(0xB02C000000000006, 0x909F1381718DE51E), new UInt128(0xB01E00000000002D, 0xCE88CD810A4F6601), new UInt128(0x300A46A940A69240, 0x67EBE25A5A68E21F) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x00000000000018F2), new UInt128(0x303C000000000000, 0x000000000046CBDD), new UInt128(0xAFD643DC59C6E179, 0x77D217EDE86BABCC) }; + yield return new object[] { new UInt128(0x5F400000000005E8, 0x2C8923BCBDB9FD3E), new UInt128(0xB034000000000000, 0x00000000000C3C2B), new UInt128(0xDF2AAB8556757D28, 0xD46648FBAAFF5F2F) }; + yield return new object[] { new UInt128(0xD95C0000000051B5, 0x62B3BD63621B218F), new UInt128(0xB01E000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xB0280000000006F8, 0x435BE30981A1CB95), new UInt128(0x54FE000000000000, 0x00015E8772D1DBC6), new UInt128(0x8B37A50D2C37AC59, 0xD606984662943B6A) }; + yield return new object[] { new UInt128(0x5C72000037D35F7D, 0xF4E8427727658DEF), new UInt128(0x3042000000000000, 0x0001692E610A8ED8), new UInt128(0x5C48D6803B794C4E, 0xA6922295D7A11D64) }; + yield return new object[] { new UInt128(0x305C000000000000, 0x000000000000FE36), new UInt128(0x3014000000000000, 0x000000D1D108B375), new UInt128(0x3037640DA7EB4F3D, 0xF70D116B4CC77265) }; + yield return new object[] { new UInt128(0xB042000000000000, 0x000000000000A932), new UInt128(0x303E000000000000, 0x000002503A034940), new UInt128(0xAFF253F531466EE5, 0x20D5831D0B9F13FC) }; + yield return new object[] { new UInt128(0xB00E000000000000, 0x587FADD919E02966), new UInt128(0x3014000000000000, 0x0721E25C46337978), new UInt128(0xAFFA3D2D2C168F46, 0x26985E7DE8022E37) }; + yield return new object[] { new UInt128(0xAFFA4821D7034A02, 0xB8ACC25DF6F4CF86), new UInt128(0xB044000000000000, 0x00E1766220EE98D3), new UInt128(0x2FD471A975C830ED, 0x0211EE4AF299EBED) }; + yield return new object[] { new UInt128(0x3024000000000000, 0x000000000007C72E), new UInt128(0x1758000000000000, 0x000000041F72D9AA), new UInt128(0x48C08DEE0D62CA1A, 0x64D9A54AF884B677) }; + yield return new object[] { new UInt128(0x301A0000000061C8, 0xEEFC10C4D3DADBE3), new UInt128(0xB0100003A1125F48, 0x953E3B9231F49265), new UInt128(0xAFFC4F2E6E9690B3, 0xDCB74C8FE1EF531E) }; + yield return new object[] { new UInt128(0x301000004BB9B532, 0xE5CB89F41922BD32), new UInt128(0xDE64000000000000, 0x0000CCBC550D3404), new UInt128(0x81C6335464D97738, 0xE074EA1AE02D536C) }; + yield return new object[] { new UInt128(0xB024000000000000, 0x000000000116C396), new UInt128(0x3028000000005351, 0x729900C58D0BD408), new UInt128(0xAFD8E4ED692C9CD4, 0xAE2CE29C4965AF22) }; + yield return new object[] { new UInt128(0x300200000220753C, 0x8CA178C667F5ED50), new UInt128(0xD382000000000000, 0x0A69C7380DBC5B46), new UInt128(0x8C8FB07E9B67A8C7, 0x7F8CF2E324F5ED40) }; + yield return new object[] { new UInt128(0x300C045E788BFD23, 0xFDC576761B96F039), new UInt128(0xB01A000000000000, 0x0AE0ABB2D104776E), new UInt128(0xB00C37BD9345F786, 0x7BCC90D8781F904C) }; + yield return new object[] { new UInt128(0xCD32000000000000, 0x00000062ADC4C04D), new UInt128(0xB040000000000000, 0xD235D4832BF52B34), new UInt128(0x4CE089F3E8481FD9, 0x898A4C67B47BC8DA) }; + yield return new object[] { new UInt128(0xB040000000000000, 0x00017F274F195D94), new UInt128(0x2FFA000000000000, 0x022F53F33F61565B), new UInt128(0xB03E83EE559A4D25, 0xE27D334E7E6D35F9) }; + yield return new object[] { new UInt128(0xB044000000000000, 0x0000000000000046), new UInt128(0x300A00000005E4A6, 0x3DBCC6C875ABB5B6), new UInt128(0xB009E46DEC94605E, 0xC7571D5C27FE5D49) }; + yield return new object[] { new UInt128(0x3066000000000000, 0x0000000000000015), new UInt128(0x3014000000008E7B, 0xA150E858F5A1264C), new UInt128(0x302299E0D48E6734, 0x207E4332183038FA) }; + yield return new object[] { new UInt128(0x21A60000000048E8, 0xDC7FE586D3707752), new UInt128(0xB024000000000023, 0x2E0D31C20845EFD5), new UInt128(0xA1850595706B4AF7, 0x28FE4C6B3E8361ED) }; + yield return new object[] { new UInt128(0xAFF403A1D73F5ACD, 0x96A762C7BAA70840), new UInt128(0x3042000000000000, 0x000000019FF4DE39), new UInt128(0xAFDC340C30509846, 0x39B2D63AA1FE08FB) }; + yield return new object[] { new UInt128(0x303C000000000000, 0x00113925FCD09CDA), new UInt128(0x3000000000007140, 0x43CE4908B69E66F9), new UInt128(0x3029BEEC6622872B, 0x7A151FE09435B4FF) }; + yield return new object[] { new UInt128(0x3004000000000000, 0x00003C65D7E4774B), new UInt128(0x301825B99EFDAE69, 0x303766A98D4C34B5), new UInt128(0x2FC3ABE8CF0915E5, 0xDDD46FB7F4B41926) }; + yield return new object[] { new UInt128(0xB0220000004AE762, 0xF65E345A6F0E69F9), new UInt128(0x301A000000000000, 0x00000000000DC8E9), new UInt128(0xB02E316B8C450FA4, 0x5CB5EF3047A81DAF) }; + yield return new object[] { new UInt128(0x3052000000000000, 0x000000000000002A), new UInt128(0x300000001C3D6C1F, 0x26F3E82E5A4692F0), new UInt128(0x301AECEEFA9A5EDB, 0x6AF2737449986B22) }; + yield return new object[] { new UInt128(0xB048000000000000, 0x0000000000000280), new UInt128(0xAFFC000000000000, 0x0AE997D86F01F581), new UInt128(0x302B914A094EF4D1, 0x6321CF5E46D6E827) }; + yield return new object[] { new UInt128(0xB018000000120CBF, 0x23F4CD9940BD2B9A), new UInt128(0xDFCC005830554042, 0x149E930F0D337664), new UInt128(0x003E99FA6C26C43F, 0x6C3B7206FB91392C) }; + yield return new object[] { new UInt128(0x300E000000000000, 0x0112E59C50FC31A5), new UInt128(0x2FEC16CD5779F833, 0xFD5BB8EB3F0C6FD3), new UInt128(0x3000527D27495963, 0xEE1F2C36110345C1) }; + yield return new object[] { new UInt128(0x3D3E000000000000, 0x00000000E4F7F4D6), new UInt128(0xD270000000000000, 0x00000815ABADD29E), new UInt128(0x9AC4D51105256889, 0x3089FD94A8A3BBBF) }; + yield return new object[] { new UInt128(0x3034000000000000, 0x00181648272743F6), new UInt128(0x569600000002FBFF, 0x4C2693B7C7D3A38C), new UInt128(0x098A5CA6C49E66A5, 0x1E6D169ACA7DC27B) }; + } + + [Theory] + [MemberData(nameof(op_Division_TestData))] + public static void op_Division(UInt128 left, UInt128 right, UInt128 expected) + { + Decimal128 result = Unsafe.BitCast(left) / Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } 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 index c84a8960b8f383..694617979d721c 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -1078,5 +1078,135 @@ public static void op_Multiply(uint left, uint right, uint expected) Assert.Equal(expected, Unsafe.BitCast(result)); } + public static IEnumerable op_Division_TestData() + { + yield return new object[] { 0xFC000000U, 0x32800001U, 0xFC000000U }; // NaN / 1 -> NaN + yield return new object[] { 0x32800001U, 0xFC000000U, 0xFC000000U }; // 1 / NaN -> NaN + yield return new object[] { 0xFC000000U, 0x78000000U, 0xFC000000U }; // NaN / +Inf -> NaN + yield return new object[] { 0x78000000U, 0x78000000U, 0xFC000000U }; // +Inf / +Inf -> NaN + yield return new object[] { 0x78000000U, 0xF8000000U, 0xFC000000U }; // +Inf / -Inf -> NaN + yield return new object[] { 0xF8000000U, 0x78000000U, 0xFC000000U }; // -Inf / +Inf -> NaN + yield return new object[] { 0xF8000000U, 0xF8000000U, 0xFC000000U }; // -Inf / -Inf -> NaN + yield return new object[] { 0x78000000U, 0x32800001U, 0x78000000U }; // +Inf / 1 -> +Inf + yield return new object[] { 0x78000000U, 0xB2800001U, 0xF8000000U }; // +Inf / -1 -> -Inf + yield return new object[] { 0xF8000000U, 0x32800002U, 0xF8000000U }; // -Inf / 2 -> -Inf + yield return new object[] { 0xF8000000U, 0xB2800002U, 0x78000000U }; // -Inf / -2 -> +Inf + yield return new object[] { 0x78000000U, 0x32800000U, 0x78000000U }; // +Inf / +0 -> +Inf + yield return new object[] { 0xF8000000U, 0x32800000U, 0xF8000000U }; // -Inf / +0 -> -Inf + yield return new object[] { 0x78000000U, 0xB2800000U, 0xF8000000U }; // +Inf / -0 -> -Inf + yield return new object[] { 0x32800001U, 0x78000000U, 0x00000000U }; // 1 / +Inf -> +0 (Etiny) + yield return new object[] { 0xB2800001U, 0x78000000U, 0x80000000U }; // -1 / +Inf -> -0 (Etiny) + yield return new object[] { 0x32800005U, 0xF8000000U, 0x80000000U }; // 5 / -Inf -> -0 (Etiny) + yield return new object[] { 0x37800005U, 0x78000000U, 0x00000000U }; // 5e10 / +Inf -> +0 (Etiny, dividend exp ignored) + yield return new object[] { 0x32800000U, 0x78000000U, 0x00000000U }; // +0 / +Inf -> +0 (Etiny) + yield return new object[] { 0xB2800000U, 0x78000000U, 0x80000000U }; // -0 / +Inf -> -0 (Etiny) + yield return new object[] { 0x32800005U, 0x32800000U, 0x78000000U }; // 5 / +0 -> +Inf + yield return new object[] { 0xB2800005U, 0x32800000U, 0xF8000000U }; // -5 / +0 -> -Inf + yield return new object[] { 0x32800005U, 0xB2800000U, 0xF8000000U }; // 5 / -0 -> -Inf + yield return new object[] { 0xB2800005U, 0xB2800000U, 0x78000000U }; // -5 / -0 -> +Inf + yield return new object[] { 0x32800000U, 0x32800000U, 0xFC000000U }; // +0 / +0 -> NaN + yield return new object[] { 0xB2800000U, 0xB2800000U, 0xFC000000U }; // -0 / -0 -> NaN + yield return new object[] { 0x32800000U, 0xB2800000U, 0xFC000000U }; // +0 / -0 -> NaN + yield return new object[] { 0x32800000U, 0x32800005U, 0x32800000U }; // +0 / 5 -> +0 (ideal exp) + yield return new object[] { 0xB2800000U, 0x32800005U, 0xB2800000U }; // -0 / 5 -> -0 (ideal exp, sign xor) + yield return new object[] { 0x33800000U, 0x32800005U, 0x33800000U }; // 0e2 / 5 -> 0e2 (ideal exp) + yield return new object[] { 0x32800000U, 0x34000005U, 0x31000000U }; // 0 / 5e3 -> 0e-3 (ideal exp) + yield return new object[] { 0x35000000U, 0x30000005U, 0x37800000U }; // 0e5 / 5e-5 -> 0e10 (ideal exp) + yield return new object[] { 0x5F800000U, 0x30000005U, 0x5F800000U }; // zero dividend, ideal exp far above max quantum (clamped) + yield return new object[] { 0x32800001U, 0x32800002U, 0x32000005U }; // 1 / 2 -> 0.5 (exact) + yield return new object[] { 0x32800001U, 0x32800008U, 0x3100007DU }; // 1 / 8 -> 0.125 (exact) + yield return new object[] { 0x32800064U, 0x32800004U, 0x32800019U }; // 100 / 4 -> 25 (exact) + yield return new object[] { 0x3280000AU, 0x32800002U, 0x32800005U }; // 10 / 2 -> 5 (exact) + yield return new object[] { 0x32800006U, 0x32800003U, 0x32800002U }; // 6 / 3 -> 2 (exact) + yield return new object[] { 0x32800005U, 0x32800005U, 0x32800001U }; // 5 / 5 -> 1 (exact) + yield return new object[] { 0x31800064U, 0x32800004U, 0x31800019U }; // 1.00 / 4 -> 0.25 (exact, ideal exp preserved) + yield return new object[] { 0x32800007U, 0x32800002U, 0x32000023U }; // 7 / 2 -> 3.5 (exact) + yield return new object[] { 0xB280000FU, 0x32800004U, 0xB1800177U }; // -15 / 4 -> -3.75 (exact, sign) + yield return new object[] { 0x32800001U, 0x32800003U, 0x2F32DCD5U }; // 1 / 3 -> repeating (round) + yield return new object[] { 0x32800002U, 0x32800003U, 0x2F65B9ABU }; // 2 / 3 -> repeating (round) + yield return new object[] { 0x328F4240U, 0x32800007U, 0x3215CC5BU }; // 1000000 / 7 (round) + yield return new object[] { 0x3280000AU, 0x32800003U, 0x2FB2DCD5U }; // 10 / 3 -> repeating (round) + yield return new object[] { 0x6CB8967FU, 0x32800007U, 0x3295CC5BU }; // all-nines / 7 (round) + yield return new object[] { 0x32800001U, 0x6CB8967FU, 0x2C0F4240U }; // 1 / all-nines (round) + yield return new object[] { 0x32800002U, 0x32800007U, 0x2F2B98B7U }; // 2 / 7 (round) + yield return new object[] { 0x2F945855U, 0x2F9B2071U, 0x2F7270E1U }; // full-precision / full-precision (round) + yield return new object[] { 0x6CB8967FU, 0x2F90F447U, 0x35800009U }; // all-nines / near-one full precision (round) + yield return new object[] { 0x77E95440U, 0x30000001U, 0x78000000U }; // huge / tiny -> +Inf (overflow) + yield return new object[] { 0xF7E95440U, 0x30000001U, 0xF8000000U }; // -huge / tiny -> -Inf (overflow) + yield return new object[] { 0x02800001U, 0x35000001U, 0x00000001U }; // tiny / large -> underflow + yield return new object[] { 0x32800001U, 0x5F000003U, 0x02B2DCD5U }; // 1 / huge -> tiny subnormal (round) + yield return new object[] { 0x03000001U, 0x35000001U, 0x00800001U }; // small / large (subnormal-ish) + yield return new object[] { 0x78000002U, 0x32800001U, 0x78000000U }; // non-canonical +Inf / 1 -> canonical +Inf + yield return new object[] { 0x32800001U, 0xF8000005U, 0x80000000U }; // 1 / non-canonical -Inf -> canonical -0 (Etiny) + yield return new object[] { 0x2F2C8C35U, 0x2A00002DU, 0x36E2FEAFU }; + yield return new object[] { 0xB8002104U, 0xB98043D6U, 0x2DCA43A5U }; + yield return new object[] { 0xA8800010U, 0x34802213U, 0xA21BFCF7U }; + yield return new object[] { 0x318496C5U, 0xA71C20DCU, 0xB998E4C6U }; + yield return new object[] { 0x2E830F1EU, 0xA9000005U, 0xB7861E3CU }; + yield return new object[] { 0xAD00006CU, 0xB7011BA9U, 0x2416B197U }; + yield return new object[] { 0xAC000017U, 0x380B7DD1U, 0xA12E99C7U }; + yield return new object[] { 0xBC800009U, 0xA600170BU, 0x449747B2U }; + yield return new object[] { 0x1C8C3773U, 0x4C800FA6U, 0x009E7EEAU }; + yield return new object[] { 0xB2000007U, 0xEAD57BF3U, 0x336D077CU }; + yield return new object[] { 0xBA000010U, 0x2F0ED152U, 0xB8192411U }; + yield return new object[] { 0x6A41D272U, 0xA8800006U, 0xB315A313U }; + yield return new object[] { 0xAC0000EBU, 0x28800027U, 0xB35BF1A9U }; + yield return new object[] { 0xAB8B81BBU, 0xB0000044U, 0x2D10EBF5U }; + yield return new object[] { 0x390001B3U, 0xB885B1AEU, 0xAE91C985U }; + yield return new object[] { 0x3B800006U, 0x27007EF8U, 0x421C2AA7U }; + yield return new object[] { 0x27034AB4U, 0x3280024CU, 0x2537FBB0U }; + yield return new object[] { 0x3B800094U, 0x3685C05CU, 0x32BBE9F9U }; + yield return new object[] { 0xC0001DD4U, 0x330000DDU, 0xBD34B8E4U }; + yield return new object[] { 0x378000FAU, 0x9A02AE5DU, 0xCB95B5D7U }; + yield return new object[] { 0x278014E4U, 0x3806111AU, 0x1E148643U }; + yield return new object[] { 0xB20ACE79U, 0x3204CA4CU, 0xAFA26C55U }; + yield return new object[] { 0xB98025B8U, 0xB2000002U, 0x3A0012DCU }; + yield return new object[] { 0xB780002BU, 0x3B80007DU, 0xAD000158U }; + yield return new object[] { 0xDA81A30AU, 0x38002124U, 0xD2934B2BU }; + yield return new object[] { 0xB5800001U, 0x36000006U, 0xAE996E6BU }; + yield return new object[] { 0xB8800004U, 0x35297F01U, 0xB0167191U }; + yield return new object[] { 0xAF00252CU, 0xBF000006U, 0x22800632U }; + yield return new object[] { 0x92EA6215U, 0x38800002U, 0x8CB5310AU }; + yield return new object[] { 0xAE800008U, 0xAE00009FU, 0x2F4CC617U }; + yield return new object[] { 0x2A0025A7U, 0x35001CE0U, 0x2493E5A9U }; + yield return new object[] { 0x45800056U, 0x32000030U, 0x431B56B3U }; + yield return new object[] { 0x360DAA53U, 0xB20D6A8EU, 0xB38F8AC7U }; + yield return new object[] { 0xAF800202U, 0xC000857DU, 0x1E16F36FU }; + yield return new object[] { 0xE1000004U, 0x2D00003DU, 0x8914FBCEU }; + yield return new object[] { 0xBF805674U, 0x6E377AE8U, 0xB5220489U }; + yield return new object[] { 0x2E0087F8U, 0x2F0E50D7U, 0x2DB89C87U }; + yield return new object[] { 0x32800061U, 0x4D80016BU, 0x1428C630U }; + yield return new object[] { 0xB1FD9564U, 0x33800008U, 0xB08FB2ACU }; + yield return new object[] { 0xA9000C89U, 0xC0000CD9U, 0x6614E05DU }; + yield return new object[] { 0x37001CE3U, 0x58095C61U, 0x0D9264B8U }; + yield return new object[] { 0x2E8018F2U, 0xB98002B6U, 0xE92C6841U }; + yield return new object[] { 0x400B2B74U, 0xAE0003ACU, 0xC2F6D3B7U }; + yield return new object[] { 0x2B000676U, 0xD1000009U, 0x8A9C0AD2U }; + yield return new object[] { 0xDD8E1313U, 0x33325AAEU, 0xD9AAA65CU }; + yield return new object[] { 0x3500008BU, 0x38800030U, 0x2C2C2FD9U }; + yield return new object[] { 0x338DB8E1U, 0xB9000023U, 0xAC83EBAEU }; + yield return new object[] { 0xB700030FU, 0x2A807402U, 0xBB283AFDU }; + yield return new object[] { 0xA68542F3U, 0x35000005U, 0xA38A85E6U }; + yield return new object[] { 0x261B9CA7U, 0xB7000005U, 0xA18585BBU }; + yield return new object[] { 0x33000007U, 0xB180033BU, 0xEBE127C9U }; + yield return new object[] { 0x0D80010EU, 0xBC000016U, 0x8192BA09U }; + yield return new object[] { 0x310000DCU, 0xAC0583C6U, 0xB2DCE222U }; + yield return new object[] { 0x297CCD10U, 0x2A000006U, 0x3194CCD8U }; + yield return new object[] { 0x15000001U, 0xA7010BBCU, 0x9B16433AU }; + yield return new object[] { 0xAB8002D0U, 0xAA84EC44U, 0x2F220D78U }; + yield return new object[] { 0xB4000180U, 0x38002093U, 0xAAC643C5U }; + yield return new object[] { 0x2A015FAAU, 0x3A000006U, 0x2196E511U }; + yield return new object[] { 0x388002C5U, 0x2A04F49DU, 0x3CA14FCDU }; + yield return new object[] { 0x35003DFEU, 0x35BB999DU, 0x2DBDFF39U }; + } + + [Theory] + [MemberData(nameof(op_Division_TestData))] + public static void op_Division(uint left, uint right, uint expected) + { + Decimal32 result = Unsafe.BitCast(left) / Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } 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 index ba8fa4469208c2..ca34384139bc03 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -1084,5 +1084,135 @@ public static void op_Multiply(ulong left, ulong right, ulong expected) Assert.Equal(expected, Unsafe.BitCast(result)); } + public static IEnumerable op_Division_TestData() + { + yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, 0xFC000000_00000000UL }; // NaN / 1 -> NaN + yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // 1 / NaN -> NaN + yield return new object[] { 0xFC000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // NaN / +Inf -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // +Inf / +Inf -> NaN + yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // +Inf / -Inf -> NaN + yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // -Inf / +Inf -> NaN + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // -Inf / -Inf -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // +Inf / 1 -> +Inf + yield return new object[] { 0x78000000_00000000UL, 0xB1C00000_00000001UL, 0xF8000000_00000000UL }; // +Inf / -1 -> -Inf + yield return new object[] { 0xF8000000_00000000UL, 0x31C00000_00000002UL, 0xF8000000_00000000UL }; // -Inf / 2 -> -Inf + yield return new object[] { 0xF8000000_00000000UL, 0xB1C00000_00000002UL, 0x78000000_00000000UL }; // -Inf / -2 -> +Inf + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000000UL, 0x78000000_00000000UL }; // +Inf / +0 -> +Inf + yield return new object[] { 0xF8000000_00000000UL, 0x31C00000_00000000UL, 0xF8000000_00000000UL }; // -Inf / +0 -> -Inf + yield return new object[] { 0x78000000_00000000UL, 0xB1C00000_00000000UL, 0xF8000000_00000000UL }; // +Inf / -0 -> -Inf + yield return new object[] { 0x31C00000_00000001UL, 0x78000000_00000000UL, 0x00000000_00000000UL }; // 1 / +Inf -> +0 (Etiny) + yield return new object[] { 0xB1C00000_00000001UL, 0x78000000_00000000UL, 0x80000000_00000000UL }; // -1 / +Inf -> -0 (Etiny) + yield return new object[] { 0x31C00000_00000005UL, 0xF8000000_00000000UL, 0x80000000_00000000UL }; // 5 / -Inf -> -0 (Etiny) + yield return new object[] { 0x33000000_00000005UL, 0x78000000_00000000UL, 0x00000000_00000000UL }; // 5e10 / +Inf -> +0 (Etiny, dividend exp ignored) + yield return new object[] { 0x31C00000_00000000UL, 0x78000000_00000000UL, 0x00000000_00000000UL }; // +0 / +Inf -> +0 (Etiny) + yield return new object[] { 0xB1C00000_00000000UL, 0x78000000_00000000UL, 0x80000000_00000000UL }; // -0 / +Inf -> -0 (Etiny) + yield return new object[] { 0x31C00000_00000005UL, 0x31C00000_00000000UL, 0x78000000_00000000UL }; // 5 / +0 -> +Inf + yield return new object[] { 0xB1C00000_00000005UL, 0x31C00000_00000000UL, 0xF8000000_00000000UL }; // -5 / +0 -> -Inf + yield return new object[] { 0x31C00000_00000005UL, 0xB1C00000_00000000UL, 0xF8000000_00000000UL }; // 5 / -0 -> -Inf + yield return new object[] { 0xB1C00000_00000005UL, 0xB1C00000_00000000UL, 0x78000000_00000000UL }; // -5 / -0 -> +Inf + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0xFC000000_00000000UL }; // +0 / +0 -> NaN + yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000000UL, 0xFC000000_00000000UL }; // -0 / -0 -> NaN + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, 0xFC000000_00000000UL }; // +0 / -0 -> NaN + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000005UL, 0x31C00000_00000000UL }; // +0 / 5 -> +0 (ideal exp) + yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000005UL, 0xB1C00000_00000000UL }; // -0 / 5 -> -0 (ideal exp, sign xor) + yield return new object[] { 0x32000000_00000000UL, 0x31C00000_00000005UL, 0x32000000_00000000UL }; // 0e2 / 5 -> 0e2 (ideal exp) + yield return new object[] { 0x31C00000_00000000UL, 0x32200000_00000005UL, 0x31600000_00000000UL }; // 0 / 5e3 -> 0e-3 (ideal exp) + yield return new object[] { 0x32600000_00000000UL, 0x31200000_00000005UL, 0x33000000_00000000UL }; // 0e5 / 5e-5 -> 0e10 (ideal exp) + yield return new object[] { 0x5FE00000_00000000UL, 0x31200000_00000005UL, 0x5FE00000_00000000UL }; // zero dividend, ideal exp far above max quantum (clamped) + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL, 0x31A00000_00000005UL }; // 1 / 2 -> 0.5 (exact) + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000008UL, 0x31600000_0000007DUL }; // 1 / 8 -> 0.125 (exact) + yield return new object[] { 0x31C00000_00000064UL, 0x31C00000_00000004UL, 0x31C00000_00000019UL }; // 100 / 4 -> 25 (exact) + yield return new object[] { 0x31C00000_0000000AUL, 0x31C00000_00000002UL, 0x31C00000_00000005UL }; // 10 / 2 -> 5 (exact) + yield return new object[] { 0x31C00000_00000006UL, 0x31C00000_00000003UL, 0x31C00000_00000002UL }; // 6 / 3 -> 2 (exact) + yield return new object[] { 0x31C00000_00000005UL, 0x31C00000_00000005UL, 0x31C00000_00000001UL }; // 5 / 5 -> 1 (exact) + yield return new object[] { 0x31800000_00000064UL, 0x31C00000_00000004UL, 0x31800000_00000019UL }; // 1.00 / 4 -> 0.25 (exact, ideal exp preserved) + yield return new object[] { 0x31C00000_00000007UL, 0x31C00000_00000002UL, 0x31A00000_00000023UL }; // 7 / 2 -> 3.5 (exact) + yield return new object[] { 0xB1C00000_0000000FUL, 0x31C00000_00000004UL, 0xB1800000_00000177UL }; // -15 / 4 -> -3.75 (exact, sign) + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000003UL, 0x2FCBD7A6_25405555UL }; // 1 / 3 -> repeating (round) + yield return new object[] { 0x31C00000_00000002UL, 0x31C00000_00000003UL, 0x2FD7AF4C_4A80AAABUL }; // 2 / 3 -> repeating (round) + yield return new object[] { 0x31C00000_000F4240UL, 0x31C00000_00000007UL, 0x30851347_34894925UL }; // 1000000 / 7 (round) + yield return new object[] { 0x31C00000_0000000AUL, 0x31C00000_00000003UL, 0x2FEBD7A6_25405555UL }; // 10 / 3 -> repeating (round) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x31C00000_00000007UL, 0x31C51347_34894924UL }; // all-nines / 7 (round) + yield return new object[] { 0x31C00000_00000001UL, 0x6C7386F2_6FC0FFFFUL, 0x2DE38D7E_A4C68000UL }; // 1 / all-nines (round) + yield return new object[] { 0x31C00000_00000002UL, 0x31C00000_00000007UL, 0x2FCA268E_69129249UL }; // 2 / 7 (round) + yield return new object[] { 0x2FE4BCA8_DBB35555UL, 0x2FE650E1_24EF1C71UL, 0x2FDAA535_D3D0C001UL }; // full-precision / full-precision (round) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x2FE3F28C_B71571C7UL, 0x33A00000_00000009UL }; // all-nines / near-one full precision (round) + yield return new object[] { 0x5FFFF973_CAFA8000UL, 0x31200000_00000001UL, 0x78000000_00000000UL }; // huge / tiny -> +Inf (overflow) + yield return new object[] { 0xDFFFF973_CAFA8000UL, 0x31200000_00000001UL, 0xF8000000_00000000UL }; // -huge / tiny -> -Inf (overflow) + yield return new object[] { 0x01C00000_00000001UL, 0x32600000_00000001UL, 0x01200000_00000001UL }; // tiny / large -> underflow + yield return new object[] { 0x31C00000_00000001UL, 0x5FC00000_00000003UL, 0x01CBD7A6_25405555UL }; // 1 / huge -> tiny subnormal (round) + yield return new object[] { 0x01E00000_00000001UL, 0x32600000_00000001UL, 0x01400000_00000001UL }; // small / large (subnormal-ish) + yield return new object[] { 0x78000000_00000002UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // non-canonical +Inf / 1 -> canonical +Inf + yield return new object[] { 0x31C00000_00000001UL, 0xF8000000_00000005UL, 0x80000000_00000000UL }; // 1 / non-canonical -Inf -> canonical -0 (Etiny) + yield return new object[] { 0xAF200000_000023D2UL, 0xAFA34A58_43C82F35UL, 0x6B7B2CEB_297ED958UL }; + yield return new object[] { 0x19C00000_0006B3C8UL, 0xB2400000_000014EDUL, 0x979D2147_81117476UL }; + yield return new object[] { 0x31200054_42297730UL, 0x2F000063_A23BC84AUL, 0x31FE0B6E_C9E4B663UL }; + yield return new object[] { 0x2E60007D_E4F31691UL, 0x328000E1_F407BFE1UL, 0x2BB3CB6E_24C9CCB5UL }; + yield return new object[] { 0x95400091_BAB57FB9UL, 0x14600009_ED261C68UL, 0xB0E5373B_9B321342UL }; + yield return new object[] { 0xB1E00000_173C0E97UL, 0x17C00575_228BEDE5UL, 0xC9771439_895B2784UL }; + yield return new object[] { 0x31C00000_3838FC8EUL, 0x33800000_0008C901UL, 0x2E85D20C_CE429AB3UL }; + yield return new object[] { 0x33E00000_00000006UL, 0xB3000000_000738AFUL, 0xB024810D_4C0C6020UL }; + yield return new object[] { 0xB120007E_4A0EE845UL, 0xB1A2CD7D_00E19992UL, 0x2EF86D56_8CCEE558UL }; + yield return new object[] { 0xB1800000_003142FFUL, 0x2F4004A5_E17CD24DUL, 0xB156717A_82887347UL }; + yield return new object[] { 0x30A1AE18_98FE6B6EUL, 0xB2400000_00034D87UL, 0xAF67C2FF_FFD9495DUL }; + yield return new object[] { 0x1040016E_281289C6UL, 0x2E417F41_004E3595UL, 0x118D4239_24D52B06UL }; + yield return new object[] { 0x2F2160D1_63A6598CUL, 0xAD988DFF_DCC311D5UL, 0xB153F0C6_047336B7UL }; + yield return new object[] { 0xAF800000_000149F7UL, 0x82600001_9308E202UL, 0x5C64702C_F70E8C82UL }; + yield return new object[] { 0xB3600000_0000230CUL, 0xB8600000_00000003UL, 0x2B4A9FFE_D84EEAABUL }; + yield return new object[] { 0xAD80816C_363173A8UL, 0x2FE00718_1926DD3EUL, 0xADA67B3F_728271F8UL }; + yield return new object[] { 0xB2800000_00000000UL, 0x2F000000_007AF558UL, 0xB5400000_00000000UL }; + yield return new object[] { 0xAE836513_54C06345UL, 0x8D000000_0000002AUL, 0x5308153A_3780EC5BUL }; + yield return new object[] { 0xAE543A07_C082FDB3UL, 0x35200000_00000007UL, 0xAADCE52F_A54D6A6DUL }; + yield return new object[] { 0x32600000_0000B48AUL, 0x31400000_00015465UL, 0x30F2D7CB_58E04973UL }; + yield return new object[] { 0x33400000_000E8F6AUL, 0x33C00000_00000287UL, 0x2FC53D5A_B1B4EF3CUL }; + yield return new object[] { 0xAFE00000_0025FA34UL, 0x31C00000_000001BBUL, 0xAE73F5C4_5E9939EFUL }; + yield return new object[] { 0x31200015_EE481751UL, 0xB06385FA_ED15C9E7UL, 0xEC01BE15_983D5BE2UL }; + yield return new object[] { 0xB3A00000_0001B644UL, 0x31400000_88C9241BUL, 0xB1B15E7C_6BD1FB18UL }; + yield return new object[] { 0xCE000000_2C155ADEUL, 0x2E400000_00000CC1UL, 0xD0480C36_8CE138FBUL }; + yield return new object[] { 0x2E0059A2_1C73F798UL, 0xAEC00000_0000038FUL, 0xB083D7E6_4738794BUL }; + yield return new object[] { 0xB4400000_00000000UL, 0xB3C00000_00000002UL, 0x32400000_00000000UL }; + yield return new object[] { 0x14200000_1E148863UL, 0xAF600000_00000003UL, 0x96800000_0A06D821UL }; + yield return new object[] { 0xB3000000_093826EBUL, 0x27600000_00000008UL, 0xBD000004_806B00BFUL }; + yield return new object[] { 0xBDA00000_0054E714UL, 0xAEC00000_00083858UL, 0x3EE3AB63_2787AAABUL }; + yield return new object[] { 0xBBE00000_00000008UL, 0xB1000000_00000008UL, 0x3CA00000_00000001UL }; + yield return new object[] { 0xD8600000_72FA8C0BUL, 0x30404C87_C53B7F29UL, 0xD76824FD_5DC6387AUL }; + yield return new object[] { 0x31E00000_00011A74UL, 0x31EB8DA8_BD290BE9UL, 0x2E87E644_72962839UL }; + yield return new object[] { 0xAE8000BE_61899933UL, 0x2E8E68EB_22D62BD8UL, 0xAF672984_2954D3E5UL }; + yield return new object[] { 0x30A00002_3C81B477UL, 0xB3400000_0000019EUL, 0xAE283E16_28D505EEUL }; + yield return new object[] { 0xAFC00016_E7160E75UL, 0x32600000_00000013UL, 0xAE72649C_3A014DDBUL }; + yield return new object[] { 0x328000C6_0667F8FFUL, 0x437376D5_46AB54AAUL, 0x1E8583E6_67068B52UL }; + yield return new object[] { 0x316008E3_5687A600UL, 0x32C00000_000000BCUL, 0x2FD277AE_2C929D9EUL }; + yield return new object[] { 0x32600011_10EB5AFDUL, 0xB1200000_70F0F514UL, 0xB14DBE35_89D2645BUL }; + yield return new object[] { 0x2F600000_0000018DUL, 0x34000000_000001F2UL, 0x2B3C5263_B59E7BE3UL }; + yield return new object[] { 0x3260573E_35DB7C8EUL, 0x328018AA_7862D5CEUL, 0x2FCC90DF_5F1B1DBCUL }; + yield return new object[] { 0x22C00000_00000005UL, 0xB0A00000_000004FDUL, 0xA1ADE90F_5674DD52UL }; + yield return new object[] { 0xD4200014_54458F18UL, 0xB2C00000_0000001DUL, 0x526AB24E_6D558412UL }; + yield return new object[] { 0x30200000_0000004CUL, 0x324199F5_8C48AAF6UL, 0x2C25FD76_8E35F895UL }; + yield return new object[] { 0xEB90A2E6_7C00A0EDUL, 0xB2C00000_000020F5UL, 0x2CE3DE44_E337731CUL }; + yield return new object[] { 0xB282B0EE_E060C24AUL, 0x30600000_00000008UL, 0xECE9A3A9_F4B97C9DUL }; + yield return new object[] { 0x2E20004A_73ED6947UL, 0x9C6F546E_573DB6B7UL, 0xC11A5413_6BDEAB49UL }; + yield return new object[] { 0xB1600000_00000054UL, 0x30800000_00000500UL, 0xB1E00000_00010059UL }; + yield return new object[] { 0xAA4042B6_7AA7818BUL, 0x31800000_00000055UL, 0xAA1EA890_D66400D2UL }; + yield return new object[] { 0xB2E00000_00000366UL, 0xB2C00000_00001F1DUL, 0x2FE3E16B_F510BDF3UL }; + yield return new object[] { 0x33000000_00000002UL, 0x33200000_02DF7470UL, 0x2ECEBDEE_F33FAD55UL }; + yield return new object[] { 0xB062FAF8_87469CE0UL, 0x30200000_00000045UL, 0xB1C451C1_3AC6CD18UL }; + yield return new object[] { 0xB0200000_000E661FUL, 0xB1000000_00076DFBUL, 0x2F06E2A3_F0EE7805UL }; + yield return new object[] { 0xA9800000_01ECEE93UL, 0x2DC04A74_C2BEB838UL, 0xAACE04F2_43140E20UL }; + yield return new object[] { 0x29200001_1579BC2EUL, 0xAFC058BC_B025DCC1UL, 0xA8B0F380_7A2A5151UL }; + yield return new object[] { 0xB3C00000_00000012UL, 0xD5600000_0000001EUL, 0x10000000_00000006UL }; + yield return new object[] { 0xAF400000_000003E0UL, 0xB2E00000_1892B9E7UL, 0x2B888C6C_1C593DF8UL }; + yield return new object[] { 0x32400000_1E910857UL, 0xB120000E_041E212AUL, 0xB0BE43D2_A6ACD64EUL }; + yield return new object[] { 0x2FE00000_36F3932DUL, 0x32000000_00003529UL, 0x2E58114F_9968A5F7UL }; + yield return new object[] { 0x32C00001_B3BF92FFUL, 0x2FC00000_02D2E250UL, 0x33257B7B_B885F394UL }; + } + + [Theory] + [MemberData(nameof(op_Division_TestData))] + public static void op_Division(ulong left, ulong right, ulong expected) + { + Decimal64 result = Unsafe.BitCast(left) / Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } From dcd5fb22999d912c342b7f5549114ec7680b9a1c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 9 Jul 2026 21:03:27 -0700 Subject: [PATCH 07/19] Implement increment and decrement for Decimal32/64/128 Adds the op_Increment (++) and op_Decrement (--) operators for the IEEE 754 decimal types. These are implemented as value + 1E0 / value - 1E0 by reusing the existing Number.AddDecimalIeee754 helper, matching the binary +/- operators. A OneValue constant (canonical 1E0) is added to each type. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/Decimal128.cs | 21 ++ .../src/System/Numerics/Decimal32.cs | 18 ++ .../src/System/Numerics/Decimal64.cs | 18 ++ .../System.Runtime/ref/System.Runtime.cs | 6 + .../System/Decimal128Tests.cs | 208 ++++++++++++++++++ .../System/Decimal32Tests.cs | 208 ++++++++++++++++++ .../System/Decimal64Tests.cs | 208 ++++++++++++++++++ 7 files changed, 687 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index 6eca24ff8bbb4d..b8d39a08ae802e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -33,6 +33,8 @@ public readonly struct Decimal128 // which stores zero with the biased exponent rather than the minimum exponent. private static UInt128 ZeroValue => new UInt128(0x3040_0000_0000_0000, 0); private static UInt128 NegativeZeroValue => new UInt128(0xB040_0000_0000_0000, 0); + // One (+1 * 10^0) shares the biased exponent of canonical zero with a coefficient of one. + private static UInt128 OneValue => new UInt128(0x3040_0000_0000_0000, 1); private static UInt128 QuietNaNValue => new UInt128(0xFC00_0000_0000_0000, 0); private const ulong SignMaskUpper = 0x8000_0000_0000_0000; @@ -262,6 +264,25 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The unary negation of . public static Decimal128 operator -(Decimal128 value) => new Decimal128(value._upper ^ SignMaskUpper, value._lower); + /// Increments a value. + /// The value to increment. + /// The result of incrementing by one. + public static Decimal128 operator ++(Decimal128 value) + { + UInt128 result = Number.AddDecimalIeee754(new UInt128(value._upper, value._lower), OneValue); + return new Decimal128(result); + } + + /// Decrements a value. + /// The value to decrement. + /// The result of decrementing by one. + public static Decimal128 operator --(Decimal128 value) + { + UInt128 negativeOne = new UInt128(OneValue.Upper ^ SignMaskUpper, OneValue.Lower); + UInt128 result = Number.AddDecimalIeee754(new UInt128(value._upper, value._lower), negativeOne); + return new Decimal128(result); + } + /// Adds two values together to compute their sum. /// The value to which is added. /// The value which is added to . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index 912133e435afa6..16b8b0639ad3a7 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -34,6 +34,8 @@ internal Decimal32(uint value) // which stores zero with the biased exponent rather than the minimum exponent. private const uint ZeroValue = 0x3280_0000; private const uint NegativeZeroValue = 0xB280_0000; + // One (+1 * 10^0) shares the biased exponent of canonical zero with a coefficient of one. + private const uint OneValue = ZeroValue | 0x1; private const uint QuietNaNValue = 0xFC00_0000; private const uint G0G1Mask = 0x6000_0000; private const uint SignMask = 0x8000_0000; @@ -268,6 +270,22 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The unary negation of . public static Decimal32 operator -(Decimal32 value) => new Decimal32(value._value ^ SignMask); + /// Increments a value. + /// The value to increment. + /// The result of incrementing by one. + public static Decimal32 operator ++(Decimal32 value) + { + return new Decimal32(Number.AddDecimalIeee754(value._value, OneValue)); + } + + /// Decrements a value. + /// The value to decrement. + /// The result of decrementing by one. + public static Decimal32 operator --(Decimal32 value) + { + return new Decimal32(Number.AddDecimalIeee754(value._value, OneValue ^ SignMask)); + } + /// Adds two values together to compute their sum. /// The value to which is added. /// The value which is added to . diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index 8747a8a36409c4..dbe51ca29661fb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -27,6 +27,8 @@ public readonly struct Decimal64 // which stores zero with the biased exponent rather than the minimum exponent. private const ulong ZeroValue = 0x31C0_0000_0000_0000; private const ulong NegativeZeroValue = 0xB1C0_0000_0000_0000; + // One (+1 * 10^0) shares the biased exponent of canonical zero with a coefficient of one. + private const ulong OneValue = ZeroValue | 0x1; private const ulong QuietNaNValue = 0xFC00_0000_0000_0000; private const ulong G0G1Mask = 0x6000_0000_0000_0000; private const ulong SignMask = 0x8000_0000_0000_0000; @@ -269,6 +271,22 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The unary negation of . public static Decimal64 operator -(Decimal64 value) => new Decimal64(value._value ^ SignMask); + /// Increments a value. + /// The value to increment. + /// The result of incrementing by one. + public static Decimal64 operator ++(Decimal64 value) + { + return new Decimal64(Number.AddDecimalIeee754(value._value, OneValue)); + } + + /// Decrements a value. + /// The value to decrement. + /// The result of decrementing by one. + public static Decimal64 operator --(Decimal64 value) + { + return new Decimal64(Number.AddDecimalIeee754(value._value, OneValue ^ SignMask)); + } + /// Adds two values together to compute their sum. /// The value to which is added. /// The value which is added to . diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 27d559ca80c20d..861bd9494fe774 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11406,6 +11406,8 @@ public readonly struct Decimal32 public static Decimal32 operator +(Decimal32 value) { throw null; } public static Decimal32 operator -(Decimal32 value) { throw null; } + public static Decimal32 operator ++(Decimal32 value) { throw null; } + public static Decimal32 operator --(Decimal32 value) { throw null; } public static Decimal32 operator +(Decimal32 left, Decimal32 right) { throw null; } public static Decimal32 operator -(Decimal32 left, Decimal32 right) { throw null; } public static Decimal32 operator *(Decimal32 left, Decimal32 right) { throw null; } @@ -11462,6 +11464,8 @@ public readonly struct Decimal64 public static Decimal64 operator +(Decimal64 value) { throw null; } public static Decimal64 operator -(Decimal64 value) { throw null; } + public static Decimal64 operator ++(Decimal64 value) { throw null; } + public static Decimal64 operator --(Decimal64 value) { throw null; } public static Decimal64 operator +(Decimal64 left, Decimal64 right) { throw null; } public static Decimal64 operator -(Decimal64 left, Decimal64 right) { throw null; } public static Decimal64 operator *(Decimal64 left, Decimal64 right) { throw null; } @@ -11518,6 +11522,8 @@ public readonly struct Decimal128 public static Decimal128 operator +(Decimal128 value) { throw null; } public static Decimal128 operator -(Decimal128 value) { throw null; } + public static Decimal128 operator ++(Decimal128 value) { throw null; } + public static Decimal128 operator --(Decimal128 value) { throw null; } public static Decimal128 operator +(Decimal128 left, Decimal128 right) { throw null; } public static Decimal128 operator -(Decimal128 left, Decimal128 right) { throw null; } public static Decimal128 operator *(Decimal128 left, Decimal128 right) { throw null; } 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 index add5c123174099..e629bf86be969e 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -1208,5 +1208,213 @@ public static void op_Division(UInt128 left, UInt128 right, UInt128 expected) Assert.Equal(expected, Unsafe.BitCast(result)); } + public static IEnumerable op_Increment_TestData() + { + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf (canonicalizes) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // +0 (++ -> 1, -- -> -1) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { new UInt128(0x304A000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 0e5 (preferred exponent min(exp,0)) + yield return new object[] { new UInt128(0x3036000000000000, 0x0000000000000000), new UInt128(0x3036000000000000, 0x00000000000186A0) }; // 0e-5 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002) }; // 1 (++ -> 2, -- -> 0) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000003) }; // 2 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000002), new UInt128(0xB040000000000000, 0x0000000000000001) }; // -2 + yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x000000000000000B) }; // 10 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000005), new UInt128(0x303E000000000000, 0x000000000000000F) }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { new UInt128(0xB03E000000000000, 0x0000000000000005), new UInt128(0x303E000000000000, 0x0000000000000005) }; // -0.5 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000019), new UInt128(0x303E000000000000, 0x0000000000000023) }; // 2.5 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x000000000000000B) }; // 0.1 (++ -> 1.1) + yield return new object[] { new UInt128(0x303C000000000000, 0x0000000000000177), new UInt128(0x303C000000000000, 0x00000000000001DB) }; // 3.75 + yield return new object[] { new UInt128(0xB03C000000000000, 0x0000000000000019), new UInt128(0x303C000000000000, 0x000000000000004B) }; // -0.25 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000009), new UInt128(0x3040000000000000, 0x000000000000000A) }; // 9 (++ -> 10) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3042314DC6448D93, 0x38C15B0A00000000) }; // all-nines (++ overflows precision, rounds) + yield return new object[] { new UInt128(0x3040314DC6448D93, 0x38C15B0A00000000), new UInt128(0x3040314DC6448D93, 0x38C15B0A00000001) }; // 10^(P-1) + yield return new object[] { new UInt128(0x3040314DC6448D93, 0x38C15B09FFFFFFFF), new UInt128(0x3040314DC6448D93, 0x38C15B0A00000000) }; // (P-1)-nines + yield return new object[] { new UInt128(0x3088000000000000, 0x0000000000000001), new UInt128(0x3046314DC6448D93, 0x38C15B0A00000000) }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000) }; // near-max (1 negligible) + yield return new object[] { new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000) }; // near -max (1 negligible) + yield return new object[] { new UInt128(0x0040000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // tiny subnormal (++ ~ 1) + yield return new object[] { new UInt128(0x8040000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // tiny negative subnormal (-- ~ -1) + yield return new object[] { new UInt128(0x3036000000000000, 0x0000000000000001), new UInt128(0x3036000000000000, 0x00000000000186A1) }; // 1e-5 + yield return new object[] { new UInt128(0x2FFE41BD085B676E, 0xF657240D55555555), new UInt128(0x2FFE730ACE9FF502, 0x2F187F1755555555) }; // 1.333... full precision + yield return new object[] { new UInt128(0x2FFFED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3000363BF3B1CEEE, 0xBE6E4A8B00000000) }; // 9.999... full precision (++ carry) + yield return new object[] { new UInt128(0xB0000000000000B1, 0x4C1E4DF5ED261C68), new UInt128(0x300004EE2D6D40AA, 0x398EA18B12D9E398) }; + yield return new object[] { new UInt128(0xB02C000000008117, 0x952633E9760D3996), new UInt128(0xB02C000000008117, 0x952633E722015596) }; + yield return new object[] { new UInt128(0xAFF80000000000BC, 0x57570583228BEDE5), new UInt128(0x2FFDED09BEAD87BE, 0x556649372B2CAE43) }; + yield return new object[] { new UInt128(0x27C800000029E4B4, 0x709EF360CF7CC897), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0x3062000000000000, 0x0000000000000006), new UInt128(0x3040000000000000, 0x0853A0D2313C0001) }; + yield return new object[] { new UInt128(0xB02C000000000000, 0x0000467047DBE843), new UInt128(0xB02C000000000000, 0x0000466DF3D00443) }; + yield return new object[] { new UInt128(0x301204EA7A9BDA00, 0xB6F5E4F86A521036), new UInt128(0x301204EA7A9BEF2D, 0xB9BDC64360D21036) }; + yield return new object[] { new UInt128(0x30020000000085EF, 0xF5A15252168838A9), new UInt128(0x3002007E37BEA612, 0xB6329D78968838A9) }; + yield return new object[] { new UInt128(0x3018000000000000, 0x5DF1A27C94EB2D4F), new UInt128(0x3018000000000005, 0xC9B900A9F7FB2D4F) }; + yield return new object[] { new UInt128(0x304C000000000000, 0x0000000000000009), new UInt128(0x3040000000000000, 0x0000000000895441) }; + yield return new object[] { new UInt128(0xB0180000003AF28B, 0xE740E213A5668D80), new UInt128(0xB0180000003AF286, 0x7B7983E642568D80) }; + yield return new object[] { new UInt128(0xB01E000000000000, 0x0000000616E4354B), new UInt128(0x301E000000000000, 0x0163457246A5CAB5) }; + yield return new object[] { new UInt128(0x3024000000000000, 0x0000000000000005), new UInt128(0x3024000000000000, 0x00005AF3107A4005) }; + yield return new object[] { new UInt128(0xCA2C00012586FEF1, 0xA857CDDF6237FE18), new UInt128(0xCA23BFE31CC37D7E, 0xFA82FB3EB0175F00) }; + yield return new object[] { new UInt128(0xB014000000000000, 0x00000000293EF566), new UInt128(0x301400000000021E, 0x19E0C9BA89010A9A) }; + yield return new object[] { new UInt128(0x81D4000000000000, 0x5D3FA1D19308E202), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xB038000000000000, 0x0000000004FDAAAC), new UInt128(0xB038000000000000, 0x0000000004FD839C) }; + yield return new object[] { new UInt128(0xB6D2000000000000, 0x000000000000001D), new UInt128(0xB6928EFB2560675E, 0x2497219D00000000) }; + yield return new object[] { new UInt128(0x3026000101B0C4EA, 0xAB917F63F245E3FF), new UInt128(0x3026000101B0C4EA, 0xAB91887C40B883FF) }; + yield return new object[] { new UInt128(0xB0140000BC4F01DB, 0x04D54985FC8DE0DB), new UInt128(0xB0140000BC4EFFBC, 0xEAF47FCB4A4DE0DB) }; + yield return new object[] { new UInt128(0x3008000000000000, 0x000007AFAEE4D58A), new UInt128(0x30080000204FCE5E, 0x3E250A10BEE4D58A) }; + yield return new object[] { new UInt128(0x84840000C3B35E24, 0xD944C9B454C06345), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xD9EE000000000000, 0x000000000000005A), new UInt128(0xD9AFBBBBF868FA2C, 0xFECC335A00000000) }; + yield return new object[] { new UInt128(0x2FF6000000000007, 0x1BF19A20781F7D89), new UInt128(0x2FFE314DC6448D93, 0x38EFF238F422346A) }; + yield return new object[] { new UInt128(0x301C0000086A265B, 0x3BC9B1C066BAF9CB), new UInt128(0x301C0000086A265B, 0x49AA68740E1EF9CB) }; + yield return new object[] { new UInt128(0x3030000000000000, 0x000000002A8CA672), new UInt128(0x3030000000000000, 0x0000000030828772) }; + yield return new object[] { new UInt128(0x2FF229E50E8F6A9F, 0xAE76568536C55686), new UInt128(0x2FFE314DC9036E2B, 0xC0531A6B0824E929) }; + yield return new object[] { new UInt128(0x4BF6000000000000, 0x0000000000015DFE), new UInt128(0x4BBDB9C093345A45, 0x21A2D72AC0000000) }; + yield return new object[] { new UInt128(0x3010000001BB3680, 0x155F5B37B10809BD), new UInt128(0x3010000001BC0A42, 0x312E2825520809BD) }; + yield return new object[] { new UInt128(0xAFF8000000000056, 0xAD502213EE481751), new UInt128(0x2FFDED09BEAD87BF, 0x59A8EA35B87F4772) }; + yield return new object[] { new UInt128(0x2FF20060F3D32F96, 0xC08CDAE35E8F6169), new UInt128(0x2FFE314DC64AE82A, 0xC92FA2EF72CB9C9E) }; + yield return new object[] { new UInt128(0x301C000000000000, 0x0A29F51D6081C988), new UInt128(0x301C000000000000, 0x180AABD107E5C988) }; + yield return new object[] { new UInt128(0xAFFA000000046488, 0x16C345A9670E0BDD), new UInt128(0x2FFDED09BEAD174C, 0x3546D43975B1CB9D) }; + yield return new object[] { new UInt128(0x2CFA000000000000, 0x00000000003305E2), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0x9646000000F92EA4, 0x0C7335403F583DB1), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xB02C005606ABA329, 0xA60456ECE8C0B9E7), new UInt128(0xB02C005606ABA329, 0xA60456EA94B4D5E7) }; + yield return new object[] { new UInt128(0xB05E000000000000, 0x0000000000000014), new UInt128(0xB040000000000000, 0x00470DE4DF81FFFF) }; + yield return new object[] { new UInt128(0x300A000000000000, 0x004433E77852218C), new UInt128(0x300A0000033B2E3C, 0xA014B4246052218C) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x0000000000000003), new UInt128(0x301A000000000000, 0x8AC7230489E7FFFD) }; + yield return new object[] { new UInt128(0x303A000000000000, 0x0132602F948B8E0D), new UInt128(0x303A000000000000, 0x0132602F948B91F5) }; + yield return new object[] { new UInt128(0xB00000000016390D, 0xBD0D18E1CAF1E112), new UInt128(0x300004EE2D57084D, 0xC89FD69F350E1EEE) }; + yield return new object[] { new UInt128(0x301A000000000000, 0x00000793A637ABF6), new UInt128(0x301A000000000000, 0x8AC72A98301FABF6) }; + yield return new object[] { new UInt128(0x8EA6000000000000, 0x0000000000000037), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xB016000000000175, 0x8A684F948BA6A804), new UInt128(0xB01600000000013F, 0x549EA1CEAD06A804) }; + yield return new object[] { new UInt128(0xB0260000C03688D2, 0x7AC2450F1321F8C8), new UInt128(0xB0260000C03688D2, 0x7AC23BF6C4AF58C8) }; + yield return new object[] { new UInt128(0x5402000000000000, 0x0000000CF771D12A), new UInt128(0x53D51293F938DCC4, 0xF7A5780AF1000000) }; + yield return new object[] { new UInt128(0xB00404F6A0D84B65, 0x900E7A5542B364B4), new UInt128(0xB00404EA01ABAE95, 0x49998C6B02B364B4) }; + yield return new object[] { new UInt128(0x3006000000000000, 0x07737E4214E44C17), new UInt128(0x30060001431E0FAE, 0x74E5960CB4E44C17) }; + yield return new object[] { new UInt128(0xB02C00013631CF73, 0x2562CDE5A526311A), new UInt128(0xB02C00013631CF73, 0x2562CDE3511A4D1A) }; + yield return new object[] { new UInt128(0x3000000000000000, 0x000DC3F18ACD96FF), new UInt128(0x300004EE2D6D415B, 0x85BAB3728ACD96FF) }; + yield return new object[] { new UInt128(0xB05A000000000000, 0x000000000000002F), new UInt128(0xB040000000000000, 0x0001AB76670B5FFF) }; + yield return new object[] { new UInt128(0x56600000000000DA, 0xB478C74AE7160E75), new UInt128(0x5648C6E937EC9E0B, 0x7316FDA996505000) }; + yield return new object[] { new UInt128(0x30CA000000000000, 0x027CB1C80FA6BE2D), new UInt128(0x30AA585BEE5EDC33, 0xD9EF6DB4E2ED0000) }; + yield return new object[] { new UInt128(0x30120000000002CF, 0x9740029D9E1DA392), new UInt128(0x30120000000017FC, 0x9A07E3E8949DA392) }; + yield return new object[] { new UInt128(0x30111C665687A600, 0x659DFC592140D366), new UInt128(0x30111C66568879C2, 0x816CC946C240D366) }; + yield return new object[] { new UInt128(0x3048000000000000, 0x00000000001799B2), new UInt128(0x3040000000000000, 0x0000000399E3B921) }; + yield return new object[] { new UInt128(0x9028000000000026, 0x8DD5910A10EB5AFD), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xAFEA00008584F9E7, 0xA777461B117E33B1), new UInt128(0x2FFDED09BEAD87BD, 0xFA17619EAFF9BE0F) }; + yield return new object[] { new UInt128(0xB03A000000000000, 0x00033EF697D6BABE), new UInt128(0xB03A000000000000, 0x00033EF697D6B6D6) }; + yield return new object[] { new UInt128(0x3032000000000000, 0x000049D9BB8E8CD9), new UInt128(0x3032000000000000, 0x000049D9BC272359) }; + } + + [Theory] + [MemberData(nameof(op_Increment_TestData))] + public static void op_Increment(UInt128 value, UInt128 expected) + { + Decimal128 result = Unsafe.BitCast(value); + result++; + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + public static IEnumerable op_Decrement_TestData() + { + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf (canonicalizes) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001) }; // +0 (++ -> 1, -- -> -1) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001) }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { new UInt128(0x304A000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001) }; // 0e5 (preferred exponent min(exp,0)) + yield return new object[] { new UInt128(0x3036000000000000, 0x0000000000000000), new UInt128(0xB036000000000000, 0x00000000000186A0) }; // 0e-5 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000) }; // 1 (++ -> 2, -- -> 0) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000002) }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 2 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000002), new UInt128(0xB040000000000000, 0x0000000000000003) }; // -2 + yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x0000000000000009) }; // 10 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000005), new UInt128(0xB03E000000000000, 0x0000000000000005) }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { new UInt128(0xB03E000000000000, 0x0000000000000005), new UInt128(0xB03E000000000000, 0x000000000000000F) }; // -0.5 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000019), new UInt128(0x303E000000000000, 0x000000000000000F) }; // 2.5 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0xB03E000000000000, 0x0000000000000009) }; // 0.1 (++ -> 1.1) + yield return new object[] { new UInt128(0x303C000000000000, 0x0000000000000177), new UInt128(0x303C000000000000, 0x0000000000000113) }; // 3.75 + yield return new object[] { new UInt128(0xB03C000000000000, 0x0000000000000019), new UInt128(0xB03C000000000000, 0x000000000000007D) }; // -0.25 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000009), new UInt128(0x3040000000000000, 0x0000000000000008) }; // 9 (++ -> 10) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE) }; // all-nines (++ overflows precision, rounds) + yield return new object[] { new UInt128(0x3040314DC6448D93, 0x38C15B0A00000000), new UInt128(0x3040314DC6448D93, 0x38C15B09FFFFFFFF) }; // 10^(P-1) + yield return new object[] { new UInt128(0x3040314DC6448D93, 0x38C15B09FFFFFFFF), new UInt128(0x3040314DC6448D93, 0x38C15B09FFFFFFFE) }; // (P-1)-nines + yield return new object[] { new UInt128(0x3088000000000000, 0x0000000000000001), new UInt128(0x3046314DC6448D93, 0x38C15B0A00000000) }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000) }; // near-max (1 negligible) + yield return new object[] { new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000) }; // near -max (1 negligible) + yield return new object[] { new UInt128(0x0040000000000000, 0x0000000000000001), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; // tiny subnormal (++ ~ 1) + yield return new object[] { new UInt128(0x8040000000000000, 0x0000000000000001), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; // tiny negative subnormal (-- ~ -1) + yield return new object[] { new UInt128(0x3036000000000000, 0x0000000000000001), new UInt128(0xB036000000000000, 0x000000000001869F) }; // 1e-5 + yield return new object[] { new UInt128(0x2FFE41BD085B676E, 0xF657240D55555555), new UInt128(0x2FFE106F4216D9DB, 0xBD95C90355555555) }; // 1.333... full precision + yield return new object[] { new UInt128(0x2FFFED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x2FFFBBBBF868FA2C, 0xFECC3359FFFFFFFF) }; // 9.999... full precision (++ carry) + yield return new object[] { new UInt128(0xB0000000000000B1, 0x4C1E4DF5ED261C68), new UInt128(0xB00004EE2D6D420C, 0xD1CB3D76ED261C68) }; + yield return new object[] { new UInt128(0xB02C000000008117, 0x952633E9760D3996), new UInt128(0xB02C000000008117, 0x952633EBCA191D96) }; + yield return new object[] { new UInt128(0xAFF80000000000BC, 0x57570583228BEDE5), new UInt128(0xAFFE314DC6448D93, 0x68F87B8E7BAEBB60) }; + yield return new object[] { new UInt128(0x27C800000029E4B4, 0x709EF360CF7CC897), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0x3062000000000000, 0x0000000000000006), new UInt128(0x3040000000000000, 0x0853A0D2313BFFFF) }; + yield return new object[] { new UInt128(0xB02C000000000000, 0x0000467047DBE843), new UInt128(0xB02C000000000000, 0x000046729BE7CC43) }; + yield return new object[] { new UInt128(0x301204EA7A9BDA00, 0xB6F5E4F86A521036), new UInt128(0x301204EA7A9BC4D3, 0xB42E03AD73D21036) }; + yield return new object[] { new UInt128(0x30020000000085EF, 0xF5A15252168838A9), new UInt128(0xB002007E37BD9A32, 0xCAEFF8D46977C757) }; + yield return new object[] { new UInt128(0x3018000000000000, 0x5DF1A27C94EB2D4F), new UInt128(0xB018000000000005, 0x0DD5BBB0CE24D2B1) }; + yield return new object[] { new UInt128(0x304C000000000000, 0x0000000000000009), new UInt128(0x3040000000000000, 0x000000000089543F) }; + yield return new object[] { new UInt128(0xB0180000003AF28B, 0xE740E213A5668D80), new UInt128(0xB0180000003AF291, 0x5308404108768D80) }; + yield return new object[] { new UInt128(0xB01E000000000000, 0x0000000616E4354B), new UInt128(0xB01E000000000000, 0x0163457E746E354B) }; + yield return new object[] { new UInt128(0x3024000000000000, 0x0000000000000005), new UInt128(0xB024000000000000, 0x00005AF3107A3FFB) }; + yield return new object[] { new UInt128(0xCA2C00012586FEF1, 0xA857CDDF6237FE18), new UInt128(0xCA23BFE31CC37D7E, 0xFA82FB3EB0175F00) }; + yield return new object[] { new UInt128(0xB014000000000000, 0x00000000293EF566), new UInt128(0xB01400000000021E, 0x19E0C9BADB7EF566) }; + yield return new object[] { new UInt128(0x81D4000000000000, 0x5D3FA1D19308E202), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xB038000000000000, 0x0000000004FDAAAC), new UInt128(0xB038000000000000, 0x0000000004FDD1BC) }; + yield return new object[] { new UInt128(0xB6D2000000000000, 0x000000000000001D), new UInt128(0xB6928EFB2560675E, 0x2497219D00000000) }; + yield return new object[] { new UInt128(0x3026000101B0C4EA, 0xAB917F63F245E3FF), new UInt128(0x3026000101B0C4EA, 0xAB91764BA3D343FF) }; + yield return new object[] { new UInt128(0xB0140000BC4F01DB, 0x04D54985FC8DE0DB), new UInt128(0xB0140000BC4F03F9, 0x1EB61340AECDE0DB) }; + yield return new object[] { new UInt128(0x3008000000000000, 0x000007AFAEE4D58A), new UInt128(0xB0080000204FCE5E, 0x3E24FAB1611B2A76) }; + yield return new object[] { new UInt128(0x84840000C3B35E24, 0xD944C9B454C06345), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xD9EE000000000000, 0x000000000000005A), new UInt128(0xD9AFBBBBF868FA2C, 0xFECC335A00000000) }; + yield return new object[] { new UInt128(0x2FF6000000000007, 0x1BF19A20781F7D89), new UInt128(0xAFFDED09BEAD87C0, 0x35BBA68E76A9F3D8) }; + yield return new object[] { new UInt128(0x301C0000086A265B, 0x3BC9B1C066BAF9CB), new UInt128(0x301C0000086A265B, 0x2DE8FB0CBF56F9CB) }; + yield return new object[] { new UInt128(0x3030000000000000, 0x000000002A8CA672), new UInt128(0x3030000000000000, 0x000000002496C572) }; + yield return new object[] { new UInt128(0x2FF229E50E8F6A9F, 0xAE76568536C55686), new UInt128(0xAFFDED09A338C1CA, 0xEBDC1499AE8EE461) }; + yield return new object[] { new UInt128(0x4BF6000000000000, 0x0000000000015DFE), new UInt128(0x4BBDB9C093345A45, 0x21A2D72AC0000000) }; + yield return new object[] { new UInt128(0x3010000001BB3680, 0x155F5B37B10809BD), new UInt128(0x3010000001BA62BD, 0xF9908E4A100809BD) }; + yield return new object[] { new UInt128(0xAFF8000000000056, 0xAD502213EE481751), new UInt128(0xAFFE314DC6448D93, 0x4EF1D1DB6D8CDF41) }; + yield return new object[] { new UInt128(0x2FF20060F3D32F96, 0xC08CDAE35E8F6169), new UInt128(0xAFFDED09BE6DFDD4, 0x933EBF6D840BE1D0) }; + yield return new object[] { new UInt128(0x301C000000000000, 0x0A29F51D6081C988), new UInt128(0xB01C000000000000, 0x03B6C19646E23678) }; + yield return new object[] { new UInt128(0xAFFA000000046488, 0x16C345A9670E0BDD), new UInt128(0xAFFE314DC64498D2, 0x05C86DA7DAA16BA3) }; + yield return new object[] { new UInt128(0x2CFA000000000000, 0x00000000003305E2), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0x9646000000F92EA4, 0x0C7335403F583DB1), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xB02C005606ABA329, 0xA60456ECE8C0B9E7), new UInt128(0xB02C005606ABA329, 0xA60456EF3CCC9DE7) }; + yield return new object[] { new UInt128(0xB05E000000000000, 0x0000000000000014), new UInt128(0xB040000000000000, 0x00470DE4DF820001) }; + yield return new object[] { new UInt128(0x300A000000000000, 0x004433E77852218C), new UInt128(0xB00A0000033B2E3C, 0x9F8C4C556FADDE74) }; + yield return new object[] { new UInt128(0xB01A000000000000, 0x0000000000000003), new UInt128(0xB01A000000000000, 0x8AC7230489E80003) }; + yield return new object[] { new UInt128(0x303A000000000000, 0x0132602F948B8E0D), new UInt128(0x303A000000000000, 0x0132602F948B8A25) }; + yield return new object[] { new UInt128(0xB00000000016390D, 0xBD0D18E1CAF1E112), new UInt128(0xB00004EE2D837A69, 0x42BA0862CAF1E112) }; + yield return new object[] { new UInt128(0x301A000000000000, 0x00000793A637ABF6), new UInt128(0xB01A000000000000, 0x8AC71B70E3B0540A) }; + yield return new object[] { new UInt128(0x8EA6000000000000, 0x0000000000000037), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xB016000000000175, 0x8A684F948BA6A804), new UInt128(0xB0160000000001AB, 0xC031FD5A6A46A804) }; + yield return new object[] { new UInt128(0xB0260000C03688D2, 0x7AC2450F1321F8C8), new UInt128(0xB0260000C03688D2, 0x7AC24E27619498C8) }; + yield return new object[] { new UInt128(0x5402000000000000, 0x0000000CF771D12A), new UInt128(0x53D51293F938DCC4, 0xF7A5780AF1000000) }; + yield return new object[] { new UInt128(0xB00404F6A0D84B65, 0x900E7A5542B364B4), new UInt128(0xB00405034004E835, 0xD683683F82B364B4) }; + yield return new object[] { new UInt128(0x3006000000000000, 0x07737E4214E44C17), new UInt128(0xB0060001431E0FAE, 0x65FE99888B1BB3E9) }; + yield return new object[] { new UInt128(0xB02C00013631CF73, 0x2562CDE5A526311A), new UInt128(0xB02C00013631CF73, 0x2562CDE7F932151A) }; + yield return new object[] { new UInt128(0x3000000000000000, 0x000DC3F18ACD96FF), new UInt128(0xB00004EE2D6D415B, 0x859F2B8F75326901) }; + yield return new object[] { new UInt128(0xB05A000000000000, 0x000000000000002F), new UInt128(0xB040000000000000, 0x0001AB76670B6001) }; + yield return new object[] { new UInt128(0x56600000000000DA, 0xB478C74AE7160E75), new UInt128(0x5648C6E937EC9E0B, 0x7316FDA996505000) }; + yield return new object[] { new UInt128(0x30CA000000000000, 0x027CB1C80FA6BE2D), new UInt128(0x30AA585BEE5EDC33, 0xD9EF6DB4E2ED0000) }; + yield return new object[] { new UInt128(0x30120000000002CF, 0x9740029D9E1DA392), new UInt128(0xB01200000000125D, 0x6B87DEAD58625C6E) }; + yield return new object[] { new UInt128(0x30111C665687A600, 0x659DFC592140D366), new UInt128(0x30111C665686D23E, 0x49CF2F6B8040D366) }; + yield return new object[] { new UInt128(0x3048000000000000, 0x00000000001799B2), new UInt128(0x3040000000000000, 0x0000000399E3B91F) }; + yield return new object[] { new UInt128(0x9028000000000026, 0x8DD5910A10EB5AFD), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; + yield return new object[] { new UInt128(0xAFEA00008584F9E7, 0xA777461B117E33B1), new UInt128(0xAFFE314DC6448D93, 0x7219F91DBB33D365) }; + yield return new object[] { new UInt128(0xB03A000000000000, 0x00033EF697D6BABE), new UInt128(0xB03A000000000000, 0x00033EF697D6BEA6) }; + yield return new object[] { new UInt128(0x3032000000000000, 0x000049D9BB8E8CD9), new UInt128(0x3032000000000000, 0x000049D9BAF5F659) }; + } + + [Theory] + [MemberData(nameof(op_Decrement_TestData))] + public static void op_Decrement(UInt128 value, UInt128 expected) + { + Decimal128 result = Unsafe.BitCast(value); + result--; + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } 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 index 694617979d721c..974de18da82aab 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -1208,5 +1208,213 @@ public static void op_Division(uint left, uint right, uint expected) Assert.Equal(expected, Unsafe.BitCast(result)); } + public static IEnumerable op_Increment_TestData() + { + yield return new object[] { 0xFC000000U, 0xFC000000U }; // NaN + yield return new object[] { 0x78000000U, 0x78000000U }; // +Inf + yield return new object[] { 0xF8000000U, 0xF8000000U }; // -Inf + yield return new object[] { 0x78000002U, 0x78000000U }; // non-canonical +Inf (canonicalizes) + yield return new object[] { 0x32800000U, 0x32800001U }; // +0 (++ -> 1, -- -> -1) + yield return new object[] { 0xB2800000U, 0x32800001U }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { 0x35000000U, 0x32800001U }; // 0e5 (preferred exponent min(exp,0)) + yield return new object[] { 0x30000000U, 0x300186A0U }; // 0e-5 + yield return new object[] { 0x32800001U, 0x32800002U }; // 1 (++ -> 2, -- -> 0) + yield return new object[] { 0xB2800001U, 0x32800000U }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { 0x32800002U, 0x32800003U }; // 2 + yield return new object[] { 0xB2800002U, 0xB2800001U }; // -2 + yield return new object[] { 0x3280000AU, 0x3280000BU }; // 10 + yield return new object[] { 0x32000005U, 0x3200000FU }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { 0xB2000005U, 0x32000005U }; // -0.5 + yield return new object[] { 0x32000019U, 0x32000023U }; // 2.5 + yield return new object[] { 0x32000001U, 0x3200000BU }; // 0.1 (++ -> 1.1) + yield return new object[] { 0x31800177U, 0x318001DBU }; // 3.75 + yield return new object[] { 0xB1800019U, 0x3180004BU }; // -0.25 + yield return new object[] { 0x32800009U, 0x3280000AU }; // 9 (++ -> 10) + yield return new object[] { 0x6CB8967FU, 0x330F4240U }; // all-nines (++ overflows precision, rounds) + yield return new object[] { 0x328F4240U, 0x328F4241U }; // 10^(P-1) + yield return new object[] { 0x328F423FU, 0x328F4240U }; // (P-1)-nines + yield return new object[] { 0x37000001U, 0x340F4240U }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { 0x77E95440U, 0x77E95440U }; // near-max (1 negligible) + yield return new object[] { 0xF7E95440U, 0xF7E95440U }; // near -max (1 negligible) + yield return new object[] { 0x02800001U, 0x2F8F4240U }; // tiny subnormal (++ ~ 1) + yield return new object[] { 0x82800001U, 0x2F8F4240U }; // tiny negative subnormal (-- ~ -1) + yield return new object[] { 0x30000001U, 0x300186A1U }; // 1e-5 + yield return new object[] { 0x2F945855U, 0x2FA39A95U }; // 1.333... full precision + yield return new object[] { 0x6BF8967FU, 0x3010C8E0U }; // 9.999... full precision (++ carry) + yield return new object[] { 0x2F2C8C35U, 0x2F93B6ACU }; + yield return new object[] { 0x2A00002DU, 0x2F8F4240U }; + yield return new object[] { 0xB8002104U, 0xEDA0F7A0U }; + yield return new object[] { 0xB98043D6U, 0xB89A7F98U }; + yield return new object[] { 0xA8800010U, 0x2F8F4240U }; + yield return new object[] { 0x34802213U, 0x6CC51A38U }; + yield return new object[] { 0x318496C5U, 0x31849729U }; + yield return new object[] { 0xA71C20DCU, 0x2F8F4240U }; + yield return new object[] { 0x2E830F1EU, 0x2F8F4A15U }; + yield return new object[] { 0xA9000005U, 0x2F8F4240U }; + yield return new object[] { 0xAD00006CU, 0x2F8F4240U }; + yield return new object[] { 0xB7011BA9U, 0xB66ECE04U }; + yield return new object[] { 0xAC000017U, 0x2F8F4240U }; + yield return new object[] { 0x380B7DD1U, 0x37F2EA2AU }; + yield return new object[] { 0xBC800009U, 0xEE695440U }; + yield return new object[] { 0xA600170BU, 0x2F8F4240U }; + yield return new object[] { 0x1C8C3773U, 0x2F8F4240U }; + yield return new object[] { 0x4C800FA6U, 0x4B3D2070U }; + yield return new object[] { 0xB2000007U, 0x32000003U }; + yield return new object[] { 0xEAD57BF3U, 0x2F8F4240U }; + yield return new object[] { 0xBA000010U, 0xB7986A00U }; + yield return new object[] { 0x2F0ED152U, 0x2F90BD95U }; + yield return new object[] { 0x6A41D272U, 0x2F8F4240U }; + yield return new object[] { 0xA8800006U, 0x2F8F4240U }; + yield return new object[] { 0xAC0000EBU, 0x2F8F4240U }; + yield return new object[] { 0x28800027U, 0x2F8F4240U }; + yield return new object[] { 0xAB8B81BBU, 0x2F8F4240U }; + yield return new object[] { 0xB0000044U, 0x3001865CU }; + yield return new object[] { 0x390001B3U, 0x37426030U }; + yield return new object[] { 0xB885B1AEU, 0xB838F0CCU }; + yield return new object[] { 0x3B800006U, 0x38DB8D80U }; + yield return new object[] { 0x27007EF8U, 0x2F8F4240U }; + yield return new object[] { 0x27034AB4U, 0x2F8F4240U }; + yield return new object[] { 0x3280024CU, 0x3280024DU }; + yield return new object[] { 0x3B800094U, 0x39969540U }; + yield return new object[] { 0x3685C05CU, 0x36398398U }; + yield return new object[] { 0xC0001DD4U, 0xBEF48420U }; + yield return new object[] { 0x330000DDU, 0x328008A3U }; + yield return new object[] { 0x378000FAU, 0x35A625A0U }; + yield return new object[] { 0x9A02AE5DU, 0x2F8F4240U }; + yield return new object[] { 0x278014E4U, 0x2F8F4240U }; + yield return new object[] { 0x3806111AU, 0x37BCAB04U }; + yield return new object[] { 0xB20ACE79U, 0xB20ACE6FU }; + yield return new object[] { 0x3204CA4CU, 0x3204CA56U }; + yield return new object[] { 0xB98025B8U, 0xEE1356C0U }; + yield return new object[] { 0xB2000002U, 0x32000008U }; + yield return new object[] { 0xB780002BU, 0xB5419CE0U }; + yield return new object[] { 0x3B80007DU, 0x399312D0U }; + yield return new object[] { 0xDA81A30AU, 0xDA105E64U }; + yield return new object[] { 0x38002124U, 0x6DA174A0U }; + yield return new object[] { 0xB5800001U, 0xB28F423FU }; + yield return new object[] { 0x36000006U, 0x335B8D80U }; + yield return new object[] { 0xB8800004U, 0xB5BD0900U }; + yield return new object[] { 0x35297F01U, 0x35297F01U }; + yield return new object[] { 0xAF00252CU, 0x6BD87154U }; + yield return new object[] { 0xBF000006U, 0xBC5B8D80U }; + yield return new object[] { 0x92EA6215U, 0x2F8F4240U }; + yield return new object[] { 0x38800002U, 0x359E8480U }; + yield return new object[] { 0xAE800008U, 0x6BD8967FU }; + yield return new object[] { 0xAE00009FU, 0x6BD8967EU }; + } + + [Theory] + [MemberData(nameof(op_Increment_TestData))] + public static void op_Increment(uint value, uint expected) + { + Decimal32 result = Unsafe.BitCast(value); + result++; + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + public static IEnumerable op_Decrement_TestData() + { + yield return new object[] { 0xFC000000U, 0xFC000000U }; // NaN + yield return new object[] { 0x78000000U, 0x78000000U }; // +Inf + yield return new object[] { 0xF8000000U, 0xF8000000U }; // -Inf + yield return new object[] { 0x78000002U, 0x78000000U }; // non-canonical +Inf (canonicalizes) + yield return new object[] { 0x32800000U, 0xB2800001U }; // +0 (++ -> 1, -- -> -1) + yield return new object[] { 0xB2800000U, 0xB2800001U }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { 0x35000000U, 0xB2800001U }; // 0e5 (preferred exponent min(exp,0)) + yield return new object[] { 0x30000000U, 0xB00186A0U }; // 0e-5 + yield return new object[] { 0x32800001U, 0x32800000U }; // 1 (++ -> 2, -- -> 0) + yield return new object[] { 0xB2800001U, 0xB2800002U }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { 0x32800002U, 0x32800001U }; // 2 + yield return new object[] { 0xB2800002U, 0xB2800003U }; // -2 + yield return new object[] { 0x3280000AU, 0x32800009U }; // 10 + yield return new object[] { 0x32000005U, 0xB2000005U }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { 0xB2000005U, 0xB200000FU }; // -0.5 + yield return new object[] { 0x32000019U, 0x3200000FU }; // 2.5 + yield return new object[] { 0x32000001U, 0xB2000009U }; // 0.1 (++ -> 1.1) + yield return new object[] { 0x31800177U, 0x31800113U }; // 3.75 + yield return new object[] { 0xB1800019U, 0xB180007DU }; // -0.25 + yield return new object[] { 0x32800009U, 0x32800008U }; // 9 (++ -> 10) + yield return new object[] { 0x6CB8967FU, 0x6CB8967EU }; // all-nines (++ overflows precision, rounds) + yield return new object[] { 0x328F4240U, 0x328F423FU }; // 10^(P-1) + yield return new object[] { 0x328F423FU, 0x328F423EU }; // (P-1)-nines + yield return new object[] { 0x37000001U, 0x340F4240U }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { 0x77E95440U, 0x77E95440U }; // near-max (1 negligible) + yield return new object[] { 0xF7E95440U, 0xF7E95440U }; // near -max (1 negligible) + yield return new object[] { 0x02800001U, 0xAF8F4240U }; // tiny subnormal (++ ~ 1) + yield return new object[] { 0x82800001U, 0xAF8F4240U }; // tiny negative subnormal (-- ~ -1) + yield return new object[] { 0x30000001U, 0xB001869FU }; // 1e-5 + yield return new object[] { 0x2F945855U, 0x2F851615U }; // 1.333... full precision + yield return new object[] { 0x6BF8967FU, 0x6BE9543FU }; // 9.999... full precision (++ carry) + yield return new object[] { 0x2F2C8C35U, 0xAF6C0A4BU }; + yield return new object[] { 0x2A00002DU, 0xAF8F4240U }; + yield return new object[] { 0xB8002104U, 0xEDA0F7A0U }; + yield return new object[] { 0xB98043D6U, 0xB89A7F98U }; + yield return new object[] { 0xA8800010U, 0xAF8F4240U }; + yield return new object[] { 0x34802213U, 0x6CC51A38U }; + yield return new object[] { 0x318496C5U, 0x31849661U }; + yield return new object[] { 0xA71C20DCU, 0xAF8F4240U }; + yield return new object[] { 0x2E830F1EU, 0xEBD84830U }; + yield return new object[] { 0xA9000005U, 0xAF8F4240U }; + yield return new object[] { 0xAD00006CU, 0xAF8F4240U }; + yield return new object[] { 0xB7011BA9U, 0xB66ECE04U }; + yield return new object[] { 0xAC000017U, 0xAF8F4240U }; + yield return new object[] { 0x380B7DD1U, 0x37F2EA2AU }; + yield return new object[] { 0xBC800009U, 0xEE695440U }; + yield return new object[] { 0xA600170BU, 0xAF8F4240U }; + yield return new object[] { 0x1C8C3773U, 0xAF8F4240U }; + yield return new object[] { 0x4C800FA6U, 0x4B3D2070U }; + yield return new object[] { 0xB2000007U, 0xB2000011U }; + yield return new object[] { 0xEAD57BF3U, 0xAF8F4240U }; + yield return new object[] { 0xBA000010U, 0xB7986A00U }; + yield return new object[] { 0x2F0ED152U, 0xEBC9C52EU }; + yield return new object[] { 0x6A41D272U, 0xAF8F4240U }; + yield return new object[] { 0xA8800006U, 0xAF8F4240U }; + yield return new object[] { 0xAC0000EBU, 0xAF8F4240U }; + yield return new object[] { 0x28800027U, 0xAF8F4240U }; + yield return new object[] { 0xAB8B81BBU, 0xAF8F4240U }; + yield return new object[] { 0xB0000044U, 0xB00186E4U }; + yield return new object[] { 0x390001B3U, 0x37426030U }; + yield return new object[] { 0xB885B1AEU, 0xB838F0CCU }; + yield return new object[] { 0x3B800006U, 0x38DB8D80U }; + yield return new object[] { 0x27007EF8U, 0xAF8F4240U }; + yield return new object[] { 0x27034AB4U, 0xAF8F4240U }; + yield return new object[] { 0x3280024CU, 0x3280024BU }; + yield return new object[] { 0x3B800094U, 0x39969540U }; + yield return new object[] { 0x3685C05CU, 0x36398398U }; + yield return new object[] { 0xC0001DD4U, 0xBEF48420U }; + yield return new object[] { 0x330000DDU, 0x328008A1U }; + yield return new object[] { 0x378000FAU, 0x35A625A0U }; + yield return new object[] { 0x9A02AE5DU, 0xAF8F4240U }; + yield return new object[] { 0x278014E4U, 0xAF8F4240U }; + yield return new object[] { 0x3806111AU, 0x37BCAB04U }; + yield return new object[] { 0xB20ACE79U, 0xB20ACE83U }; + yield return new object[] { 0x3204CA4CU, 0x3204CA42U }; + yield return new object[] { 0xB98025B8U, 0xEE1356C0U }; + yield return new object[] { 0xB2000002U, 0xB200000CU }; + yield return new object[] { 0xB780002BU, 0xB5419CE0U }; + yield return new object[] { 0x3B80007DU, 0x399312D0U }; + yield return new object[] { 0xDA81A30AU, 0xDA105E64U }; + yield return new object[] { 0x38002124U, 0x6DA174A0U }; + yield return new object[] { 0xB5800001U, 0xB28F4241U }; + yield return new object[] { 0x36000006U, 0x335B8D80U }; + yield return new object[] { 0xB8800004U, 0xB5BD0900U }; + yield return new object[] { 0x35297F01U, 0x35297F01U }; + yield return new object[] { 0xAF00252CU, 0xAF8F45F8U }; + yield return new object[] { 0xBF000006U, 0xBC5B8D80U }; + yield return new object[] { 0x92EA6215U, 0xAF8F4240U }; + yield return new object[] { 0x38800002U, 0x359E8480U }; + yield return new object[] { 0xAE800008U, 0xAF8F4240U }; + yield return new object[] { 0xAE00009FU, 0xAF8F4240U }; + } + + [Theory] + [MemberData(nameof(op_Decrement_TestData))] + public static void op_Decrement(uint value, uint expected) + { + Decimal32 result = Unsafe.BitCast(value); + result--; + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } 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 index ca34384139bc03..9d8bdb9c47e1e3 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -1214,5 +1214,213 @@ public static void op_Division(ulong left, ulong right, ulong expected) Assert.Equal(expected, Unsafe.BitCast(result)); } + public static IEnumerable op_Increment_TestData() + { + yield return new object[] { 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // NaN + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL }; // +Inf + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL }; // -Inf + yield return new object[] { 0x78000000_00000002UL, 0x78000000_00000000UL }; // non-canonical +Inf (canonicalizes) + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000001UL }; // +0 (++ -> 1, -- -> -1) + yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000001UL }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { 0x32600000_00000000UL, 0x31C00000_00000001UL }; // 0e5 (preferred exponent min(exp,0)) + yield return new object[] { 0x31200000_00000000UL, 0x31200000_000186A0UL }; // 0e-5 + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL }; // 1 (++ -> 2, -- -> 0) + yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000000UL }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { 0x31C00000_00000002UL, 0x31C00000_00000003UL }; // 2 + yield return new object[] { 0xB1C00000_00000002UL, 0xB1C00000_00000001UL }; // -2 + yield return new object[] { 0x31C00000_0000000AUL, 0x31C00000_0000000BUL }; // 10 + yield return new object[] { 0x31A00000_00000005UL, 0x31A00000_0000000FUL }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { 0xB1A00000_00000005UL, 0x31A00000_00000005UL }; // -0.5 + yield return new object[] { 0x31A00000_00000019UL, 0x31A00000_00000023UL }; // 2.5 + yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_0000000BUL }; // 0.1 (++ -> 1.1) + yield return new object[] { 0x31800000_00000177UL, 0x31800000_000001DBUL }; // 3.75 + yield return new object[] { 0xB1800000_00000019UL, 0x31800000_0000004BUL }; // -0.25 + yield return new object[] { 0x31C00000_00000009UL, 0x31C00000_0000000AUL }; // 9 (++ -> 10) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x31E38D7E_A4C68000UL }; // all-nines (++ overflows precision, rounds) + yield return new object[] { 0x31C38D7E_A4C68000UL, 0x31C38D7E_A4C68001UL }; // 10^(P-1) + yield return new object[] { 0x31C38D7E_A4C67FFFUL, 0x31C38D7E_A4C68000UL }; // (P-1)-nines + yield return new object[] { 0x34000000_00000001UL, 0x32238D7E_A4C68000UL }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { 0x5FFFF973_CAFA8000UL, 0x5FFFF973_CAFA8000UL }; // near-max (1 negligible) + yield return new object[] { 0xDFFFF973_CAFA8000UL, 0xDFFFF973_CAFA8000UL }; // near -max (1 negligible) + yield return new object[] { 0x01C00000_00000001UL, 0x2FE38D7E_A4C68000UL }; // tiny subnormal (++ ~ 1) + yield return new object[] { 0x81C00000_00000001UL, 0x2FE38D7E_A4C68000UL }; // tiny negative subnormal (-- ~ -1) + yield return new object[] { 0x31200000_00000001UL, 0x31200000_000186A1UL }; // 1e-5 + yield return new object[] { 0x2FE4BCA8_DBB35555UL, 0x2FE84A27_8079D555UL }; // 1.333... full precision + yield return new object[] { 0x6BFB86F2_6FC0FFFFUL, 0x3003E871_B540C000UL }; // 9.999... full precision (++ carry) + yield return new object[] { 0xDE000D7B_C1739419UL, 0xDDC54457_9125D9C4UL }; + yield return new object[] { 0x33A00000_00000035UL, 0x31F2D452_694F4000UL }; + yield return new object[] { 0x32A00000_001AACBFUL, 0x31C00FE6_3FF64981UL }; + yield return new object[] { 0x31400000_0000C292UL, 0x31400000_0000E9A2UL }; + yield return new object[] { 0xB0E00000_00002125UL, 0x30E00000_0098755BUL }; + yield return new object[] { 0xB4200000_00000004UL, 0xB24E35FA_931A0000UL }; + yield return new object[] { 0x31C00000_06E71450UL, 0x31C00000_06E71451UL }; + yield return new object[] { 0xB2E00000_05A136AAUL, 0xEC798E4D_53BD6A00UL }; + yield return new object[] { 0x2FA00000_03D3544BUL, 0x2FE38D7E_A4D04B15UL }; + yield return new object[] { 0xB0C00000_0002002AUL, 0x30C00000_05F3E0D6UL }; + yield return new object[] { 0xB2604C77_D2CE2737UL, 0xB23DDECE_5887517CUL }; + yield return new object[] { 0x32600000_01C6C941UL, 0x31C002B5_F2D6CEA1UL }; + yield return new object[] { 0x30E00000_33939110UL, 0x30E00000_342C2790UL }; + yield return new object[] { 0x32C00000_5AC09507UL, 0x320568C5_11FA0FC0UL }; + yield return new object[] { 0xB06D3F1C_656F52FAUL, 0xB06D3F05_1CF86AFAUL }; + yield return new object[] { 0x31E00000_00000043UL, 0x31C00000_0000029FUL }; + yield return new object[] { 0xAE00C898_D85C6FF7UL, 0x6BF386F2_6FC0FFFEUL }; + yield return new object[] { 0xBE8CD924_B6560750UL, 0xBE8CD924_B6560750UL }; + yield return new object[] { 0x3102EDC1_D68C47BEUL, 0x3102EDC1_D69B89FEUL }; + yield return new object[] { 0xB0E00000_00000009UL, 0x30E00000_00989677UL }; + yield return new object[] { 0xB0600586_0DF1C1B6UL, 0xB060056E_C57AD9B6UL }; + yield return new object[] { 0x33000000_00000016UL, 0x31C00033_39059801UL }; + yield return new object[] { 0xB0C0007E_EB0B6EA0UL, 0xB0C0007E_E5158DA0UL }; + yield return new object[] { 0x2F400000_00000897UL, 0x2FE38D7E_A4C68000UL }; + yield return new object[] { 0x32A00000_000003E0UL, 0x31C00002_4F473001UL }; + yield return new object[] { 0xB2A00000_00000004UL, 0xB1C00000_026259FFUL }; + yield return new object[] { 0x3820253B_3AF58D7CUL, 0x37EE8B23_07EB4470UL }; + yield return new object[] { 0x32A00000_0060035AUL, 0x31C0393A_6F686901UL }; + yield return new object[] { 0x2EC00000_1B1D8BA6UL, 0x2FE38D7E_A4C68000UL }; + yield return new object[] { 0xB2800000_22850479UL, 0xB1C20EBA_2F7F503FUL }; + yield return new object[] { 0xB2200000_30F3774BUL, 0xB1C000BF_3709FCF7UL }; + yield return new object[] { 0xAF200000_042E8A44UL, 0x6BF386F2_6FC0FD42UL }; + yield return new object[] { 0x302012E4_B193AA00UL, 0x30201BFD_00064A00UL }; + yield return new object[] { 0xAEE00001_BE122682UL, 0x6BF386F2_6FC0FD14UL }; + yield return new object[] { 0x07600000_00000000UL, 0x2FE38D7E_A4C68000UL }; + yield return new object[] { 0xB2600000_0000D120UL, 0xB1C00001_3F1973FFUL }; + yield return new object[] { 0x2EC00008_5153EF36UL, 0x2FE38D7E_A4C68024UL }; + yield return new object[] { 0x2F200000_00000045UL, 0x2FE38D7E_A4C68000UL }; + yield return new object[] { 0x2FCDCB8B_5E0C05CAUL, 0x2FE4EEA6_2E2E1A2EUL }; + yield return new object[] { 0xBEE000E4_BBA70FA7UL, 0xEF9AE6E2_2DD36B70UL }; + yield return new object[] { 0x5B600000_000003D2UL, 0x7672BEDB_B1E74000UL }; + yield return new object[] { 0x2F600000_00004436UL, 0x2FE38D7E_A4C68002UL }; + yield return new object[] { 0x30600000_00833DA9UL, 0x30600017_48FA25A9UL }; + yield return new object[] { 0x2F200000_1228ADE2UL, 0x2FE38D7E_A4C68131UL }; + yield return new object[] { 0xAF600000_2AD1A374UL, 0x6BF386F2_6FB609D2UL }; + yield return new object[] { 0xB1600000_18006133UL, 0xB1600000_18005D4BUL }; + yield return new object[] { 0x6B5A3F19_333501CCUL, 0x2FE38D7E_A4C68000UL }; + yield return new object[] { 0x32E00000_001FC878UL, 0x31C7666B_545EB001UL }; + yield return new object[] { 0x31800000_B171BD11UL, 0x31800000_B171BD75UL }; + yield return new object[] { 0x2EE00001_723C5118UL, 0x2FE38D7E_A4C6803EUL }; + yield return new object[] { 0x9F400000_00001DF5UL, 0x2FE38D7E_A4C68000UL }; + yield return new object[] { 0xAEC00000_001D0824UL, 0x2FE38D7E_A4C68000UL }; + yield return new object[] { 0xAFA34A58_43C82F35UL, 0x6BF332B6_68F9C814UL }; + yield return new object[] { 0x19C00000_0006B3C8UL, 0x2FE38D7E_A4C68000UL }; + yield return new object[] { 0xB2400000_000014EDUL, 0xB1C00000_033169CFUL }; + yield return new object[] { 0x31200054_42297730UL, 0x31200054_422AFDD0UL }; + yield return new object[] { 0x2F000063_A23BC84AUL, 0x2FE38D7E_A4C72728UL }; + yield return new object[] { 0x2E60007D_E4F31691UL, 0x2FE38D7E_A4C68001UL }; + yield return new object[] { 0x328000E1_F407BFE1UL, 0x6C827A4C_6EB74510UL }; + yield return new object[] { 0x95400091_BAB57FB9UL, 0x2FE38D7E_A4C68000UL }; + } + + [Theory] + [MemberData(nameof(op_Increment_TestData))] + public static void op_Increment(ulong value, ulong expected) + { + Decimal64 result = Unsafe.BitCast(value); + result++; + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + public static IEnumerable op_Decrement_TestData() + { + yield return new object[] { 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // NaN + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL }; // +Inf + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL }; // -Inf + yield return new object[] { 0x78000000_00000002UL, 0x78000000_00000000UL }; // non-canonical +Inf (canonicalizes) + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000001UL }; // +0 (++ -> 1, -- -> -1) + yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000001UL }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { 0x32600000_00000000UL, 0xB1C00000_00000001UL }; // 0e5 (preferred exponent min(exp,0)) + yield return new object[] { 0x31200000_00000000UL, 0xB1200000_000186A0UL }; // 0e-5 + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000000UL }; // 1 (++ -> 2, -- -> 0) + yield return new object[] { 0xB1C00000_00000001UL, 0xB1C00000_00000002UL }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { 0x31C00000_00000002UL, 0x31C00000_00000001UL }; // 2 + yield return new object[] { 0xB1C00000_00000002UL, 0xB1C00000_00000003UL }; // -2 + yield return new object[] { 0x31C00000_0000000AUL, 0x31C00000_00000009UL }; // 10 + yield return new object[] { 0x31A00000_00000005UL, 0xB1A00000_00000005UL }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { 0xB1A00000_00000005UL, 0xB1A00000_0000000FUL }; // -0.5 + yield return new object[] { 0x31A00000_00000019UL, 0x31A00000_0000000FUL }; // 2.5 + yield return new object[] { 0x31A00000_00000001UL, 0xB1A00000_00000009UL }; // 0.1 (++ -> 1.1) + yield return new object[] { 0x31800000_00000177UL, 0x31800000_00000113UL }; // 3.75 + yield return new object[] { 0xB1800000_00000019UL, 0xB1800000_0000007DUL }; // -0.25 + yield return new object[] { 0x31C00000_00000009UL, 0x31C00000_00000008UL }; // 9 (++ -> 10) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFEUL }; // all-nines (++ overflows precision, rounds) + yield return new object[] { 0x31C38D7E_A4C68000UL, 0x31C38D7E_A4C67FFFUL }; // 10^(P-1) + yield return new object[] { 0x31C38D7E_A4C67FFFUL, 0x31C38D7E_A4C67FFEUL }; // (P-1)-nines + yield return new object[] { 0x34000000_00000001UL, 0x32238D7E_A4C68000UL }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { 0x5FFFF973_CAFA8000UL, 0x5FFFF973_CAFA8000UL }; // near-max (1 negligible) + yield return new object[] { 0xDFFFF973_CAFA8000UL, 0xDFFFF973_CAFA8000UL }; // near -max (1 negligible) + yield return new object[] { 0x01C00000_00000001UL, 0xAFE38D7E_A4C68000UL }; // tiny subnormal (++ ~ 1) + yield return new object[] { 0x81C00000_00000001UL, 0xAFE38D7E_A4C68000UL }; // tiny negative subnormal (-- ~ -1) + yield return new object[] { 0x31200000_00000001UL, 0xB1200000_0001869FUL }; // 1e-5 + yield return new object[] { 0x2FE4BCA8_DBB35555UL, 0x2FE12F2A_36ECD555UL }; // 1.333... full precision + yield return new object[] { 0x6BFB86F2_6FC0FFFFUL, 0x2FFFF973_CAFA7FFFUL }; // 9.999... full precision (++ carry) + yield return new object[] { 0xDE000D7B_C1739419UL, 0xDDC54457_9125D9C4UL }; + yield return new object[] { 0x33A00000_00000035UL, 0x31F2D452_694F4000UL }; + yield return new object[] { 0x32A00000_001AACBFUL, 0x31C00FE6_3FF6497FUL }; + yield return new object[] { 0x31400000_0000C292UL, 0x31400000_00009B82UL }; + yield return new object[] { 0xB0E00000_00002125UL, 0xB0E00000_0098B7A5UL }; + yield return new object[] { 0xB4200000_00000004UL, 0xB24E35FA_931A0000UL }; + yield return new object[] { 0x31C00000_06E71450UL, 0x31C00000_06E7144FUL }; + yield return new object[] { 0xB2E00000_05A136AAUL, 0xEC798E4D_53BD6A00UL }; + yield return new object[] { 0x2FA00000_03D3544BUL, 0xEBF386F2_6F5F112CUL }; + yield return new object[] { 0xB0C00000_0002002AUL, 0xB0C00000_05F7E12AUL }; + yield return new object[] { 0xB2604C77_D2CE2737UL, 0xB23DDECE_5887517CUL }; + yield return new object[] { 0x32600000_01C6C941UL, 0x31C002B5_F2D6CE9FUL }; + yield return new object[] { 0x30E00000_33939110UL, 0x30E00000_32FAFA90UL }; + yield return new object[] { 0x32C00000_5AC09507UL, 0x320568C5_11FA0FC0UL }; + yield return new object[] { 0xB06D3F1C_656F52FAUL, 0xB06D3F33_ADE63AFAUL }; + yield return new object[] { 0x31E00000_00000043UL, 0x31C00000_0000029DUL }; + yield return new object[] { 0xAE00C898_D85C6FF7UL, 0xAFE38D7E_A4C68000UL }; + yield return new object[] { 0xBE8CD924_B6560750UL, 0xBE8CD924_B6560750UL }; + yield return new object[] { 0x3102EDC1_D68C47BEUL, 0x3102EDC1_D67D057EUL }; + yield return new object[] { 0xB0E00000_00000009UL, 0xB0E00000_00989689UL }; + yield return new object[] { 0xB0600586_0DF1C1B6UL, 0xB060059D_5668A9B6UL }; + yield return new object[] { 0x33000000_00000016UL, 0x31C00033_390597FFUL }; + yield return new object[] { 0xB0C0007E_EB0B6EA0UL, 0xB0C0007E_F1014FA0UL }; + yield return new object[] { 0x2F400000_00000897UL, 0xAFE38D7E_A4C68000UL }; + yield return new object[] { 0x32A00000_000003E0UL, 0x31C00002_4F472FFFUL }; + yield return new object[] { 0xB2A00000_00000004UL, 0xB1C00000_02625A01UL }; + yield return new object[] { 0x3820253B_3AF58D7CUL, 0x37EE8B23_07EB4470UL }; + yield return new object[] { 0x32A00000_0060035AUL, 0x31C0393A_6F6868FFUL }; + yield return new object[] { 0x2EC00000_1B1D8BA6UL, 0xEBF386F2_6FC0FFFBUL }; + yield return new object[] { 0xB2800000_22850479UL, 0xB1C20EBA_2F7F5041UL }; + yield return new object[] { 0xB2200000_30F3774BUL, 0xB1C000BF_3709FCF9UL }; + yield return new object[] { 0xAF200000_042E8A44UL, 0xAFE38D7E_A4C68046UL }; + yield return new object[] { 0x302012E4_B193AA00UL, 0x302009CC_63210A00UL }; + yield return new object[] { 0xAEE00001_BE122682UL, 0xAFE38D7E_A4C6804BUL }; + yield return new object[] { 0x07600000_00000000UL, 0xAFE38D7E_A4C68000UL }; + yield return new object[] { 0xB2600000_0000D120UL, 0xB1C00001_3F197401UL }; + yield return new object[] { 0x2EC00008_5153EF36UL, 0xEBF386F2_6FC0FE9BUL }; + yield return new object[] { 0x2F200000_00000045UL, 0xAFE38D7E_A4C68000UL }; + yield return new object[] { 0x2FCDCB8B_5E0C05CAUL, 0xAFD5BB67_11B4FA36UL }; + yield return new object[] { 0xBEE000E4_BBA70FA7UL, 0xEF9AE6E2_2DD36B70UL }; + yield return new object[] { 0x5B600000_000003D2UL, 0x7672BEDB_B1E74000UL }; + yield return new object[] { 0x2F600000_00004436UL, 0xEBF386F2_6FC0FFEFUL }; + yield return new object[] { 0x30600000_00833DA9UL, 0xB0600017_47F3AA57UL }; + yield return new object[] { 0x2F200000_1228ADE2UL, 0xEBF386F2_6FC0F419UL }; + yield return new object[] { 0xAF600000_2AD1A374UL, 0xAFE38D7E_A4C7989EUL }; + yield return new object[] { 0xB1600000_18006133UL, 0xB1600000_1800651BUL }; + yield return new object[] { 0x6B5A3F19_333501CCUL, 0xAFE38D7E_A4C68000UL }; + yield return new object[] { 0x32E00000_001FC878UL, 0x31C7666B_545EAFFFUL }; + yield return new object[] { 0x31800000_B171BD11UL, 0x31800000_B171BCADUL }; + yield return new object[] { 0x2EE00001_723C5118UL, 0xEBF386F2_6FC0FD93UL }; + yield return new object[] { 0x9F400000_00001DF5UL, 0xAFE38D7E_A4C68000UL }; + yield return new object[] { 0xAEC00000_001D0824UL, 0xAFE38D7E_A4C68000UL }; + yield return new object[] { 0xAFA34A58_43C82F35UL, 0xAFE395EB_0BDA6BFEUL }; + yield return new object[] { 0x19C00000_0006B3C8UL, 0xAFE38D7E_A4C68000UL }; + yield return new object[] { 0xB2400000_000014EDUL, 0xB1C00000_033169D1UL }; + yield return new object[] { 0x31200054_42297730UL, 0x31200054_4227F090UL }; + yield return new object[] { 0x2F000063_A23BC84AUL, 0xEBF386F2_6FBA786CUL }; + yield return new object[] { 0x2E60007D_E4F31691UL, 0xEBF386F2_6FC0FFFBUL }; + yield return new object[] { 0x328000E1_F407BFE1UL, 0x6C827A4C_6EB74510UL }; + yield return new object[] { 0x95400091_BAB57FB9UL, 0xAFE38D7E_A4C68000UL }; + } + + [Theory] + [MemberData(nameof(op_Decrement_TestData))] + public static void op_Decrement(ulong value, ulong expected) + { + Decimal64 result = Unsafe.BitCast(value); + result--; + Assert.Equal(expected, Unsafe.BitCast(result)); + } + } } From 98b99a50b47e7ffef1e217d482aedcf6049c0bc0 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 10 Jul 2026 08:41:03 -0700 Subject: [PATCH 08/19] Rewrite Decimal32/64/128 arithmetic to word-level integer operations The internal Add/Multiply/Divide cores for the IEEE 754 decimal types previously serialized significands into per-digit ASCII byte spans and performed base-10 "string" arithmetic, which made the operators far too slow to be useful. Rewrite the arithmetic to operate directly on the native limb type (uint/ulong/UInt128): - Multiply computes the full double-width product via WideMultiply and rounds through the shared word-level encoder. - Add aligns operands using single-limb scaling into a (Hi,Lo) pair, then performs a wide add (same sign) or compare-and-subtract (opposite sign) with correct sticky-bit propagation. - Divide uses single-limb decimal long division, producing the quotient one digit at a time with an exact-result trailing-zero strip toward the preferred exponent. All three feed NumberToDecimalIeee754BitsFromWide, which mirrors the existing NumberToDecimalIeee754Bits rounding (round-half-even, subnormal capping to avoid double rounding, overflow-to-infinity, exact preferred exponent). The now-dead Big* digit-span helper cluster is removed. Validated against the Intel BID reference oracle with large randomized batches (39235 vectors across all three ops and formats) plus the existing committed test suite, all passing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 605 ++++++++---------- 1 file changed, 265 insertions(+), 340 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index 61b763a24e3b1f..6ebecc5507cb0a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// 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; @@ -556,11 +556,11 @@ private static TValue DecimalIeee754Rounding(ref NumberBuffer /// bit pattern of the correctly rounded (round-to-nearest, ties-to-even) sum. /// /// - /// The two operands are decoded, aligned to their common (smaller) exponent, and summed - /// exactly in base 10. Digits that fall below the retained precision are folded into a - /// sticky flag so the shared rounding path produces the same result as computing the exact - /// sum. This mirrors the mathematical behavior of the Intel reference implementation while - /// remaining independent of the underlying integer width. + /// The two operands are decoded and aligned to their common (smaller) exponent by scaling the larger-exponent + /// coefficient up at double integer width. Digits of the smaller operand that fall below the retained precision + /// are folded into a sticky flag. The aligned coefficients are then added or subtracted with word-level integer + /// arithmetic and fed into the shared rounding path, producing the same result as computing the exact sum. This + /// mirrors the mathematical behavior of the Intel reference implementation. /// internal static TValue AddDecimalIeee754(TValue left, TValue right) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo @@ -653,77 +653,77 @@ internal static TValue AddDecimalIeee754(TValue left, TValue r int droppedDigits = exponentDifference - effectiveDifference; int commonExponent = hi.UnbiasedExponent - effectiveDifference; - int capacity = (2 * TDecimal.Precision) + 4; - Span hiDigits = stackalloc byte[capacity]; - Span loDigits = stackalloc byte[capacity]; + // Align `hi` to the common exponent by scaling its coefficient up by 10^effectiveDifference. The + // scaled coefficient can exceed a single limb (up to ~10^(2*Precision+1)), so it is held at double + // width. The scale factor fits a single limb because effectiveDifference <= Precision + 2. + WideMultiply(hi.Significand, PowerOfTen(effectiveDifference), out TValue magnitudeHigh, out TValue magnitudeLow); - int hiLength = WriteDigits(hi.Significand, hiDigits, TDecimal.CountDigits(hi.Significand)); - for (int i = 0; i < effectiveDifference; i++) - { - hiDigits[hiLength++] = (byte)'0'; - } - - int loRawLength = TDecimal.CountDigits(lo.Significand); + // Align `lo` to the common exponent by discarding its `droppedDigits` least-significant digits, which + // fall below the retained range and only contribute stickiness. The retained portion fits a single limb. bool sticky = false; - int loLength; + TValue loRetained; - if (droppedDigits >= loRawLength) + if (droppedDigits >= TDecimal.CountDigits(lo.Significand)) { - // Every digit of `lo` falls below the retained range; it only contributes stickiness. - loLength = 0; + loRetained = TValue.Zero; sticky = true; } else if (droppedDigits > 0) { - Span loRaw = stackalloc byte[capacity]; - WriteDigits(lo.Significand, loRaw, loRawLength); - loLength = loRawLength - droppedDigits; - loRaw.Slice(0, loLength).CopyTo(loDigits); - - for (int i = loLength; i < loRawLength; i++) - { - if (loRaw[i] != (byte)'0') - { - sticky = true; - break; - } - } + (loRetained, TValue remainder) = TValue.DivRem(lo.Significand, TDecimal.Power10(droppedDigits)); + sticky = !TValue.IsZero(remainder); } else { - loLength = WriteDigits(lo.Significand, loDigits, loRawLength); + loRetained = lo.Significand; } bool sameSign = hi.Signed == lo.Signed; bool resultSign; - int magnitudeLength; - Span magnitude = stackalloc byte[capacity + 1]; if (sameSign) { - magnitudeLength = BigAdd(hiDigits.Slice(0, hiLength), loDigits.Slice(0, loLength), magnitude); + // Magnitudes add. Fold the retained `lo` coefficient into the double-width accumulator. + TValue newLow = magnitudeLow + loRetained; + if (newLow < magnitudeLow) + { + magnitudeHigh += TValue.One; + } + magnitudeLow = newLow; resultSign = hi.Signed; } else { - int comparison = BigCompare(hiDigits.Slice(0, hiLength), loDigits.Slice(0, loLength)); + // Magnitudes subtract. When `droppedDigits > 0` the scaled `hi` coefficient is at least 10^guard, + // which dominates the retained `lo` (< 10^Precision), so `hi` always compares greater in that case. + int comparison = !TValue.IsZero(magnitudeHigh) ? 1 : magnitudeLow.CompareTo(loRetained); if (comparison > 0) { - magnitudeLength = BigSub(hiDigits.Slice(0, hiLength), loDigits.Slice(0, loLength), magnitude); + if (magnitudeLow < loRetained) + { + magnitudeHigh -= TValue.One; + } + magnitudeLow -= loRetained; resultSign = hi.Signed; if (sticky) { - // The true `lo` magnitude is slightly larger than its retained digits, so the - // exact difference is one unit smaller with a non-zero fractional remainder. - BigDecrement(magnitude.Slice(0, magnitudeLength)); + // The true `lo` magnitude is slightly larger than its retained digits, so the exact + // difference is one unit smaller with a non-zero fractional remainder. + if (TValue.IsZero(magnitudeLow)) + { + magnitudeHigh -= TValue.One; + } + magnitudeLow -= TValue.One; } } else if (comparison < 0) { - // `droppedDigits` is always zero here, so there is no sticky remainder to account for. - magnitudeLength = BigSub(loDigits.Slice(0, loLength), hiDigits.Slice(0, hiLength), magnitude); + // `droppedDigits` is always zero here, so there is no sticky remainder to account for and both + // magnitudes fit a single limb. + magnitudeLow = loRetained - magnitudeLow; + magnitudeHigh = TValue.Zero; resultSign = lo.Signed; } else @@ -733,42 +733,12 @@ internal static TValue AddDecimalIeee754(TValue left, TValue r } } - int start = 0; - while (start < magnitudeLength && magnitude[start] == (byte)'0') - { - start++; - } - int digitsCount = magnitudeLength - start; - - if (digitsCount == 0) + if (TValue.IsZero(magnitudeHigh) && TValue.IsZero(magnitudeLow) && !sticky) { return DecimalIeee754FiniteNumberBinaryEncoding(false, TValue.Zero, commonExponent); } - Span numberDigits = stackalloc byte[capacity + 2]; - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, numberDigits); - magnitude.Slice(start, digitsCount).CopyTo(number.Digits); - number.Digits[digitsCount] = 0; - number.DigitsCount = digitsCount; - number.Scale = digitsCount + commonExponent; - number.IsNegative = resultSign; - number.HasNonZeroTail = sticky; - number.CheckConsistency(); - - return NumberToDecimalIeee754Bits(ref number); - - static int WriteDigits(TValue value, Span destination, int length) - { - TValue ten = TValue.CreateTruncating(10); - - for (int i = length - 1; i >= 0; i--) - { - (value, TValue remainder) = TValue.DivRem(value, ten); - destination[i] = (byte)('0' + int.CreateTruncating(remainder)); - } - - return length; - } + return NumberToDecimalIeee754BitsFromWide(resultSign, magnitudeHigh, magnitudeLow, commonExponent, sticky); } /// @@ -776,12 +746,11 @@ static int WriteDigits(TValue value, Span destination, int length) /// bit pattern of the correctly rounded (round-to-nearest, ties-to-even) product. /// /// - /// The operands are decoded and their coefficients multiplied exactly in base 10 (the product can - /// require up to twice the format precision, so it is computed on digit spans rather than the - /// underlying integer width). The exact product exponent is the sum of the operand exponents, which - /// is also the IEEE 754 preferred exponent because trailing zeros of the product are retained. The - /// exact digit string is then fed into the shared rounding path. This mirrors the mathematical - /// behavior of the Intel reference implementation while remaining independent of the integer width. + /// The operands are decoded and their coefficients multiplied exactly using a double-width integer product + /// (the product can require up to twice the format precision). The exact product exponent is the sum of the + /// operand exponents, which is also the IEEE 754 preferred exponent because trailing zeros of the product are + /// retained. The exact product is then fed into the shared word-level rounding path. This mirrors the + /// mathematical behavior of the Intel reference implementation. /// internal static TValue MultiplyDecimalIeee754(TValue left, TValue right) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo @@ -831,47 +800,13 @@ internal static TValue MultiplyDecimalIeee754(TValue left, TVa return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, TValue.Zero, productExponent); } - int capacity = (2 * TDecimal.Precision) + 4; - Span leftDigits = stackalloc byte[capacity]; - Span rightDigits = stackalloc byte[capacity]; - Span productDigits = stackalloc byte[capacity]; - - int leftLength = WriteDigits(a.Significand, leftDigits, TDecimal.CountDigits(a.Significand)); - int rightLength = WriteDigits(b.Significand, rightDigits, TDecimal.CountDigits(b.Significand)); - - int productLength = BigMultiply(leftDigits.Slice(0, leftLength), rightDigits.Slice(0, rightLength), productDigits); - - int start = 0; - while (start < productLength && productDigits[start] == (byte)'0') - { - start++; - } - int digitsCount = productLength - start; - Debug.Assert(digitsCount > 0); + // The exact product of two coefficients needs up to twice the format precision, so it is computed at + // double the integer width. The sum of the operand exponents is also the IEEE 754 preferred exponent + // (trailing zeros of the product are retained), so it is fed unchanged into the shared word-level + // rounding/encoding path. + WideMultiply(a.Significand, b.Significand, out TValue productHigh, out TValue productLow); - Span numberDigits = stackalloc byte[capacity + 2]; - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, numberDigits); - productDigits.Slice(start, digitsCount).CopyTo(number.Digits); - number.Digits[digitsCount] = 0; - number.DigitsCount = digitsCount; - number.Scale = digitsCount + productExponent; - number.IsNegative = resultSign; - number.CheckConsistency(); - - return NumberToDecimalIeee754Bits(ref number); - - static int WriteDigits(TValue value, Span destination, int length) - { - TValue ten = TValue.CreateTruncating(10); - - for (int i = length - 1; i >= 0; i--) - { - (value, TValue remainder) = TValue.DivRem(value, ten); - destination[i] = (byte)('0' + int.CreateTruncating(remainder)); - } - - return length; - } + return NumberToDecimalIeee754BitsFromWide(resultSign, productHigh, productLow, productExponent, sticky: false); } /// @@ -879,14 +814,13 @@ static int WriteDigits(TValue value, Span destination, int length) /// bit pattern of the correctly rounded (round-to-nearest, ties-to-even) quotient. /// /// - /// The dividend coefficient is scaled up by a power of ten large enough to expose more than the - /// format precision in significant quotient digits, then long-divided by the divisor coefficient - /// in base 10 (the intermediate values can exceed the underlying integer width, so the arithmetic - /// is performed on digit spans). The remainder determines whether the result is exact; an inexact - /// result carries a sticky flag into the shared rounding path, while an exact result has its - /// exponent raised toward the IEEE 754 preferred exponent (the difference of the operand exponents) - /// by discarding trailing zeros. This mirrors the mathematical behavior of the Intel reference - /// implementation while remaining independent of the integer width. + /// The dividend coefficient is scaled up by a power of ten large enough to expose more than the format + /// precision in significant quotient digits, then long-divided by the divisor coefficient one decimal digit + /// at a time. The running remainder and the quotient each stay within a single limb, so the division uses only + /// word-level integer arithmetic. The final remainder determines whether the result is exact; an inexact + /// result carries a sticky flag into the shared rounding path, while an exact result has its exponent raised + /// toward the IEEE 754 preferred exponent (the difference of the operand exponents) by discarding trailing + /// zeros. This mirrors the mathematical behavior of the Intel reference implementation. /// internal static TValue DivideDecimalIeee754(TValue left, TValue right) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo @@ -957,316 +891,307 @@ internal static TValue DivideDecimalIeee754(TValue left, TValu int quotientExponent = a.UnbiasedExponent - b.UnbiasedExponent - shift; - int capacity = (2 * TDecimal.Precision) + 4; - Span divisorSpan = stackalloc byte[capacity]; - Span numeratorSpan = stackalloc byte[capacity]; - Span quotientSpan = stackalloc byte[capacity]; - Span remainderSpan = stackalloc byte[capacity]; - - int divisorLength = WriteDigits(b.Significand, divisorSpan, divisorDigits); + // Long-divide `a.Significand * 10^shift` by `b.Significand` one decimal digit at a time. The running + // remainder always stays below the divisor and the quotient always stays below 10^(Precision + 2), so + // every intermediate value fits a single limb and no wide integer is needed for division. The final + // remainder determines whether the quotient is exact. + TValue divisor = b.Significand; + TValue ten = TValue.CreateTruncating(10); + (TValue quotient, TValue remainder) = TValue.DivRem(a.Significand, divisor); - int numeratorLength = WriteDigits(a.Significand, numeratorSpan, dividendDigits); for (int i = 0; i < shift; i++) { - numeratorSpan[numeratorLength++] = (byte)'0'; + remainder *= ten; + (TValue digit, remainder) = TValue.DivRem(remainder, divisor); + quotient = (quotient * ten) + digit; } - BigDivRem(numeratorSpan.Slice(0, numeratorLength), divisorSpan.Slice(0, divisorLength), quotientSpan, remainderSpan, out bool remainderNonZero); - - int start = 0; - while (start < numeratorLength && quotientSpan[start] == (byte)'0') - { - start++; - } - int digitsCount = numeratorLength - start; - Debug.Assert(digitsCount > 0); + bool remainderNonZero = !TValue.IsZero(remainder); if (!remainderNonZero) { // The quotient is exact, so raise its exponent toward the preferred exponent (the difference // of the operand exponents) by discarding trailing zeros. int idealExponent = a.UnbiasedExponent - b.UnbiasedExponent; - while (digitsCount > 1 && quotientExponent < idealExponent && quotientSpan[start + digitsCount - 1] == (byte)'0') - { - digitsCount--; - quotientExponent++; - } - } - - Span numberDigits = stackalloc byte[capacity + 2]; - NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, numberDigits); - quotientSpan.Slice(start, digitsCount).CopyTo(number.Digits); - number.Digits[digitsCount] = 0; - number.DigitsCount = digitsCount; - number.Scale = digitsCount + quotientExponent; - number.IsNegative = resultSign; - number.HasNonZeroTail = remainderNonZero; - number.CheckConsistency(); - return NumberToDecimalIeee754Bits(ref number); + while (quotientExponent < idealExponent) + { + (TValue stripped, TValue digit) = TValue.DivRem(quotient, ten); - static int WriteDigits(TValue value, Span destination, int length) - { - TValue ten = TValue.CreateTruncating(10); + if (!TValue.IsZero(digit)) + { + break; + } - for (int i = length - 1; i >= 0; i--) - { - (value, TValue remainder) = TValue.DivRem(value, ten); - destination[i] = (byte)('0' + int.CreateTruncating(remainder)); + quotient = stripped; + quotientExponent++; } - - return length; } + + return NumberToDecimalIeee754BitsFromWide(resultSign, TValue.Zero, quotient, quotientExponent, remainderNonZero); } - private static ReadOnlySpan TrimLeadingZeros(ReadOnlySpan digits) + /// + /// Computes 10^ as a single limb. Unlike the + /// format's Power10 lookup table (which only covers exponents up to Precision - 1), this supports + /// the slightly larger exponents needed when aligning operands (up to Precision + 2), which still fit a + /// single limb for every supported format. + /// + private static TValue PowerOfTen(int exponent) + where TValue : unmanaged, IBinaryInteger { - int start = 0; - while (start < digits.Length && digits[start] == (byte)'0') + TValue ten = TValue.CreateTruncating(10); + TValue result = TValue.One; + + for (int i = 0; i < exponent; i++) { - start++; + result *= ten; } - return digits.Slice(start); + + return result; } - private static int BigCompare(ReadOnlySpan left, ReadOnlySpan right) + /// + /// Computes the full (double-width) product of two significands as a pair of + /// limbs ( holds the more significant half). The product of two coefficients can + /// require up to twice the format precision, which always fits in two limbs of the underlying integer width. + /// + private static void WideMultiply(TValue left, TValue right, out TValue high, out TValue low) + where TValue : unmanaged, IBinaryInteger { - left = TrimLeadingZeros(left); - right = TrimLeadingZeros(right); + int bits = TValue.Zero.GetByteCount() * 8; + int half = bits / 2; + TValue lowMask = (TValue.One << half) - TValue.One; - if (left.Length != right.Length) - { - return left.Length < right.Length ? -1 : 1; - } + TValue leftLow = left & lowMask; + TValue leftHigh = left >> half; + TValue rightLow = right & lowMask; + TValue rightHigh = right >> half; + + TValue lowLow = leftLow * rightLow; + TValue lowHigh = leftLow * rightHigh; + TValue highLow = leftHigh * rightLow; + TValue highHigh = leftHigh * rightHigh; - return left.SequenceCompareTo(right); + TValue cross = (lowLow >> half) + (lowHigh & lowMask) + (highLow & lowMask); + + low = (lowLow & lowMask) | ((cross & lowMask) << half); + high = highHigh + (lowHigh >> half) + (highLow >> half) + (cross >> half); } - // Multiplies two base-10 digit spans (big-endian, '0'..'9') using schoolbook multiplication. - // The product is written to `result` (which must have room for `left.Length + right.Length` - // digits) and may carry a single leading zero, which the caller trims. - private static int BigMultiply(ReadOnlySpan left, ReadOnlySpan right, Span result) + /// + /// Divides the double-width value in (, ) by ten in place, + /// returning the discarded least-significant decimal digit. Used to strip low-order digits during rounding. + /// + private static int WideDivideByTen(ref TValue high, ref TValue low) + where TValue : unmanaged, IBinaryInteger { - left = TrimLeadingZeros(left); - right = TrimLeadingZeros(right); - - int leftLength = left.Length; - int rightLength = right.Length; + TValue ten = TValue.CreateTruncating(10); - if (leftLength == 0 || rightLength == 0) + if (TValue.IsZero(high)) { - return 0; + (TValue quotient, TValue remainder) = TValue.DivRem(low, ten); + low = quotient; + return int.CreateTruncating(remainder); } - int length = leftLength + rightLength; + int bits = TValue.Zero.GetByteCount() * 8; + int half = bits / 2; + TValue lowMask = (TValue.One << half) - TValue.One; + TValue baseValue = TValue.One << half; - for (int i = 0; i < length; i++) - { - result[i] = (byte)'0'; - } + // Long division of the four half-width limbs (most significant first) by ten. Because the running + // remainder stays below ten, `remainder * baseValue + limb` never exceeds the integer width. + TValue rem = TValue.Zero; + (TValue q3, rem) = TValue.DivRem((rem * baseValue) + (high >> half), ten); + (TValue q2, rem) = TValue.DivRem((rem * baseValue) + (high & lowMask), ten); + (TValue q1, rem) = TValue.DivRem((rem * baseValue) + (low >> half), ten); + (TValue q0, rem) = TValue.DivRem((rem * baseValue) + (low & lowMask), ten); - for (int i = leftLength - 1; i >= 0; i--) - { - int carry = 0; - int leftDigit = left[i] - '0'; + high = (q3 << half) | q2; + low = (q1 << half) | q0; + return int.CreateTruncating(rem); + } - for (int j = rightLength - 1; j >= 0; j--) - { - int index = i + j + 1; - int product = (leftDigit * (right[j] - '0')) + (result[index] - '0') + carry; - result[index] = (byte)('0' + (product % 10)); - carry = product / 10; - } + /// + /// Counts the number of decimal digits in the double-width value (, ), + /// which must be non-zero. + /// + private static int WideDigitCount(TValue high, TValue low) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + int count = 0; - result[i] = (byte)('0' + carry); + while (!TValue.IsZero(high)) + { + WideDivideByTen(ref high, ref low); + count++; } - return length; + return count + TDecimal.CountDigits(low); } - private static void BigDivRem(ReadOnlySpan dividend, ReadOnlySpan divisor, Span quotient, Span remainder, out bool remainderNonZero) + /// + /// Removes the least-significant decimal digits from the double-width value, + /// returning the retained significand (which fits in a single limb). The most-significant removed digit is + /// returned in for the rounding decision; all lower removed digits are folded + /// into . + /// + private static TValue DropDigits(ref TValue high, ref TValue low, int dropCount, ref bool sticky, out int roundDigit) + where TValue : unmanaged, IBinaryInteger { - // Schoolbook base-10 long division. Each dividend digit is brought down into the running - // remainder (which multiplies it by ten and adds the digit), then the quotient digit is found - // by repeatedly subtracting the divisor. The remainder is stored right-aligned so the subtract - // and compare helpers operate on the least-significant digits regardless of leading zeros. - int dividendLength = dividend.Length; - int divisorLength = divisor.Length; - int remainderLength = 0; + roundDigit = 0; - for (int i = 0; i < dividendLength; i++) + for (int i = 0; i < dropCount; i++) { - remainder[remainderLength++] = dividend[i]; - - int digit = 0; - while (CompareRightAligned(remainder, remainderLength, divisor, divisorLength) >= 0) + if (i > 0) { - SubtractRightAligned(remainder, remainderLength, divisor, divisorLength); - digit++; + sticky |= roundDigit != 0; } - quotient[i] = (byte)('0' + digit); + roundDigit = WideDivideByTen(ref high, ref low); } - remainderNonZero = false; - - for (int i = 0; i < remainderLength; i++) - { - if (remainder[i] != (byte)'0') - { - remainderNonZero = true; - break; - } - } + Debug.Assert(TValue.IsZero(high)); + return low; } - private static int CompareRightAligned(ReadOnlySpan left, int leftLength, ReadOnlySpan right, int rightLength) + /// + /// Rounds the exact double-width coefficient (, ) with value + /// ±coefficient·10^ (plus an optional sub-unit tail) + /// to the nearest representable IEEE 754 decimal value using round-to-nearest, ties-to-even, and returns its + /// BID bit pattern. This is the integer-based counterpart of + /// used by the arithmetic operators; it mirrors the same exponent-range handling without materializing a digit string. + /// + private static TValue NumberToDecimalIeee754BitsFromWide(bool sign, TValue high, TValue low, int exponent, bool sticky) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger { - int leftStart = 0; - while (leftStart < leftLength && left[leftStart] == (byte)'0') - { - leftStart++; - } - - int rightStart = 0; - while (rightStart < rightLength && right[rightStart] == (byte)'0') + if (TValue.IsZero(high) && TValue.IsZero(low)) { - rightStart++; + return DecimalIeee754FiniteNumberBinaryEncoding(sign, TValue.Zero, exponent); } - int leftDigits = leftLength - leftStart; - int rightDigits = rightLength - rightStart; + int precision = TDecimal.Precision; + int digitsCount = WideDigitCount(high, low); - if (leftDigits != rightDigits) + if (exponent > TDecimal.MaxExponent) { - return leftDigits < rightDigits ? -1 : 1; + return sign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; } - for (int i = 0; i < leftDigits; i++) + if (exponent > TDecimal.MaxAdjustedExponent) { - int difference = left[leftStart + i] - right[rightStart + i]; + // The least-significant digit already sits above the largest representable quantum. The value is + // representable only if the coefficient can be padded with trailing zeros without exceeding the + // precision; otherwise it overflows to infinity. + int numberZeroDigits = exponent - TDecimal.MaxAdjustedExponent; - if (difference != 0) + if (digitsCount + numberZeroDigits > precision) { - return difference < 0 ? -1 : 1; + return sign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; } - } - return 0; - } - - private static void SubtractRightAligned(Span left, int leftLength, ReadOnlySpan right, int rightLength) - { - int borrow = 0; + TValue paddedSignificand = low * TDecimal.Power10(numberZeroDigits); + return DecimalIeee754FiniteNumberBinaryEncoding(sign, paddedSignificand, TDecimal.MaxAdjustedExponent); + } - for (int i = 0; i < leftLength; i++) + if (exponent < TDecimal.MinAdjustedExponent) { - int leftIndex = leftLength - 1 - i; - int digit = (left[leftIndex] - '0') - borrow; + int numberDigitsRemove = TDecimal.MinAdjustedExponent - exponent; - if (i < rightLength) + if (numberDigitsRemove > digitsCount) { - digit -= right[rightLength - 1 - i] - '0'; + return DecimalIeee754FiniteNumberBinaryEncoding(sign, TValue.Zero, TDecimal.MinAdjustedExponent); } - - if (digit < 0) + else if (numberDigitsRemove < digitsCount) { - digit += 10; - borrow = 1; + int numberDigitsRemain = digitsCount - numberDigitsRemove; + + if (numberDigitsRemain > precision) + { + // Still above the format precision after shifting to the minimum quantum, so the value is + // actually normal. Round to the precision in a single step to avoid a double rounding. + numberDigitsRemain = precision; + } + + return RoundWideToSignificand(sign, high, low, digitsCount, exponent, numberDigitsRemain, sticky); } else { - borrow = 0; + return RoundWideToZeroOrEpsilon(sign, high, low, digitsCount, sticky); } - - left[leftIndex] = (byte)('0' + digit); } - } - - private static int BigAdd(ReadOnlySpan left, ReadOnlySpan right, Span result) - { - int leftLength = left.Length; - int rightLength = right.Length; - int length = Math.Max(leftLength, rightLength) + 1; - int carry = 0; - for (int position = 0; position < length; position++) + if (digitsCount > precision) { - int sum = carry; + int numberDigitsRemove = digitsCount - precision; - if (position < leftLength) - { - sum += left[leftLength - 1 - position] - '0'; - } - if (position < rightLength) - { - sum += right[rightLength - 1 - position] - '0'; - } - - if (sum >= 10) - { - sum -= 10; - carry = 1; - } - else + if (exponent + numberDigitsRemove > TDecimal.MaxAdjustedExponent) { - carry = 0; + return sign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; } - result[length - 1 - position] = (byte)('0' + sum); + return RoundWideToSignificand(sign, high, low, digitsCount, exponent, precision, sticky); } - return length; + return DecimalIeee754FiniteNumberBinaryEncoding(sign, low, exponent); } - // Subtracts `right` from `left`, which must be greater than or equal to `right` in magnitude. - private static int BigSub(ReadOnlySpan left, ReadOnlySpan right, Span result) + /// + /// Rounds the double-width coefficient to significant digits using + /// round-to-nearest, ties-to-even, and encodes the result. This is the integer-based counterpart of + /// . + /// + private static TValue RoundWideToSignificand(bool sign, TValue high, TValue low, int digitsCount, int exponent, int numberDigitsRemain, bool sticky) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger { - left = TrimLeadingZeros(left); - right = TrimLeadingZeros(right); + int dropCount = digitsCount - numberDigitsRemain; + TValue significand = DropDigits(ref high, ref low, dropCount, ref sticky, out int roundDigit); + int resultExponent = exponent + dropCount; - int leftLength = left.Length; - int rightLength = right.Length; - int borrow = 0; + bool roundUp = (roundDigit > 5) + || ((roundDigit == 5) && (sticky || TValue.IsOddInteger(significand))); - for (int position = 0; position < leftLength; position++) + if (!roundUp) { - int difference = (left[leftLength - 1 - position] - '0') - borrow; + return DecimalIeee754FiniteNumberBinaryEncoding(sign, significand, resultExponent); + } - if (position < rightLength) - { - difference -= right[rightLength - 1 - position] - '0'; - } + if (significand == TDecimal.MaxSignificand) + { + resultExponent += 1; - if (difference < 0) + if (resultExponent > TDecimal.MaxAdjustedExponent) { - difference += 10; - borrow = 1; - } - else - { - borrow = 0; + return sign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; } - result[leftLength - 1 - position] = (byte)('0' + difference); + significand = TDecimal.Power10(TDecimal.Precision - 1); + return DecimalIeee754FiniteNumberBinaryEncoding(sign, significand, resultExponent); } - return leftLength; + significand += TValue.One; + return DecimalIeee754FiniteNumberBinaryEncoding(sign, significand, resultExponent); } - // Subtracts one from a non-zero magnitude in place. The caller guarantees no underflow. - private static void BigDecrement(Span magnitude) + /// + /// Rounds a double-width coefficient whose value lies below the minimum quantum to either zero or the + /// smallest representable subnormal (epsilon), using round-to-nearest, ties-to-even. This is the + /// integer-based counterpart of . + /// + private static TValue RoundWideToZeroOrEpsilon(bool sign, TValue high, TValue low, int digitsCount, bool sticky) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger { - for (int position = magnitude.Length - 1; position >= 0; position--) - { - if (magnitude[position] > (byte)'0') - { - magnitude[position]--; - return; - } + TValue lead = DropDigits(ref high, ref low, digitsCount - 1, ref sticky, out int roundDigit); + bool restNonZero = sticky || (roundDigit != 0); + int leadDigit = int.CreateTruncating(lead); - magnitude[position] = (byte)'9'; - } + TValue significand = ((leadDigit > 5) || ((leadDigit == 5) && restNonZero)) ? TValue.One : TValue.Zero; + return DecimalIeee754FiniteNumberBinaryEncoding(sign, significand, TDecimal.MinAdjustedExponent); } } } From 6fde6a70826fe640d5d36c977934bdb7ac6018f6 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 10 Jul 2026 10:39:41 -0700 Subject: [PATCH 09/19] Regenerate System.Runtime ref for Decimal32/64/128 via GenAPI The Decimal32/64/128 operator declarations were previously hand-added to the reference assembly in a non-conformant style (non-partial, unqualified names, hand-ordered members, missing the implicit IParsable interface). Per docs/coding-guidelines/updating-ref-source.md, regenerate these three types with the GenAPI tool so the reference source matches tool output (fully-qualified names, partial structs, canonical member ordering), while filtering out the unrelated GenAPI churn to the rest of the file. No public API surface changes; this only normalizes the reference source. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System.Runtime/ref/System.Runtime.cs | 278 ++++++++---------- 1 file changed, 123 insertions(+), 155 deletions(-) diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 861bd9494fe774..980a03c5b4795d 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11362,178 +11362,146 @@ public static void HtmlEncode(string? value, System.IO.TextWriter output) { } } namespace System.Numerics { - public readonly struct Decimal32 - : System.IComparable, - System.IComparable, - System.IEquatable, - System.ISpanParsable, - System.Numerics.IMinMaxValue + public readonly partial struct Decimal128 : System.IComparable, System.IComparable, System.IEquatable, System.IParsable, System.ISpanParsable, System.Numerics.IMinMaxValue { + private readonly int _dummyPrimitive; + public static System.Numerics.Decimal128 Epsilon { get { throw null; } } + public static System.Numerics.Decimal128 MaxValue { get { throw null; } } + public static System.Numerics.Decimal128 MinValue { get { throw null; } } + public static System.Numerics.Decimal128 NaN { get { throw null; } } + public static System.Numerics.Decimal128 NegativeInfinity { get { throw null; } } + public static System.Numerics.Decimal128 NegativeZero { get { throw null; } } + public static System.Numerics.Decimal128 PositiveInfinity { get { throw null; } } + public static System.Numerics.Decimal128 Zero { get { throw null; } } + public int CompareTo(System.Numerics.Decimal128 other) { throw null; } public int CompareTo(object? value) { throw null; } - public int CompareTo(Decimal32 other) { throw null; } - public bool Equals(Decimal32 other) { throw null; } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public bool Equals(System.Numerics.Decimal128 other) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { 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.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, IFormatProvider? provider = null) { throw null; } - public static Decimal32 Parse(string s, System.Globalization.NumberStyles style, IFormatProvider? provider) { throw null; } - - public static Decimal32 NaN { get { throw null; } } - public static Decimal32 NegativeInfinity { get { throw null; } } - public static Decimal32 PositiveInfinity { get { throw null; } } - public static Decimal32 NegativeZero { get { throw null; } } - public static Decimal32 Zero { get { throw null; } } - public static Decimal32 MaxValue { get { throw null; } } - public static Decimal32 MinValue { get { throw null; } } - - public static Decimal32 Epsilon { get { throw null; } } - + public static System.Numerics.Decimal128 operator +(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static System.Numerics.Decimal128 operator --(System.Numerics.Decimal128 value) { throw null; } + public static System.Numerics.Decimal128 operator /(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static bool operator ==(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static bool operator >(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static bool operator >=(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static System.Numerics.Decimal128 operator ++(System.Numerics.Decimal128 value) { throw null; } + public static bool operator !=(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static bool operator <(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static bool operator <=(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static System.Numerics.Decimal128 operator *(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static System.Numerics.Decimal128 operator -(System.Numerics.Decimal128 left, System.Numerics.Decimal128 right) { throw null; } + public static System.Numerics.Decimal128 operator -(System.Numerics.Decimal128 value) { throw null; } + public static System.Numerics.Decimal128 operator +(System.Numerics.Decimal128 value) { throw null; } + public static System.Numerics.Decimal128 Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } + public static System.Numerics.Decimal128 Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Decimal128 Parse(string s) { throw null; } + public static System.Numerics.Decimal128 Parse(string s, System.Globalization.NumberStyles style) { throw null; } + public static System.Numerics.Decimal128 Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Decimal128 Parse(string s, System.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 static Decimal32 operator +(Decimal32 value) { throw null; } - public static Decimal32 operator -(Decimal32 value) { throw null; } - public static Decimal32 operator ++(Decimal32 value) { throw null; } - public static Decimal32 operator --(Decimal32 value) { throw null; } - public static Decimal32 operator +(Decimal32 left, Decimal32 right) { throw null; } - public static Decimal32 operator -(Decimal32 left, Decimal32 right) { throw null; } - public static Decimal32 operator *(Decimal32 left, Decimal32 right) { throw null; } - public static Decimal32 operator /(Decimal32 left, Decimal32 right) { throw null; } - public static bool operator ==(Decimal32 left, Decimal32 right) { throw null; } - public static bool operator !=(Decimal32 left, Decimal32 right) { throw null; } - public static bool operator <(Decimal32 left, Decimal32 right) { throw null; } - public static bool operator >(Decimal32 left, Decimal32 right) { throw null; } - public static bool operator <=(Decimal32 left, Decimal32 right) { throw null; } - public static bool operator >=(Decimal32 left, Decimal32 right) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal128 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal128 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, out System.Numerics.Decimal128 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal128 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal128 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.Numerics.Decimal128 result) { throw null; } } - - public readonly struct Decimal64 - : System.IComparable, - System.IComparable, - System.IEquatable, - System.ISpanParsable, - System.Numerics.IMinMaxValue + public readonly partial struct Decimal32 : System.IComparable, System.IComparable, System.IEquatable, System.IParsable, System.ISpanParsable, System.Numerics.IMinMaxValue { + private readonly int _dummyPrimitive; + public static System.Numerics.Decimal32 Epsilon { get { throw null; } } + public static System.Numerics.Decimal32 MaxValue { get { throw null; } } + public static System.Numerics.Decimal32 MinValue { get { throw null; } } + public static System.Numerics.Decimal32 NaN { get { throw null; } } + public static System.Numerics.Decimal32 NegativeInfinity { get { throw null; } } + public static System.Numerics.Decimal32 NegativeZero { get { throw null; } } + public static System.Numerics.Decimal32 PositiveInfinity { get { throw null; } } + public static System.Numerics.Decimal32 Zero { get { throw null; } } + public int CompareTo(System.Numerics.Decimal32 other) { throw null; } public int CompareTo(object? value) { throw null; } - public int CompareTo(Decimal64 other) { throw null; } - public bool Equals(Decimal64 other) { throw null; } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public bool Equals(System.Numerics.Decimal32 other) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { 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.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, IFormatProvider? provider = null) { throw null; } - public static Decimal64 Parse(string s, System.Globalization.NumberStyles style, IFormatProvider? provider) { throw null; } - - public static Decimal64 NaN { get { throw null; } } - public static Decimal64 NegativeInfinity { get { throw null; } } - public static Decimal64 PositiveInfinity { get { throw null; } } - public static Decimal64 NegativeZero { get { throw null; } } - public static Decimal64 Zero { get { throw null; } } - public static Decimal64 MaxValue { get { throw null; } } - public static Decimal64 MinValue { get { throw null; } } - - public static Decimal64 Epsilon { get { throw null; } } - + public static System.Numerics.Decimal32 operator +(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static System.Numerics.Decimal32 operator --(System.Numerics.Decimal32 value) { throw null; } + public static System.Numerics.Decimal32 operator /(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static bool operator ==(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static bool operator >(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static bool operator >=(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static System.Numerics.Decimal32 operator ++(System.Numerics.Decimal32 value) { throw null; } + public static bool operator !=(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static bool operator <(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static bool operator <=(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static System.Numerics.Decimal32 operator *(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static System.Numerics.Decimal32 operator -(System.Numerics.Decimal32 left, System.Numerics.Decimal32 right) { throw null; } + public static System.Numerics.Decimal32 operator -(System.Numerics.Decimal32 value) { throw null; } + public static System.Numerics.Decimal32 operator +(System.Numerics.Decimal32 value) { throw null; } + public static System.Numerics.Decimal32 Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } + public static System.Numerics.Decimal32 Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Decimal32 Parse(string s) { throw null; } + public static System.Numerics.Decimal32 Parse(string s, System.Globalization.NumberStyles style) { throw null; } + public static System.Numerics.Decimal32 Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Decimal32 Parse(string s, System.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 static Decimal64 operator +(Decimal64 value) { throw null; } - public static Decimal64 operator -(Decimal64 value) { throw null; } - public static Decimal64 operator ++(Decimal64 value) { throw null; } - public static Decimal64 operator --(Decimal64 value) { throw null; } - public static Decimal64 operator +(Decimal64 left, Decimal64 right) { throw null; } - public static Decimal64 operator -(Decimal64 left, Decimal64 right) { throw null; } - public static Decimal64 operator *(Decimal64 left, Decimal64 right) { throw null; } - public static Decimal64 operator /(Decimal64 left, Decimal64 right) { throw null; } - public static bool operator ==(Decimal64 left, Decimal64 right) { throw null; } - public static bool operator !=(Decimal64 left, Decimal64 right) { throw null; } - public static bool operator <(Decimal64 left, Decimal64 right) { throw null; } - public static bool operator >(Decimal64 left, Decimal64 right) { throw null; } - public static bool operator <=(Decimal64 left, Decimal64 right) { throw null; } - public static bool operator >=(Decimal64 left, Decimal64 right) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal32 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal32 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, out System.Numerics.Decimal32 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal32 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal32 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.Numerics.Decimal32 result) { throw null; } } - - public readonly struct Decimal128 - : System.IComparable, - System.IComparable, - System.IEquatable, - System.ISpanParsable, - System.Numerics.IMinMaxValue + public readonly partial struct Decimal64 : System.IComparable, System.IComparable, System.IEquatable, System.IParsable, System.ISpanParsable, System.Numerics.IMinMaxValue { + private readonly int _dummyPrimitive; + public static System.Numerics.Decimal64 Epsilon { get { throw null; } } + public static System.Numerics.Decimal64 MaxValue { get { throw null; } } + public static System.Numerics.Decimal64 MinValue { get { throw null; } } + public static System.Numerics.Decimal64 NaN { get { throw null; } } + public static System.Numerics.Decimal64 NegativeInfinity { get { throw null; } } + public static System.Numerics.Decimal64 NegativeZero { get { throw null; } } + public static System.Numerics.Decimal64 PositiveInfinity { get { throw null; } } + public static System.Numerics.Decimal64 Zero { get { throw null; } } + public int CompareTo(System.Numerics.Decimal64 other) { throw null; } public int CompareTo(object? value) { throw null; } - public int CompareTo(Decimal128 other) { throw null; } - public bool Equals(Decimal128 other) { throw null; } - public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhen(true)] object? obj) { throw null; } + public bool Equals(System.Numerics.Decimal64 other) { throw null; } + public override bool Equals([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] object? obj) { throw null; } public override int GetHashCode() { 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.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, IFormatProvider? provider = null) { throw null; } - public static Decimal128 Parse(string s, System.Globalization.NumberStyles style, IFormatProvider? provider) { throw null; } - - public static Decimal128 NaN { get { throw null; } } - public static Decimal128 NegativeInfinity { get { throw null; } } - public static Decimal128 PositiveInfinity { get { throw null; } } - public static Decimal128 NegativeZero { get { throw null; } } - public static Decimal128 Zero { get { throw null; } } - public static Decimal128 MaxValue { get { throw null; } } - public static Decimal128 MinValue { get { throw null; } } - - public static Decimal128 Epsilon { get { throw null; } } - + public static System.Numerics.Decimal64 operator +(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static System.Numerics.Decimal64 operator --(System.Numerics.Decimal64 value) { throw null; } + public static System.Numerics.Decimal64 operator /(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static bool operator ==(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static bool operator >(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static bool operator >=(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static System.Numerics.Decimal64 operator ++(System.Numerics.Decimal64 value) { throw null; } + public static bool operator !=(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static bool operator <(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static bool operator <=(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static System.Numerics.Decimal64 operator *(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static System.Numerics.Decimal64 operator -(System.Numerics.Decimal64 left, System.Numerics.Decimal64 right) { throw null; } + public static System.Numerics.Decimal64 operator -(System.Numerics.Decimal64 value) { throw null; } + public static System.Numerics.Decimal64 operator +(System.Numerics.Decimal64 value) { throw null; } + public static System.Numerics.Decimal64 Parse(System.ReadOnlySpan s, System.Globalization.NumberStyles style = System.Globalization.NumberStyles.AllowDecimalPoint | System.Globalization.NumberStyles.AllowExponent | System.Globalization.NumberStyles.AllowLeadingSign | System.Globalization.NumberStyles.AllowLeadingWhite | System.Globalization.NumberStyles.AllowThousands | System.Globalization.NumberStyles.AllowTrailingWhite, System.IFormatProvider? provider = null) { throw null; } + public static System.Numerics.Decimal64 Parse(System.ReadOnlySpan s, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Decimal64 Parse(string s) { throw null; } + public static System.Numerics.Decimal64 Parse(string s, System.Globalization.NumberStyles style) { throw null; } + public static System.Numerics.Decimal64 Parse(string s, System.Globalization.NumberStyles style, System.IFormatProvider? provider) { throw null; } + public static System.Numerics.Decimal64 Parse(string s, System.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 Decimal128 operator +(Decimal128 value) { throw null; } - public static Decimal128 operator -(Decimal128 value) { throw null; } - public static Decimal128 operator ++(Decimal128 value) { throw null; } - public static Decimal128 operator --(Decimal128 value) { throw null; } - public static Decimal128 operator +(Decimal128 left, Decimal128 right) { throw null; } - public static Decimal128 operator -(Decimal128 left, Decimal128 right) { throw null; } - public static Decimal128 operator *(Decimal128 left, Decimal128 right) { throw null; } - public static Decimal128 operator /(Decimal128 left, Decimal128 right) { throw null; } - public static bool operator ==(Decimal128 left, Decimal128 right) { throw null; } - public static bool operator !=(Decimal128 left, Decimal128 right) { throw null; } - public static bool operator <(Decimal128 left, Decimal128 right) { throw null; } - public static bool operator >(Decimal128 left, Decimal128 right) { throw null; } - public static bool operator <=(Decimal128 left, Decimal128 right) { throw null; } - public static bool operator >=(Decimal128 left, Decimal128 right) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format) { throw null; } + public string ToString([System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] string? format, System.IFormatProvider? provider) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal64 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal64 result) { throw null; } + public static bool TryParse(System.ReadOnlySpan s, out System.Numerics.Decimal64 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal64 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal64 result) { throw null; } + public static bool TryParse([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, out System.Numerics.Decimal64 result) { throw null; } } public readonly partial struct BFloat16 : System.IComparable, System.IComparable, System.IEquatable, System.IFormattable, System.IParsable, System.ISpanFormattable, System.ISpanParsable, System.IUtf8SpanFormattable, System.IUtf8SpanParsable, System.Numerics.IAdditionOperators, System.Numerics.IAdditiveIdentity, System.Numerics.IBinaryFloatingPointIeee754, System.Numerics.IBinaryNumber, System.Numerics.IBitwiseOperators, System.Numerics.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IExponentialFunctions, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IFloatingPointIeee754, System.Numerics.IHyperbolicFunctions, System.Numerics.IIncrementOperators, System.Numerics.ILogarithmicFunctions, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.IPowerFunctions, System.Numerics.IRootFunctions, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.ITrigonometricFunctions, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators From 72489df4f6f20a0e600be0940ee195be593633dc Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 10 Jul 2026 10:44:26 -0700 Subject: [PATCH 10/19] Note reciprocal-multiply table optimization opportunity in WideDivideByTen The Intel reference implementation divides by powers of ten using precomputed reciprocals; this helper uses direct division for now. Add a remark documenting the potential future optimization. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index 6ebecc5507cb0a..33bc76d7edb362 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -983,6 +983,12 @@ private static void WideMultiply(TValue left, TValue right, out TValue h /// Divides the double-width value in (, ) by ten in place, /// returning the discarded least-significant decimal digit. Used to strip low-order digits during rounding. /// + /// + /// The Intel reference implementation avoids hardware division here by multiplying with precomputed + /// reciprocals of powers of ten (e.g. bid_reciprocals10_64) and shifting. This helper instead uses + /// direct integer division for simplicity; adopting the reciprocal-multiply tables is a possible future + /// performance optimization. + /// private static int WideDivideByTen(ref TValue high, ref TValue low) where TValue : unmanaged, IBinaryInteger { From 2bbabffd3433ee8616b6f42ab51b3528dec94344 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 10 Jul 2026 10:50:13 -0700 Subject: [PATCH 11/19] Add Intel Decimal Floating-Point Math Library notice to THIRD-PARTY-NOTICES The Decimal32/64/128 arithmetic is based on Intel's BSD 3-Clause licensed reference implementation. Reproduce the copyright and license text as required by the license. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- THIRD-PARTY-NOTICES.TXT | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/THIRD-PARTY-NOTICES.TXT b/THIRD-PARTY-NOTICES.TXT index 2419a1a34585a9..ca41ed84ced12b 100644 --- a/THIRD-PARTY-NOTICES.TXT +++ b/THIRD-PARTY-NOTICES.TXT @@ -1664,3 +1664,36 @@ Available at https://github.com/C2SP/wycheproof/blob/2fb7240f471b70bb3aa5cc9b071 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + +License notice for Intel Decimal Floating-Point Math Library +------------------------------- + +https://www.intel.com/content/www/us/en/developer/articles/tool/intel-decimal-floating-point-math-library.html + +Copyright (c) 2007-2025, Intel Corp. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, +are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + * Neither the name of Intel Corporation nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. +IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From c15767a2b489ab7ec22760bde6344bdffc4bb43c Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 10 Jul 2026 11:43:27 -0700 Subject: [PATCH 12/19] Treat non-canonical finite decimal encodings as zero when unpacking IEEE 754 BID specifies that a finite encoding whose significand exceeds the maximum representable coefficient (10^p - 1) is non-canonical and represents zero. The generic UnpackDecimalIeee754 helper decoded the raw significand without applying this rule, so arithmetic and comparisons on such bit patterns could misbehave (e.g. finite/nc not treated as division by zero). Add a single clamp inside UnpackDecimalIeee754, matching unpack_BID32/64/128 in the Intel Decimal Floating-Point Math Library, so every downstream consumer (Add/Subtract/Multiply/Divide, Compare, GetHashCode) handles all finite bit patterns correctly. Also fix copy/pasted addition comments in op_Subtraction_TestData across all three test files so they describe subtraction operands and outcomes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 10 ++ .../System/Decimal128Tests.cs | 103 +++++++++++++----- .../System/Decimal32Tests.cs | 102 ++++++++++++----- .../System/Decimal64Tests.cs | 102 ++++++++++++----- 4 files changed, 227 insertions(+), 90 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index 33bc76d7edb362..ee1e2fad7df771 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -75,6 +75,16 @@ internal static DecodedDecimalIeee754 UnpackDecimalIeee754 TDecimal.MaxSignificand) + { + significand = TValue.Zero; + } + return new DecodedDecimalIeee754(signed, biasedExponent - TDecimal.ExponentBias, significand); } 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 index 8b8ef06306d099..3a64d3e7da6288 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -503,6 +503,49 @@ public static IEnumerable NaN_NonCanonicalEncodings128_TestData() yield return new object[] { Decimal128.NaN, canonical | (((UInt128)1 << 110) - 1) }; } + public static IEnumerable Zero_NonCanonicalEncodings_TestData() + { + // A finite encoding whose significand exceeds MaxSignificand (10^34 - 1) is non-canonical and represents zero. + // For Decimal128 every large-coefficient (G0G1 == 11) finite encoding is non-canonical. + UInt128 positiveZero = new UInt128(0x3040_0000_0000_0000, 0); + UInt128 negativeZero = new UInt128(0xB040_0000_0000_0000, 0); + + yield return new object[] { positiveZero, new UInt128(0x6C10_7FFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF) }; + yield return new object[] { positiveZero, new UInt128(0x6C10_0000_0000_0000, 0x0000_0000_0000_0001) }; + yield return new object[] { negativeZero, new UInt128(0xEC10_7FFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF) }; + yield return new object[] { negativeZero, new UInt128(0xEC10_0000_0000_0000, 0x0000_0000_0000_0001) }; + } + + [Theory] + [MemberData(nameof(Zero_NonCanonicalEncodings_TestData))] + public static void Finite_NonCanonicalEncodings_BehaveAsZero(UInt128 canonicalZero, UInt128 encoding) + { + Decimal128 zero = Unsafe.BitCast(canonicalZero); + Decimal128 nc = Unsafe.BitCast(encoding); + Decimal128 one = Unsafe.BitCast(new UInt128(0x3040_0000_0000_0000, 1)); + Decimal128 inf = Decimal128.PositiveInfinity; + + // A non-canonical finite encoding compares, hashes, and equals as zero. + Assert.Equal(0, zero.CompareTo(nc)); + Assert.True(zero == nc); + Assert.Equal(zero.GetHashCode(), nc.GetHashCode()); + + // Arithmetic treats the non-canonical operand identically to canonical zero. + Assert.Equal(Bits(zero + one), Bits(nc + one)); + Assert.Equal(Bits(zero - one), Bits(nc - one)); + Assert.Equal(Bits(one + zero), Bits(one + nc)); + Assert.Equal(Bits(zero * one), Bits(nc * one)); + Assert.Equal(Bits(one * zero), Bits(one * nc)); + Assert.Equal(Bits(zero / one), Bits(nc / one)); + + // Division-by-zero and invalid operations must trigger for non-canonical zero. + Assert.Equal(Bits(one / zero), Bits(one / nc)); // finite / 0 -> Infinity + Assert.Equal(Bits(zero / zero), Bits(nc / nc)); // 0 / 0 -> NaN + Assert.Equal(Bits(inf * zero), Bits(inf * nc)); // Infinity * 0 -> NaN + + static UInt128 Bits(Decimal128 value) => Unsafe.BitCast(value); + } + public static IEnumerable UnaryNegation_TestData() { yield return new object[] { new UInt128(0x3040_0000_0000_0000, 0), new UInt128(0xB040_0000_0000_0000, 0) }; // +0 -> -0 @@ -652,36 +695,36 @@ public static void op_Addition(UInt128 left, UInt128 right, UInt128 expected) public static IEnumerable op_Subtraction_TestData() { - yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN + 1 -> NaN (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // 1 + NaN -> NaN (sub) - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + 1 -> +Inf (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // 1 + +Inf -> +Inf (sub) - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + -Inf -> NaN (sub) - yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf + +Inf -> NaN (sub) - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf + +Inf -> +Inf (sub) - yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf + -Inf -> -Inf (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 + +0 -> +0 (sub) - yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -0 + -0 -> -0 (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 + -0 -> +0 (round-half-even) (sub) - yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000) }; // -0 + +0 -> +0 (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 1 + 0 -> 1 (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000001) }; // 0 + 1 -> 1 (sub) - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf + 1 -> canonical +Inf (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000005), new UInt128(0x7800000000000000, 0x0000000000000000) }; // 1 + non-canonical -Inf -> canonical -Inf (sub) - yield return new object[] { new UInt128(0x7800000000000000, 0x000000000000000F), new UInt128(0xF800000000000000, 0x0000000000000003), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf + non-canonical -Inf -> NaN (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0xB040000000000000, 0x0000000000000001) }; // 1 + 2 -> 3 (sub) - yield return new object[] { new UInt128(0x303E000000000000, 0x000000000000000F), new UInt128(0x303E000000000000, 0x0000000000000019), new UInt128(0xB03E000000000000, 0x000000000000000A) }; // 1.5 + 2.5 -> 4.0 (sub) - yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000002), new UInt128(0xB03E000000000000, 0x0000000000000001) }; // 0.1 + 0.2 -> 0.3 (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002) }; // 1 + -1 -> +0 (sub) - yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000002) }; // -1 + 1 -> +0 (sub) - yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE) }; // all-nines + 1 (carry/overflow to next magnitude) (sub) - yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3040000000000000, 0x0000000000000000) }; // big + big (round) (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x2FFC000000000000, 0x0000000000000001), new UInt128(0x2FFDED09BEAD87C0, 0x378D8E63FFFFFFFF) }; // 1 + 1e-P (alignment beyond precision) (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x2FF2000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // 1 + tiny (sticky rounding) (sub) - yield return new object[] { new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE000000000000, 0x0000000000000000) }; // max-ish + max-ish (overflow to Inf) (sub) - yield return new object[] { new UInt128(0x3034000000000000, 0x000000000012D687), new UInt128(0x3034000000000000, 0x000000000074CBB1), new UInt128(0xB034000000000000, 0x000000000061F52A) }; // cohort / preferred exponent (sub) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000064), new UInt128(0x303A000000000000, 0x0000000000000001), new UInt128(0x303A000000000000, 0x000000000001869F) }; // 100 + 0.001 (exponent spread) (sub) - yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0xB041ED09BEAD87C0, 0x378D8E63FFFFFFFE), new UInt128(0x3042629B8C891B26, 0x7182B61400000000) }; // cancellation leaving small (sub) + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN - 1 -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // 1 - NaN -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf - 1 -> +Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // 1 - +Inf -> -Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf - -Inf -> +Inf + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf - +Inf -> -Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf - +Inf -> NaN + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf - -Inf -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 - +0 -> +0 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -0 - -0 -> +0 (round-half-even) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 - -0 -> +0 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000) }; // -0 - +0 -> -0 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 1 - 0 -> 1 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000001) }; // 0 - 1 -> -1 + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf - 1 -> canonical +Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000005), new UInt128(0x7800000000000000, 0x0000000000000000) }; // 1 - non-canonical -Inf -> canonical +Inf + yield return new object[] { new UInt128(0x7800000000000000, 0x000000000000000F), new UInt128(0xF800000000000000, 0x0000000000000003), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf - non-canonical -Inf -> canonical +Inf + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0xB040000000000000, 0x0000000000000001) }; // 1 - 2 -> -1 + yield return new object[] { new UInt128(0x303E000000000000, 0x000000000000000F), new UInt128(0x303E000000000000, 0x0000000000000019), new UInt128(0xB03E000000000000, 0x000000000000000A) }; // 1.5 - 2.5 -> -1.0 + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000002), new UInt128(0xB03E000000000000, 0x0000000000000001) }; // 0.1 - 0.2 -> -0.1 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002) }; // 1 - -1 -> 2 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000002) }; // -1 - 1 -> -2 + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE) }; // all-nines - 1 + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3040000000000000, 0x0000000000000000) }; // big - big -> +0 + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x2FFC000000000000, 0x0000000000000001), new UInt128(0x2FFDED09BEAD87C0, 0x378D8E63FFFFFFFF) }; // 1 - 1e-P -> 0.999... (alignment beyond precision) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x2FF2000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // 1 - tiny -> 1.000... (sticky rounding) + yield return new object[] { new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE000000000000, 0x0000000000000000) }; // max-ish - max-ish -> +0 (preferred exponent retained) + yield return new object[] { new UInt128(0x3034000000000000, 0x000000000012D687), new UInt128(0x3034000000000000, 0x000000000074CBB1), new UInt128(0xB034000000000000, 0x000000000061F52A) }; // cohort / preferred exponent + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000064), new UInt128(0x303A000000000000, 0x0000000000000001), new UInt128(0x303A000000000000, 0x000000000001869F) }; // 100 - 0.001 -> 99.999 (exponent spread) + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0xB041ED09BEAD87C0, 0x378D8E63FFFFFFFE), new UInt128(0x3042629B8C891B26, 0x7182B61400000000) }; // opposite signs subtract as add (magnitudes add, carry/rounding) yield return new object[] { new UInt128(0x300C000000000000, 0x0386DF690C6EF5BE), new UInt128(0xB018000000000000, 0x00022FCFF31AA0B9), new UInt128(0x300C000000000021, 0x6195AEA21B5DD5FE) }; yield return new object[] { new UInt128(0x3016000000000000, 0x00000000025C222D), new UInt128(0xAFEE0000105989B6, 0x14E871DF7FB4E7FF), new UInt128(0x2FEE00001D248D5C, 0xB48BC0C20984E7FF) }; yield return new object[] { new UInt128(0xAFE4004BC3F60664, 0xA2D28316972FB16C), new UInt128(0x3024000000000000, 0x336683344D857468), new UInt128(0xB006B69C6D6AD20B, 0x43BC6455A5EEF320) }; 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 index 350a8c002c1623..dc46a856151ed1 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -499,6 +499,48 @@ public static IEnumerable NaN_NonCanonicalEncodings_TestData() yield return new object[] { Decimal32.NaN, canonical | 0x03FF_FFFFU }; } + public static IEnumerable Zero_NonCanonicalEncodings_TestData() + { + // A finite encoding whose significand exceeds MaxSignificand (9_999_999) is non-canonical and represents zero. + const uint PositiveZero = 0x3280_0000; + const uint NegativeZero = 0xB280_0000; + + yield return new object[] { PositiveZero, 0x6CBF_FFFFU }; // significand 0x9F_FFFF + yield return new object[] { PositiveZero, 0x6CB8_9680U }; // significand 0x98_9680 (== 10_000_000) + yield return new object[] { NegativeZero, 0xECBF_FFFFU }; + yield return new object[] { NegativeZero, 0xECB8_9680U }; + } + + [Theory] + [MemberData(nameof(Zero_NonCanonicalEncodings_TestData))] + public static void Finite_NonCanonicalEncodings_BehaveAsZero(uint canonicalZero, uint encoding) + { + Decimal32 zero = Unsafe.BitCast(canonicalZero); + Decimal32 nc = Unsafe.BitCast(encoding); + Decimal32 one = Unsafe.BitCast(0x3280_0001U); + Decimal32 inf = Decimal32.PositiveInfinity; + + // A non-canonical finite encoding compares, hashes, and equals as zero. + Assert.Equal(0, zero.CompareTo(nc)); + Assert.True(zero == nc); + Assert.Equal(zero.GetHashCode(), nc.GetHashCode()); + + // Arithmetic treats the non-canonical operand identically to canonical zero. + Assert.Equal(Bits(zero + one), Bits(nc + one)); + Assert.Equal(Bits(zero - one), Bits(nc - one)); + Assert.Equal(Bits(one + zero), Bits(one + nc)); + Assert.Equal(Bits(zero * one), Bits(nc * one)); + Assert.Equal(Bits(one * zero), Bits(one * nc)); + Assert.Equal(Bits(zero / one), Bits(nc / one)); + + // Division-by-zero and invalid operations must trigger for non-canonical zero. + Assert.Equal(Bits(one / zero), Bits(one / nc)); // finite / 0 -> Infinity + Assert.Equal(Bits(zero / zero), Bits(nc / nc)); // 0 / 0 -> NaN + Assert.Equal(Bits(inf * zero), Bits(inf * nc)); // Infinity * 0 -> NaN + + static uint Bits(Decimal32 value) => Unsafe.BitCast(value); + } + public static IEnumerable UnaryNegation_TestData() { yield return new object[] { 0x3280_0000U, 0xB280_0000U }; // +0 -> -0 @@ -652,36 +694,36 @@ public static void op_Addition(uint left, uint right, uint expected) public static IEnumerable op_Subtraction_TestData() { - yield return new object[] { 0xFC000000U, 0x32800001U, 0xFC000000U }; // NaN + 1 -> NaN (sub) - yield return new object[] { 0x32800001U, 0xFC000000U, 0xFC000000U }; // 1 + NaN -> NaN (sub) - yield return new object[] { 0x78000000U, 0x32800001U, 0x78000000U }; // +Inf + 1 -> +Inf (sub) - yield return new object[] { 0x32800001U, 0x78000000U, 0xF8000000U }; // 1 + +Inf -> +Inf (sub) - yield return new object[] { 0x78000000U, 0xF8000000U, 0x78000000U }; // +Inf + -Inf -> NaN (sub) - yield return new object[] { 0xF8000000U, 0x78000000U, 0xF8000000U }; // -Inf + +Inf -> NaN (sub) - yield return new object[] { 0x78000000U, 0x78000000U, 0xFC000000U }; // +Inf + +Inf -> +Inf (sub) - yield return new object[] { 0xF8000000U, 0xF8000000U, 0xFC000000U }; // -Inf + -Inf -> -Inf (sub) - yield return new object[] { 0x32800000U, 0x32800000U, 0x32800000U }; // +0 + +0 -> +0 (sub) - yield return new object[] { 0xB2800000U, 0xB2800000U, 0x32800000U }; // -0 + -0 -> -0 (sub) - yield return new object[] { 0x32800000U, 0xB2800000U, 0x32800000U }; // +0 + -0 -> +0 (round-half-even) (sub) - yield return new object[] { 0xB2800000U, 0x32800000U, 0xB2800000U }; // -0 + +0 -> +0 (sub) - yield return new object[] { 0x32800001U, 0x32800000U, 0x32800001U }; // 1 + 0 -> 1 (sub) - yield return new object[] { 0x32800000U, 0x32800001U, 0xB2800001U }; // 0 + 1 -> 1 (sub) - yield return new object[] { 0x78000002U, 0x32800001U, 0x78000000U }; // non-canonical +Inf + 1 -> canonical +Inf (sub) - yield return new object[] { 0x32800001U, 0xF8000005U, 0x78000000U }; // 1 + non-canonical -Inf -> canonical -Inf (sub) - yield return new object[] { 0x7800000FU, 0xF8000003U, 0x78000000U }; // non-canonical +Inf + non-canonical -Inf -> NaN (sub) - yield return new object[] { 0x32800001U, 0x32800002U, 0xB2800001U }; // 1 + 2 -> 3 (sub) - yield return new object[] { 0x3200000FU, 0x32000019U, 0xB200000AU }; // 1.5 + 2.5 -> 4.0 (sub) - yield return new object[] { 0x32000001U, 0x32000002U, 0xB2000001U }; // 0.1 + 0.2 -> 0.3 (sub) - yield return new object[] { 0x32800001U, 0xB2800001U, 0x32800002U }; // 1 + -1 -> +0 (sub) - yield return new object[] { 0xB2800001U, 0x32800001U, 0xB2800002U }; // -1 + 1 -> +0 (sub) - yield return new object[] { 0x6CB8967FU, 0x32800001U, 0x6CB8967EU }; // all-nines + 1 (carry/overflow to next magnitude) (sub) - yield return new object[] { 0x6CB8967FU, 0x6CB8967FU, 0x32800000U }; // big + big (round) (sub) - yield return new object[] { 0x32800001U, 0x2F000001U, 0x6BD8967FU }; // 1 + 1e-P (alignment beyond precision) (sub) - yield return new object[] { 0x32800001U, 0x2C800001U, 0x2F8F4240U }; // 1 + tiny (sticky rounding) (sub) - yield return new object[] { 0x5F8F4240U, 0x5F8F4240U, 0x5F800000U }; // max-ish + max-ish (overflow to Inf) (sub) - yield return new object[] { 0x2F92D687U, 0x2FF4CBB1U, 0xAFE1F52AU }; // cohort / preferred exponent (sub) - yield return new object[] { 0x32800064U, 0x31000001U, 0x3101869FU }; // 100 + 0.001 (exponent spread) (sub) - yield return new object[] { 0x6CB8967FU, 0xECB8967EU, 0x331E8480U }; // cancellation leaving small (sub) + yield return new object[] { 0xFC000000U, 0x32800001U, 0xFC000000U }; // NaN - 1 -> NaN + yield return new object[] { 0x32800001U, 0xFC000000U, 0xFC000000U }; // 1 - NaN -> NaN + yield return new object[] { 0x78000000U, 0x32800001U, 0x78000000U }; // +Inf - 1 -> +Inf + yield return new object[] { 0x32800001U, 0x78000000U, 0xF8000000U }; // 1 - +Inf -> -Inf + yield return new object[] { 0x78000000U, 0xF8000000U, 0x78000000U }; // +Inf - -Inf -> +Inf + yield return new object[] { 0xF8000000U, 0x78000000U, 0xF8000000U }; // -Inf - +Inf -> -Inf + yield return new object[] { 0x78000000U, 0x78000000U, 0xFC000000U }; // +Inf - +Inf -> NaN + yield return new object[] { 0xF8000000U, 0xF8000000U, 0xFC000000U }; // -Inf - -Inf -> NaN + yield return new object[] { 0x32800000U, 0x32800000U, 0x32800000U }; // +0 - +0 -> +0 + yield return new object[] { 0xB2800000U, 0xB2800000U, 0x32800000U }; // -0 - -0 -> +0 (round-half-even) + yield return new object[] { 0x32800000U, 0xB2800000U, 0x32800000U }; // +0 - -0 -> +0 + yield return new object[] { 0xB2800000U, 0x32800000U, 0xB2800000U }; // -0 - +0 -> -0 + yield return new object[] { 0x32800001U, 0x32800000U, 0x32800001U }; // 1 - 0 -> 1 + yield return new object[] { 0x32800000U, 0x32800001U, 0xB2800001U }; // 0 - 1 -> -1 + yield return new object[] { 0x78000002U, 0x32800001U, 0x78000000U }; // non-canonical +Inf - 1 -> canonical +Inf + yield return new object[] { 0x32800001U, 0xF8000005U, 0x78000000U }; // 1 - non-canonical -Inf -> canonical +Inf + yield return new object[] { 0x7800000FU, 0xF8000003U, 0x78000000U }; // non-canonical +Inf - non-canonical -Inf -> canonical +Inf + yield return new object[] { 0x32800001U, 0x32800002U, 0xB2800001U }; // 1 - 2 -> -1 + yield return new object[] { 0x3200000FU, 0x32000019U, 0xB200000AU }; // 1.5 - 2.5 -> -1.0 + yield return new object[] { 0x32000001U, 0x32000002U, 0xB2000001U }; // 0.1 - 0.2 -> -0.1 + yield return new object[] { 0x32800001U, 0xB2800001U, 0x32800002U }; // 1 - -1 -> 2 + yield return new object[] { 0xB2800001U, 0x32800001U, 0xB2800002U }; // -1 - 1 -> -2 + yield return new object[] { 0x6CB8967FU, 0x32800001U, 0x6CB8967EU }; // all-nines - 1 -> 9_999_998 + yield return new object[] { 0x6CB8967FU, 0x6CB8967FU, 0x32800000U }; // big - big -> +0 + yield return new object[] { 0x32800001U, 0x2F000001U, 0x6BD8967FU }; // 1 - 1e-7 -> 0.9999999 (alignment beyond precision) + yield return new object[] { 0x32800001U, 0x2C800001U, 0x2F8F4240U }; // 1 - 1e-12 -> 1.000000 (sticky rounding) + yield return new object[] { 0x5F8F4240U, 0x5F8F4240U, 0x5F800000U }; // max-ish - max-ish -> +0 (preferred exponent retained) + yield return new object[] { 0x2F92D687U, 0x2FF4CBB1U, 0xAFE1F52AU }; // cohort / preferred exponent + yield return new object[] { 0x32800064U, 0x31000001U, 0x3101869FU }; // 100 - 0.001 -> 99.999 (exponent spread) + yield return new object[] { 0x6CB8967FU, 0xECB8967EU, 0x331E8480U }; // opposite signs subtract as add: 9_999_999 - -9_999_998 -> 2.0e7 (carry/rounding) yield return new object[] { 0x2F2C8C35U, 0x2A00002DU, 0x2F2C8C35U }; yield return new object[] { 0xB8002104U, 0xB98043D6U, 0x389A7C4BU }; yield return new object[] { 0xA8800010U, 0x34802213U, 0xECC51A38U }; 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 index 97a419850cfdc2..8e335f3959847e 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -505,6 +505,48 @@ public static IEnumerable NaN_NonCanonicalEncodings64_TestData() yield return new object[] { Decimal64.NaN, canonical | 0x03FF_FFFF_FFFF_FFFFUL }; } + public static IEnumerable Zero_NonCanonicalEncodings_TestData() + { + // A finite encoding whose significand exceeds MaxSignificand (9_999_999_999_999_999) is non-canonical and represents zero. + const ulong PositiveZero = 0x31C0_0000_0000_0000; + const ulong NegativeZero = 0xB1C0_0000_0000_0000; + + yield return new object[] { PositiveZero, 0x6C77_FFFF_FFFF_FFFFUL }; + yield return new object[] { PositiveZero, 0x6C73_86F2_6FC1_0000UL }; + yield return new object[] { NegativeZero, 0xEC77_FFFF_FFFF_FFFFUL }; + yield return new object[] { NegativeZero, 0xEC73_86F2_6FC1_0000UL }; + } + + [Theory] + [MemberData(nameof(Zero_NonCanonicalEncodings_TestData))] + public static void Finite_NonCanonicalEncodings_BehaveAsZero(ulong canonicalZero, ulong encoding) + { + Decimal64 zero = Unsafe.BitCast(canonicalZero); + Decimal64 nc = Unsafe.BitCast(encoding); + Decimal64 one = Unsafe.BitCast(0x31C0_0000_0000_0001UL); + Decimal64 inf = Decimal64.PositiveInfinity; + + // A non-canonical finite encoding compares, hashes, and equals as zero. + Assert.Equal(0, zero.CompareTo(nc)); + Assert.True(zero == nc); + Assert.Equal(zero.GetHashCode(), nc.GetHashCode()); + + // Arithmetic treats the non-canonical operand identically to canonical zero. + Assert.Equal(Bits(zero + one), Bits(nc + one)); + Assert.Equal(Bits(zero - one), Bits(nc - one)); + Assert.Equal(Bits(one + zero), Bits(one + nc)); + Assert.Equal(Bits(zero * one), Bits(nc * one)); + Assert.Equal(Bits(one * zero), Bits(one * nc)); + Assert.Equal(Bits(zero / one), Bits(nc / one)); + + // Division-by-zero and invalid operations must trigger for non-canonical zero. + Assert.Equal(Bits(one / zero), Bits(one / nc)); // finite / 0 -> Infinity + Assert.Equal(Bits(zero / zero), Bits(nc / nc)); // 0 / 0 -> NaN + Assert.Equal(Bits(inf * zero), Bits(inf * nc)); // Infinity * 0 -> NaN + + static ulong Bits(Decimal64 value) => Unsafe.BitCast(value); + } + public static IEnumerable UnaryNegation_TestData() { yield return new object[] { 0x31C0_0000_0000_0000UL, 0xB1C0_0000_0000_0000UL }; // +0 -> -0 @@ -658,36 +700,36 @@ public static void op_Addition(ulong left, ulong right, ulong expected) public static IEnumerable op_Subtraction_TestData() { - yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, 0xFC000000_00000000UL }; // NaN + 1 -> NaN (sub) - yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // 1 + NaN -> NaN (sub) - yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // +Inf + 1 -> +Inf (sub) - yield return new object[] { 0x31C00000_00000001UL, 0x78000000_00000000UL, 0xF8000000_00000000UL }; // 1 + +Inf -> +Inf (sub) - yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0x78000000_00000000UL }; // +Inf + -Inf -> NaN (sub) - yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0xF8000000_00000000UL }; // -Inf + +Inf -> NaN (sub) - yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // +Inf + +Inf -> +Inf (sub) - yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // -Inf + -Inf -> -Inf (sub) - yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0x31C00000_00000000UL }; // +0 + +0 -> +0 (sub) - yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // -0 + -0 -> -0 (sub) - yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // +0 + -0 -> +0 (round-half-even) (sub) - yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000000UL, 0xB1C00000_00000000UL }; // -0 + +0 -> +0 (sub) - yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000000UL, 0x31C00000_00000001UL }; // 1 + 0 -> 1 (sub) - yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000001UL, 0xB1C00000_00000001UL }; // 0 + 1 -> 1 (sub) - yield return new object[] { 0x78000000_00000002UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // non-canonical +Inf + 1 -> canonical +Inf (sub) - yield return new object[] { 0x31C00000_00000001UL, 0xF8000000_00000005UL, 0x78000000_00000000UL }; // 1 + non-canonical -Inf -> canonical -Inf (sub) - yield return new object[] { 0x78000000_0000000FUL, 0xF8000000_00000003UL, 0x78000000_00000000UL }; // non-canonical +Inf + non-canonical -Inf -> NaN (sub) - yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL, 0xB1C00000_00000001UL }; // 1 + 2 -> 3 (sub) - yield return new object[] { 0x31A00000_0000000FUL, 0x31A00000_00000019UL, 0xB1A00000_0000000AUL }; // 1.5 + 2.5 -> 4.0 (sub) - yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_00000002UL, 0xB1A00000_00000001UL }; // 0.1 + 0.2 -> 0.3 (sub) - yield return new object[] { 0x31C00000_00000001UL, 0xB1C00000_00000001UL, 0x31C00000_00000002UL }; // 1 + -1 -> +0 (sub) - yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000001UL, 0xB1C00000_00000002UL }; // -1 + 1 -> +0 (sub) - yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x31C00000_00000001UL, 0x6C7386F2_6FC0FFFEUL }; // all-nines + 1 (carry/overflow to next magnitude) (sub) - yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFFUL, 0x31C00000_00000000UL }; // big + big (round) (sub) - yield return new object[] { 0x31C00000_00000001UL, 0x2FC00000_00000001UL, 0x6BF386F2_6FC0FFFFUL }; // 1 + 1e-P (alignment beyond precision) (sub) - yield return new object[] { 0x31C00000_00000001UL, 0x2F200000_00000001UL, 0x2FE38D7E_A4C68000UL }; // 1 + tiny (sticky rounding) (sub) - yield return new object[] { 0x5FE38D7E_A4C68000UL, 0x5FE38D7E_A4C68000UL, 0x5FE00000_00000000UL }; // max-ish + max-ish (overflow to Inf) (sub) - yield return new object[] { 0x31000000_0012D687UL, 0x31000000_0074CBB1UL, 0xB1000000_0061F52AUL }; // cohort / preferred exponent (sub) - yield return new object[] { 0x31C00000_00000064UL, 0x31600000_00000001UL, 0x31600000_0001869FUL }; // 100 + 0.001 (exponent spread) (sub) - yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0xEC7386F2_6FC0FFFEUL, 0x31E71AFD_498D0000UL }; // cancellation leaving small (sub) + yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, 0xFC000000_00000000UL }; // NaN - 1 -> NaN + yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // 1 - NaN -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // +Inf - 1 -> +Inf + yield return new object[] { 0x31C00000_00000001UL, 0x78000000_00000000UL, 0xF8000000_00000000UL }; // 1 - +Inf -> -Inf + yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0x78000000_00000000UL }; // +Inf - -Inf -> +Inf + yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0xF8000000_00000000UL }; // -Inf - +Inf -> -Inf + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // +Inf - +Inf -> NaN + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // -Inf - -Inf -> NaN + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0x31C00000_00000000UL }; // +0 - +0 -> +0 + yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // -0 - -0 -> +0 (round-half-even) + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // +0 - -0 -> +0 + yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000000UL, 0xB1C00000_00000000UL }; // -0 - +0 -> -0 + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000000UL, 0x31C00000_00000001UL }; // 1 - 0 -> 1 + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000001UL, 0xB1C00000_00000001UL }; // 0 - 1 -> -1 + yield return new object[] { 0x78000000_00000002UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // non-canonical +Inf - 1 -> canonical +Inf + yield return new object[] { 0x31C00000_00000001UL, 0xF8000000_00000005UL, 0x78000000_00000000UL }; // 1 - non-canonical -Inf -> canonical +Inf + yield return new object[] { 0x78000000_0000000FUL, 0xF8000000_00000003UL, 0x78000000_00000000UL }; // non-canonical +Inf - non-canonical -Inf -> canonical +Inf + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL, 0xB1C00000_00000001UL }; // 1 - 2 -> -1 + yield return new object[] { 0x31A00000_0000000FUL, 0x31A00000_00000019UL, 0xB1A00000_0000000AUL }; // 1.5 - 2.5 -> -1.0 + yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_00000002UL, 0xB1A00000_00000001UL }; // 0.1 - 0.2 -> -0.1 + yield return new object[] { 0x31C00000_00000001UL, 0xB1C00000_00000001UL, 0x31C00000_00000002UL }; // 1 - -1 -> 2 + yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000001UL, 0xB1C00000_00000002UL }; // -1 - 1 -> -2 + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x31C00000_00000001UL, 0x6C7386F2_6FC0FFFEUL }; // all-nines - 1 -> 9_999_999_999_999_998 + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFFUL, 0x31C00000_00000000UL }; // big - big -> +0 + yield return new object[] { 0x31C00000_00000001UL, 0x2FC00000_00000001UL, 0x6BF386F2_6FC0FFFFUL }; // 1 - 1e-16 -> 0.9999999999999999 (alignment beyond precision) + yield return new object[] { 0x31C00000_00000001UL, 0x2F200000_00000001UL, 0x2FE38D7E_A4C68000UL }; // 1 - 1e-21 -> 1.000000000000000 (sticky rounding) + yield return new object[] { 0x5FE38D7E_A4C68000UL, 0x5FE38D7E_A4C68000UL, 0x5FE00000_00000000UL }; // max-ish - max-ish -> +0 (preferred exponent retained) + yield return new object[] { 0x31000000_0012D687UL, 0x31000000_0074CBB1UL, 0xB1000000_0061F52AUL }; // cohort / preferred exponent + yield return new object[] { 0x31C00000_00000064UL, 0x31600000_00000001UL, 0x31600000_0001869FUL }; // 100 - 0.001 -> 99.999 (exponent spread) + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0xEC7386F2_6FC0FFFEUL, 0x31E71AFD_498D0000UL }; // opposite signs subtract as add: 9_999_999_999_999_999 - -9_999_999_999_999_998 -> 2.0e16 (carry/rounding) yield return new object[] { 0xAF200000_000023D2UL, 0xAFA34A58_43C82F35UL, 0x6BE0E772_A5D1D809UL }; yield return new object[] { 0x19C00000_0006B3C8UL, 0xB2400000_000014EDUL, 0x30D30829_C20FD000UL }; yield return new object[] { 0x31200054_42297730UL, 0x2F000063_A23BC84AUL, 0x30ACDB58_73BFC300UL }; From 12a7668352016591606604a4b7385ce82adf3552 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 10 Jul 2026 12:04:39 -0700 Subject: [PATCH 13/19] Skip WideDigitCount when the exponent already overflows to infinity In NumberToDecimalIeee754BitsFromWide the expensive WideDigitCount was computed before the exponent > MaxExponent early return, so the cost was paid even when immediately returning +/-Infinity. Move the overflow check ahead of the digit count. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System.Private.CoreLib/src/System/Number.DecimalIeee754.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index ee1e2fad7df771..da5dc801d38fa3 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -1090,13 +1090,14 @@ private static TValue NumberToDecimalIeee754BitsFromWide(bool } int precision = TDecimal.Precision; - int digitsCount = WideDigitCount(high, low); if (exponent > TDecimal.MaxExponent) { return sign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; } + int digitsCount = WideDigitCount(high, low); + if (exponent > TDecimal.MaxAdjustedExponent) { // The least-significant digit already sits above the largest representable quantum. The value is From eafeb1fbd8cb81d2224508a5c4f75576ac1fe193 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 10 Jul 2026 12:40:02 -0700 Subject: [PATCH 14/19] Use the Power10 table for in-range operand alignment in AddDecimalIeee754 The addition alignment scale factor 10^effectiveDifference was always computed via a multiply-by-ten loop. Reuse the format's existing Power10 lookup table: exponents in range (0..Precision-1) index it directly, and the larger alignment exponents (Precision..Precision+2, which the table does not cover) reduce by the largest table entry each iteration and finish with a single lookup for the remainder. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index da5dc801d38fa3..5258b7d9b53b1e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -666,7 +666,7 @@ internal static TValue AddDecimalIeee754(TValue left, TValue r // Align `hi` to the common exponent by scaling its coefficient up by 10^effectiveDifference. The // scaled coefficient can exceed a single limb (up to ~10^(2*Precision+1)), so it is held at double // width. The scale factor fits a single limb because effectiveDifference <= Precision + 2. - WideMultiply(hi.Significand, PowerOfTen(effectiveDifference), out TValue magnitudeHigh, out TValue magnitudeLow); + WideMultiply(hi.Significand, AlignmentScaleFactor(effectiveDifference), out TValue magnitudeHigh, out TValue magnitudeLow); // Align `lo` to the common exponent by discarding its `droppedDigits` least-significant digits, which // fall below the retained range and only contribute stickiness. The retained portion fits a single limb. @@ -942,23 +942,28 @@ internal static TValue DivideDecimalIeee754(TValue left, TValu } /// - /// Computes 10^ as a single limb. Unlike the - /// format's Power10 lookup table (which only covers exponents up to Precision - 1), this supports - /// the slightly larger exponents needed when aligning operands (up to Precision + 2), which still fit a - /// single limb for every supported format. + /// Computes the scale factor 10^ used to align addition operands. Exponents + /// within the format's Power10 lookup range (0..Precision - 1) come straight from that table, + /// matching the existing parsing/formatting paths. The slightly larger alignment exponents + /// (Precision..Precision + 2, which the table does not cover) reduce the exponent by the largest table + /// entry each iteration and finish with a single lookup for the remainder. The result always fits a single limb + /// for every supported format. /// - private static TValue PowerOfTen(int exponent) + private static TValue AlignmentScaleFactor(int exponent) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger { - TValue ten = TValue.CreateTruncating(10); + int highestTableExponent = TDecimal.Precision - 1; + TValue largest = TDecimal.Power10(highestTableExponent); TValue result = TValue.One; - for (int i = 0; i < exponent; i++) + while (exponent > highestTableExponent) { - result *= ten; + result *= largest; + exponent -= highestTableExponent; } - return result; + return result * TDecimal.Power10(exponent); } /// From 1abea0a31858ff95d2ef84e7a9bca1e9b6e0ea65 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 10 Jul 2026 16:07:54 -0700 Subject: [PATCH 15/19] Make increment/decrement operator test comments match their dataset The op_Increment_TestData and op_Decrement_TestData comments were copy/pasted, so several described the wrong operator (e.g. decrement cases annotated with '++ -> ...'). Rewrite each dataset's comments to describe only its own operator's semantics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System/Decimal128Tests.cs | 46 +++++++++---------- .../System/Decimal32Tests.cs | 46 +++++++++---------- .../System/Decimal64Tests.cs | 46 +++++++++---------- 3 files changed, 69 insertions(+), 69 deletions(-) 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 index 3a64d3e7da6288..4a5635cae3d1ec 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -1257,33 +1257,33 @@ public static IEnumerable op_Increment_TestData() yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf (canonicalizes) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // +0 (++ -> 1, -- -> -1) - yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // +0 -> 1 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // -0 -> 1 yield return new object[] { new UInt128(0x304A000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 0e5 (preferred exponent min(exp,0)) yield return new object[] { new UInt128(0x3036000000000000, 0x0000000000000000), new UInt128(0x3036000000000000, 0x00000000000186A0) }; // 0e-5 - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002) }; // 1 (++ -> 2, -- -> 0) - yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002) }; // 1 -> 2 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -1 -> 0 yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000003) }; // 2 yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000002), new UInt128(0xB040000000000000, 0x0000000000000001) }; // -2 yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x000000000000000B) }; // 10 - yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000005), new UInt128(0x303E000000000000, 0x000000000000000F) }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000005), new UInt128(0x303E000000000000, 0x000000000000000F) }; // 0.5 -> 1.5 yield return new object[] { new UInt128(0xB03E000000000000, 0x0000000000000005), new UInt128(0x303E000000000000, 0x0000000000000005) }; // -0.5 yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000019), new UInt128(0x303E000000000000, 0x0000000000000023) }; // 2.5 - yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x000000000000000B) }; // 0.1 (++ -> 1.1) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x000000000000000B) }; // 0.1 -> 1.1 yield return new object[] { new UInt128(0x303C000000000000, 0x0000000000000177), new UInt128(0x303C000000000000, 0x00000000000001DB) }; // 3.75 yield return new object[] { new UInt128(0xB03C000000000000, 0x0000000000000019), new UInt128(0x303C000000000000, 0x000000000000004B) }; // -0.25 - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000009), new UInt128(0x3040000000000000, 0x000000000000000A) }; // 9 (++ -> 10) - yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3042314DC6448D93, 0x38C15B0A00000000) }; // all-nines (++ overflows precision, rounds) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000009), new UInt128(0x3040000000000000, 0x000000000000000A) }; // 9 -> 10 + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3042314DC6448D93, 0x38C15B0A00000000) }; // all-nines (overflows precision, rounds) yield return new object[] { new UInt128(0x3040314DC6448D93, 0x38C15B0A00000000), new UInt128(0x3040314DC6448D93, 0x38C15B0A00000001) }; // 10^(P-1) yield return new object[] { new UInt128(0x3040314DC6448D93, 0x38C15B09FFFFFFFF), new UInt128(0x3040314DC6448D93, 0x38C15B0A00000000) }; // (P-1)-nines - yield return new object[] { new UInt128(0x3088000000000000, 0x0000000000000001), new UInt128(0x3046314DC6448D93, 0x38C15B0A00000000) }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { new UInt128(0x3088000000000000, 0x0000000000000001), new UInt128(0x3046314DC6448D93, 0x38C15B0A00000000) }; // 1e(P+2) (1 below quantum, no visible change) yield return new object[] { new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000) }; // near-max (1 negligible) yield return new object[] { new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000) }; // near -max (1 negligible) - yield return new object[] { new UInt128(0x0040000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // tiny subnormal (++ ~ 1) - yield return new object[] { new UInt128(0x8040000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // tiny negative subnormal (-- ~ -1) + yield return new object[] { new UInt128(0x0040000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // tiny positive subnormal (++ ~ 1) + yield return new object[] { new UInt128(0x8040000000000000, 0x0000000000000001), new UInt128(0x2FFE314DC6448D93, 0x38C15B0A00000000) }; // tiny negative subnormal (++ ~ 1) yield return new object[] { new UInt128(0x3036000000000000, 0x0000000000000001), new UInt128(0x3036000000000000, 0x00000000000186A1) }; // 1e-5 yield return new object[] { new UInt128(0x2FFE41BD085B676E, 0xF657240D55555555), new UInt128(0x2FFE730ACE9FF502, 0x2F187F1755555555) }; // 1.333... full precision - yield return new object[] { new UInt128(0x2FFFED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3000363BF3B1CEEE, 0xBE6E4A8B00000000) }; // 9.999... full precision (++ carry) + yield return new object[] { new UInt128(0x2FFFED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3000363BF3B1CEEE, 0xBE6E4A8B00000000) }; // 9.999... full precision (carry) yield return new object[] { new UInt128(0xB0000000000000B1, 0x4C1E4DF5ED261C68), new UInt128(0x300004EE2D6D40AA, 0x398EA18B12D9E398) }; yield return new object[] { new UInt128(0xB02C000000008117, 0x952633E9760D3996), new UInt128(0xB02C000000008117, 0x952633E722015596) }; yield return new object[] { new UInt128(0xAFF80000000000BC, 0x57570583228BEDE5), new UInt128(0x2FFDED09BEAD87BE, 0x556649372B2CAE43) }; @@ -1361,33 +1361,33 @@ public static IEnumerable op_Decrement_TestData() yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf (canonicalizes) - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001) }; // +0 (++ -> 1, -- -> -1) - yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001) }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001) }; // +0 -> -1 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001) }; // -0 -> -1 yield return new object[] { new UInt128(0x304A000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001) }; // 0e5 (preferred exponent min(exp,0)) yield return new object[] { new UInt128(0x3036000000000000, 0x0000000000000000), new UInt128(0xB036000000000000, 0x00000000000186A0) }; // 0e-5 - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000) }; // 1 (++ -> 2, -- -> 0) - yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000002) }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000000) }; // 1 -> 0 + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0xB040000000000000, 0x0000000000000002) }; // -1 -> -2 yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 2 yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000002), new UInt128(0xB040000000000000, 0x0000000000000003) }; // -2 yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x0000000000000009) }; // 10 - yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000005), new UInt128(0xB03E000000000000, 0x0000000000000005) }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000005), new UInt128(0xB03E000000000000, 0x0000000000000005) }; // 0.5 -> -0.5 yield return new object[] { new UInt128(0xB03E000000000000, 0x0000000000000005), new UInt128(0xB03E000000000000, 0x000000000000000F) }; // -0.5 yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000019), new UInt128(0x303E000000000000, 0x000000000000000F) }; // 2.5 - yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0xB03E000000000000, 0x0000000000000009) }; // 0.1 (++ -> 1.1) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0xB03E000000000000, 0x0000000000000009) }; // 0.1 -> -0.9 yield return new object[] { new UInt128(0x303C000000000000, 0x0000000000000177), new UInt128(0x303C000000000000, 0x0000000000000113) }; // 3.75 yield return new object[] { new UInt128(0xB03C000000000000, 0x0000000000000019), new UInt128(0xB03C000000000000, 0x000000000000007D) }; // -0.25 - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000009), new UInt128(0x3040000000000000, 0x0000000000000008) }; // 9 (++ -> 10) - yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE) }; // all-nines (++ overflows precision, rounds) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000009), new UInt128(0x3040000000000000, 0x0000000000000008) }; // 9 -> 8 + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE) }; // all-nines (decrement in last place) yield return new object[] { new UInt128(0x3040314DC6448D93, 0x38C15B0A00000000), new UInt128(0x3040314DC6448D93, 0x38C15B09FFFFFFFF) }; // 10^(P-1) yield return new object[] { new UInt128(0x3040314DC6448D93, 0x38C15B09FFFFFFFF), new UInt128(0x3040314DC6448D93, 0x38C15B09FFFFFFFE) }; // (P-1)-nines - yield return new object[] { new UInt128(0x3088000000000000, 0x0000000000000001), new UInt128(0x3046314DC6448D93, 0x38C15B0A00000000) }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { new UInt128(0x3088000000000000, 0x0000000000000001), new UInt128(0x3046314DC6448D93, 0x38C15B0A00000000) }; // 1e(P+2) (1 below quantum, no visible change) yield return new object[] { new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0x5FFFBBBBF868FA2C, 0xFECC335A00000000) }; // near-max (1 negligible) yield return new object[] { new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000), new UInt128(0xDFFFBBBBF868FA2C, 0xFECC335A00000000) }; // near -max (1 negligible) - yield return new object[] { new UInt128(0x0040000000000000, 0x0000000000000001), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; // tiny subnormal (++ ~ 1) + yield return new object[] { new UInt128(0x0040000000000000, 0x0000000000000001), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; // tiny positive subnormal (-- ~ -1) yield return new object[] { new UInt128(0x8040000000000000, 0x0000000000000001), new UInt128(0xAFFE314DC6448D93, 0x38C15B0A00000000) }; // tiny negative subnormal (-- ~ -1) yield return new object[] { new UInt128(0x3036000000000000, 0x0000000000000001), new UInt128(0xB036000000000000, 0x000000000001869F) }; // 1e-5 yield return new object[] { new UInt128(0x2FFE41BD085B676E, 0xF657240D55555555), new UInt128(0x2FFE106F4216D9DB, 0xBD95C90355555555) }; // 1.333... full precision - yield return new object[] { new UInt128(0x2FFFED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x2FFFBBBBF868FA2C, 0xFECC3359FFFFFFFF) }; // 9.999... full precision (++ carry) + yield return new object[] { new UInt128(0x2FFFED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x2FFFBBBBF868FA2C, 0xFECC3359FFFFFFFF) }; // 9.999... full precision yield return new object[] { new UInt128(0xB0000000000000B1, 0x4C1E4DF5ED261C68), new UInt128(0xB00004EE2D6D420C, 0xD1CB3D76ED261C68) }; yield return new object[] { new UInt128(0xB02C000000008117, 0x952633E9760D3996), new UInt128(0xB02C000000008117, 0x952633EBCA191D96) }; yield return new object[] { new UInt128(0xAFF80000000000BC, 0x57570583228BEDE5), new UInt128(0xAFFE314DC6448D93, 0x68F87B8E7BAEBB60) }; 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 index dc46a856151ed1..44a378b6cf64c4 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -1256,33 +1256,33 @@ public static IEnumerable op_Increment_TestData() yield return new object[] { 0x78000000U, 0x78000000U }; // +Inf yield return new object[] { 0xF8000000U, 0xF8000000U }; // -Inf yield return new object[] { 0x78000002U, 0x78000000U }; // non-canonical +Inf (canonicalizes) - yield return new object[] { 0x32800000U, 0x32800001U }; // +0 (++ -> 1, -- -> -1) - yield return new object[] { 0xB2800000U, 0x32800001U }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { 0x32800000U, 0x32800001U }; // +0 -> 1 + yield return new object[] { 0xB2800000U, 0x32800001U }; // -0 -> 1 yield return new object[] { 0x35000000U, 0x32800001U }; // 0e5 (preferred exponent min(exp,0)) yield return new object[] { 0x30000000U, 0x300186A0U }; // 0e-5 - yield return new object[] { 0x32800001U, 0x32800002U }; // 1 (++ -> 2, -- -> 0) - yield return new object[] { 0xB2800001U, 0x32800000U }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { 0x32800001U, 0x32800002U }; // 1 -> 2 + yield return new object[] { 0xB2800001U, 0x32800000U }; // -1 -> 0 yield return new object[] { 0x32800002U, 0x32800003U }; // 2 yield return new object[] { 0xB2800002U, 0xB2800001U }; // -2 yield return new object[] { 0x3280000AU, 0x3280000BU }; // 10 - yield return new object[] { 0x32000005U, 0x3200000FU }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { 0x32000005U, 0x3200000FU }; // 0.5 -> 1.5 yield return new object[] { 0xB2000005U, 0x32000005U }; // -0.5 yield return new object[] { 0x32000019U, 0x32000023U }; // 2.5 - yield return new object[] { 0x32000001U, 0x3200000BU }; // 0.1 (++ -> 1.1) + yield return new object[] { 0x32000001U, 0x3200000BU }; // 0.1 -> 1.1 yield return new object[] { 0x31800177U, 0x318001DBU }; // 3.75 yield return new object[] { 0xB1800019U, 0x3180004BU }; // -0.25 - yield return new object[] { 0x32800009U, 0x3280000AU }; // 9 (++ -> 10) - yield return new object[] { 0x6CB8967FU, 0x330F4240U }; // all-nines (++ overflows precision, rounds) + yield return new object[] { 0x32800009U, 0x3280000AU }; // 9 -> 10 + yield return new object[] { 0x6CB8967FU, 0x330F4240U }; // all-nines (overflows precision, rounds) yield return new object[] { 0x328F4240U, 0x328F4241U }; // 10^(P-1) yield return new object[] { 0x328F423FU, 0x328F4240U }; // (P-1)-nines - yield return new object[] { 0x37000001U, 0x340F4240U }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { 0x37000001U, 0x340F4240U }; // 1e(P+2) (1 below quantum, no visible change) yield return new object[] { 0x77E95440U, 0x77E95440U }; // near-max (1 negligible) yield return new object[] { 0xF7E95440U, 0xF7E95440U }; // near -max (1 negligible) - yield return new object[] { 0x02800001U, 0x2F8F4240U }; // tiny subnormal (++ ~ 1) - yield return new object[] { 0x82800001U, 0x2F8F4240U }; // tiny negative subnormal (-- ~ -1) + yield return new object[] { 0x02800001U, 0x2F8F4240U }; // tiny positive subnormal (++ ~ 1) + yield return new object[] { 0x82800001U, 0x2F8F4240U }; // tiny negative subnormal (++ ~ 1) yield return new object[] { 0x30000001U, 0x300186A1U }; // 1e-5 yield return new object[] { 0x2F945855U, 0x2FA39A95U }; // 1.333... full precision - yield return new object[] { 0x6BF8967FU, 0x3010C8E0U }; // 9.999... full precision (++ carry) + yield return new object[] { 0x6BF8967FU, 0x3010C8E0U }; // 9.999... full precision (carry) yield return new object[] { 0x2F2C8C35U, 0x2F93B6ACU }; yield return new object[] { 0x2A00002DU, 0x2F8F4240U }; yield return new object[] { 0xB8002104U, 0xEDA0F7A0U }; @@ -1360,33 +1360,33 @@ public static IEnumerable op_Decrement_TestData() yield return new object[] { 0x78000000U, 0x78000000U }; // +Inf yield return new object[] { 0xF8000000U, 0xF8000000U }; // -Inf yield return new object[] { 0x78000002U, 0x78000000U }; // non-canonical +Inf (canonicalizes) - yield return new object[] { 0x32800000U, 0xB2800001U }; // +0 (++ -> 1, -- -> -1) - yield return new object[] { 0xB2800000U, 0xB2800001U }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { 0x32800000U, 0xB2800001U }; // +0 -> -1 + yield return new object[] { 0xB2800000U, 0xB2800001U }; // -0 -> -1 yield return new object[] { 0x35000000U, 0xB2800001U }; // 0e5 (preferred exponent min(exp,0)) yield return new object[] { 0x30000000U, 0xB00186A0U }; // 0e-5 - yield return new object[] { 0x32800001U, 0x32800000U }; // 1 (++ -> 2, -- -> 0) - yield return new object[] { 0xB2800001U, 0xB2800002U }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { 0x32800001U, 0x32800000U }; // 1 -> 0 + yield return new object[] { 0xB2800001U, 0xB2800002U }; // -1 -> -2 yield return new object[] { 0x32800002U, 0x32800001U }; // 2 yield return new object[] { 0xB2800002U, 0xB2800003U }; // -2 yield return new object[] { 0x3280000AU, 0x32800009U }; // 10 - yield return new object[] { 0x32000005U, 0xB2000005U }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { 0x32000005U, 0xB2000005U }; // 0.5 -> -0.5 yield return new object[] { 0xB2000005U, 0xB200000FU }; // -0.5 yield return new object[] { 0x32000019U, 0x3200000FU }; // 2.5 - yield return new object[] { 0x32000001U, 0xB2000009U }; // 0.1 (++ -> 1.1) + yield return new object[] { 0x32000001U, 0xB2000009U }; // 0.1 -> -0.9 yield return new object[] { 0x31800177U, 0x31800113U }; // 3.75 yield return new object[] { 0xB1800019U, 0xB180007DU }; // -0.25 - yield return new object[] { 0x32800009U, 0x32800008U }; // 9 (++ -> 10) - yield return new object[] { 0x6CB8967FU, 0x6CB8967EU }; // all-nines (++ overflows precision, rounds) + yield return new object[] { 0x32800009U, 0x32800008U }; // 9 -> 8 + yield return new object[] { 0x6CB8967FU, 0x6CB8967EU }; // all-nines (decrement in last place) yield return new object[] { 0x328F4240U, 0x328F423FU }; // 10^(P-1) yield return new object[] { 0x328F423FU, 0x328F423EU }; // (P-1)-nines - yield return new object[] { 0x37000001U, 0x340F4240U }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { 0x37000001U, 0x340F4240U }; // 1e(P+2) (1 below quantum, no visible change) yield return new object[] { 0x77E95440U, 0x77E95440U }; // near-max (1 negligible) yield return new object[] { 0xF7E95440U, 0xF7E95440U }; // near -max (1 negligible) - yield return new object[] { 0x02800001U, 0xAF8F4240U }; // tiny subnormal (++ ~ 1) + yield return new object[] { 0x02800001U, 0xAF8F4240U }; // tiny positive subnormal (-- ~ -1) yield return new object[] { 0x82800001U, 0xAF8F4240U }; // tiny negative subnormal (-- ~ -1) yield return new object[] { 0x30000001U, 0xB001869FU }; // 1e-5 yield return new object[] { 0x2F945855U, 0x2F851615U }; // 1.333... full precision - yield return new object[] { 0x6BF8967FU, 0x6BE9543FU }; // 9.999... full precision (++ carry) + yield return new object[] { 0x6BF8967FU, 0x6BE9543FU }; // 9.999... full precision yield return new object[] { 0x2F2C8C35U, 0xAF6C0A4BU }; yield return new object[] { 0x2A00002DU, 0xAF8F4240U }; yield return new object[] { 0xB8002104U, 0xEDA0F7A0U }; 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 index 8e335f3959847e..fb3f844cdc56b4 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -1262,33 +1262,33 @@ public static IEnumerable op_Increment_TestData() yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL }; // +Inf yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL }; // -Inf yield return new object[] { 0x78000000_00000002UL, 0x78000000_00000000UL }; // non-canonical +Inf (canonicalizes) - yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000001UL }; // +0 (++ -> 1, -- -> -1) - yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000001UL }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000001UL }; // +0 -> 1 + yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000001UL }; // -0 -> 1 yield return new object[] { 0x32600000_00000000UL, 0x31C00000_00000001UL }; // 0e5 (preferred exponent min(exp,0)) yield return new object[] { 0x31200000_00000000UL, 0x31200000_000186A0UL }; // 0e-5 - yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL }; // 1 (++ -> 2, -- -> 0) - yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000000UL }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL }; // 1 -> 2 + yield return new object[] { 0xB1C00000_00000001UL, 0x31C00000_00000000UL }; // -1 -> 0 yield return new object[] { 0x31C00000_00000002UL, 0x31C00000_00000003UL }; // 2 yield return new object[] { 0xB1C00000_00000002UL, 0xB1C00000_00000001UL }; // -2 yield return new object[] { 0x31C00000_0000000AUL, 0x31C00000_0000000BUL }; // 10 - yield return new object[] { 0x31A00000_00000005UL, 0x31A00000_0000000FUL }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { 0x31A00000_00000005UL, 0x31A00000_0000000FUL }; // 0.5 -> 1.5 yield return new object[] { 0xB1A00000_00000005UL, 0x31A00000_00000005UL }; // -0.5 yield return new object[] { 0x31A00000_00000019UL, 0x31A00000_00000023UL }; // 2.5 - yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_0000000BUL }; // 0.1 (++ -> 1.1) + yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_0000000BUL }; // 0.1 -> 1.1 yield return new object[] { 0x31800000_00000177UL, 0x31800000_000001DBUL }; // 3.75 yield return new object[] { 0xB1800000_00000019UL, 0x31800000_0000004BUL }; // -0.25 - yield return new object[] { 0x31C00000_00000009UL, 0x31C00000_0000000AUL }; // 9 (++ -> 10) - yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x31E38D7E_A4C68000UL }; // all-nines (++ overflows precision, rounds) + yield return new object[] { 0x31C00000_00000009UL, 0x31C00000_0000000AUL }; // 9 -> 10 + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x31E38D7E_A4C68000UL }; // all-nines (overflows precision, rounds) yield return new object[] { 0x31C38D7E_A4C68000UL, 0x31C38D7E_A4C68001UL }; // 10^(P-1) yield return new object[] { 0x31C38D7E_A4C67FFFUL, 0x31C38D7E_A4C68000UL }; // (P-1)-nines - yield return new object[] { 0x34000000_00000001UL, 0x32238D7E_A4C68000UL }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { 0x34000000_00000001UL, 0x32238D7E_A4C68000UL }; // 1e(P+2) (1 below quantum, no visible change) yield return new object[] { 0x5FFFF973_CAFA8000UL, 0x5FFFF973_CAFA8000UL }; // near-max (1 negligible) yield return new object[] { 0xDFFFF973_CAFA8000UL, 0xDFFFF973_CAFA8000UL }; // near -max (1 negligible) - yield return new object[] { 0x01C00000_00000001UL, 0x2FE38D7E_A4C68000UL }; // tiny subnormal (++ ~ 1) - yield return new object[] { 0x81C00000_00000001UL, 0x2FE38D7E_A4C68000UL }; // tiny negative subnormal (-- ~ -1) + yield return new object[] { 0x01C00000_00000001UL, 0x2FE38D7E_A4C68000UL }; // tiny positive subnormal (++ ~ 1) + yield return new object[] { 0x81C00000_00000001UL, 0x2FE38D7E_A4C68000UL }; // tiny negative subnormal (++ ~ 1) yield return new object[] { 0x31200000_00000001UL, 0x31200000_000186A1UL }; // 1e-5 yield return new object[] { 0x2FE4BCA8_DBB35555UL, 0x2FE84A27_8079D555UL }; // 1.333... full precision - yield return new object[] { 0x6BFB86F2_6FC0FFFFUL, 0x3003E871_B540C000UL }; // 9.999... full precision (++ carry) + yield return new object[] { 0x6BFB86F2_6FC0FFFFUL, 0x3003E871_B540C000UL }; // 9.999... full precision (carry) yield return new object[] { 0xDE000D7B_C1739419UL, 0xDDC54457_9125D9C4UL }; yield return new object[] { 0x33A00000_00000035UL, 0x31F2D452_694F4000UL }; yield return new object[] { 0x32A00000_001AACBFUL, 0x31C00FE6_3FF64981UL }; @@ -1366,33 +1366,33 @@ public static IEnumerable op_Decrement_TestData() yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL }; // +Inf yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL }; // -Inf yield return new object[] { 0x78000000_00000002UL, 0x78000000_00000000UL }; // non-canonical +Inf (canonicalizes) - yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000001UL }; // +0 (++ -> 1, -- -> -1) - yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000001UL }; // -0 (++ -> 1, -- -> -1) + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000001UL }; // +0 -> -1 + yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000001UL }; // -0 -> -1 yield return new object[] { 0x32600000_00000000UL, 0xB1C00000_00000001UL }; // 0e5 (preferred exponent min(exp,0)) yield return new object[] { 0x31200000_00000000UL, 0xB1200000_000186A0UL }; // 0e-5 - yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000000UL }; // 1 (++ -> 2, -- -> 0) - yield return new object[] { 0xB1C00000_00000001UL, 0xB1C00000_00000002UL }; // -1 (++ -> 0, -- -> -2) + yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000000UL }; // 1 -> 0 + yield return new object[] { 0xB1C00000_00000001UL, 0xB1C00000_00000002UL }; // -1 -> -2 yield return new object[] { 0x31C00000_00000002UL, 0x31C00000_00000001UL }; // 2 yield return new object[] { 0xB1C00000_00000002UL, 0xB1C00000_00000003UL }; // -2 yield return new object[] { 0x31C00000_0000000AUL, 0x31C00000_00000009UL }; // 10 - yield return new object[] { 0x31A00000_00000005UL, 0xB1A00000_00000005UL }; // 0.5 (++ -> 1.5, -- -> -0.5) + yield return new object[] { 0x31A00000_00000005UL, 0xB1A00000_00000005UL }; // 0.5 -> -0.5 yield return new object[] { 0xB1A00000_00000005UL, 0xB1A00000_0000000FUL }; // -0.5 yield return new object[] { 0x31A00000_00000019UL, 0x31A00000_0000000FUL }; // 2.5 - yield return new object[] { 0x31A00000_00000001UL, 0xB1A00000_00000009UL }; // 0.1 (++ -> 1.1) + yield return new object[] { 0x31A00000_00000001UL, 0xB1A00000_00000009UL }; // 0.1 -> -0.9 yield return new object[] { 0x31800000_00000177UL, 0x31800000_00000113UL }; // 3.75 yield return new object[] { 0xB1800000_00000019UL, 0xB1800000_0000007DUL }; // -0.25 - yield return new object[] { 0x31C00000_00000009UL, 0x31C00000_00000008UL }; // 9 (++ -> 10) - yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFEUL }; // all-nines (++ overflows precision, rounds) + yield return new object[] { 0x31C00000_00000009UL, 0x31C00000_00000008UL }; // 9 -> 8 + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFEUL }; // all-nines (decrement in last place) yield return new object[] { 0x31C38D7E_A4C68000UL, 0x31C38D7E_A4C67FFFUL }; // 10^(P-1) yield return new object[] { 0x31C38D7E_A4C67FFFUL, 0x31C38D7E_A4C67FFEUL }; // (P-1)-nines - yield return new object[] { 0x34000000_00000001UL, 0x32238D7E_A4C68000UL }; // 1e(P+2) (1 below quantum, ++/-- no visible change) + yield return new object[] { 0x34000000_00000001UL, 0x32238D7E_A4C68000UL }; // 1e(P+2) (1 below quantum, no visible change) yield return new object[] { 0x5FFFF973_CAFA8000UL, 0x5FFFF973_CAFA8000UL }; // near-max (1 negligible) yield return new object[] { 0xDFFFF973_CAFA8000UL, 0xDFFFF973_CAFA8000UL }; // near -max (1 negligible) - yield return new object[] { 0x01C00000_00000001UL, 0xAFE38D7E_A4C68000UL }; // tiny subnormal (++ ~ 1) + yield return new object[] { 0x01C00000_00000001UL, 0xAFE38D7E_A4C68000UL }; // tiny positive subnormal (-- ~ -1) yield return new object[] { 0x81C00000_00000001UL, 0xAFE38D7E_A4C68000UL }; // tiny negative subnormal (-- ~ -1) yield return new object[] { 0x31200000_00000001UL, 0xB1200000_0001869FUL }; // 1e-5 yield return new object[] { 0x2FE4BCA8_DBB35555UL, 0x2FE12F2A_36ECD555UL }; // 1.333... full precision - yield return new object[] { 0x6BFB86F2_6FC0FFFFUL, 0x2FFFF973_CAFA7FFFUL }; // 9.999... full precision (++ carry) + yield return new object[] { 0x6BFB86F2_6FC0FFFFUL, 0x2FFFF973_CAFA7FFFUL }; // 9.999... full precision yield return new object[] { 0xDE000D7B_C1739419UL, 0xDDC54457_9125D9C4UL }; yield return new object[] { 0x33A00000_00000035UL, 0x31F2D452_694F4000UL }; yield return new object[] { 0x32A00000_001AACBFUL, 0x31C00FE6_3FF6497FUL }; From b59784addfa1a9f4e4de8644865140bf462daf7f Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Fri, 10 Jul 2026 16:25:18 -0700 Subject: [PATCH 16/19] Fix equality comment and unused parameter in decimal operator tests The op_Equality_TestData 'all-nines' case described the result with '!=' even though the table asserts '=='; correct it to match the operator under test. Also replace the ignored 'expected' parameter in Decimal128.op_UnaryPlus (which reuses UnaryNegation_TestData because UInt128 cannot be an InlineData constant) with a discard parameter, removing the redundant assignment. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../tests/System.Runtime.Tests/System/Decimal128Tests.cs | 5 ++--- .../tests/System.Runtime.Tests/System/Decimal32Tests.cs | 2 +- .../tests/System.Runtime.Tests/System/Decimal64Tests.cs | 2 +- 3 files changed, 4 insertions(+), 5 deletions(-) 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 index 4a5635cae3d1ec..8f64ae62f69d2e 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -585,9 +585,8 @@ public static void op_UnaryNegation_FiniteRoundTrips(string value) [Theory] [MemberData(nameof(UnaryNegation_TestData))] - public static void op_UnaryPlus(UInt128 value, UInt128 expected) + public static void op_UnaryPlus(UInt128 value, UInt128 _) { - _ = expected; Decimal128 d = Unsafe.BitCast(value); Assert.Equal(value, Unsafe.BitCast(+d)); } @@ -822,7 +821,7 @@ public static IEnumerable op_Equality_TestData() yield return new object[] { new UInt128(0x3040000000000000, 0x000000000000000A), new UInt128(0x3040000000000000, 0x0000000000000001), false }; // 10 == 1 -> false yield return new object[] { new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), new UInt128(0x5FFE314DC6448D93, 0x38C15B0A00000000), true }; // large == large -> true yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), true }; // all-nines == all-nines -> true - yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE), false }; // all-nines != near -> false + yield return new object[] { new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFF), new UInt128(0x3041ED09BEAD87C0, 0x378D8E63FFFFFFFE), false }; // all-nines == near -> false yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000001), true }; // 0.1 == 0.1 -> true yield return new object[] { new UInt128(0xB03E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000001), false }; // -0.1 == 0.1 -> false yield return new object[] { new UInt128(0x304A000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), true }; // +0 (exp 5) == +0 -> true (zero cohort) 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 index 44a378b6cf64c4..5d962a60960978 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -821,7 +821,7 @@ public static IEnumerable op_Equality_TestData() yield return new object[] { 0x3280000AU, 0x32800001U, false }; // 10 == 1 -> false yield return new object[] { 0x5F8F4240U, 0x5F8F4240U, true }; // large == large -> true yield return new object[] { 0x6CB8967FU, 0x6CB8967FU, true }; // all-nines == all-nines -> true - yield return new object[] { 0x6CB8967FU, 0x6CB8967EU, false }; // all-nines != near -> false + yield return new object[] { 0x6CB8967FU, 0x6CB8967EU, false }; // all-nines == near -> false yield return new object[] { 0x32000001U, 0x32000001U, true }; // 0.1 == 0.1 -> true yield return new object[] { 0xB2000001U, 0x32000001U, false }; // -0.1 == 0.1 -> false yield return new object[] { 0x35000000U, 0x32800000U, true }; // +0 (exp 5) == +0 -> true (zero cohort) 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 index fb3f844cdc56b4..35d9811ebac8ef 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -827,7 +827,7 @@ public static IEnumerable op_Equality_TestData() yield return new object[] { 0x31C00000_0000000AUL, 0x31C00000_00000001UL, false }; // 10 == 1 -> false yield return new object[] { 0x5FE38D7E_A4C68000UL, 0x5FE38D7E_A4C68000UL, true }; // large == large -> true yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFFUL, true }; // all-nines == all-nines -> true - yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFEUL, false }; // all-nines != near -> false + yield return new object[] { 0x6C7386F2_6FC0FFFFUL, 0x6C7386F2_6FC0FFFEUL, false }; // all-nines == near -> false yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_00000001UL, true }; // 0.1 == 0.1 -> true yield return new object[] { 0xB1A00000_00000001UL, 0x31A00000_00000001UL, false }; // -0.1 == 0.1 -> false yield return new object[] { 0x32600000_00000000UL, 0x31C00000_00000000UL, true }; // +0 (exp 5) == +0 -> true (zero cohort) From fc9e4b2d128f6a37d26a259fc16ec4e5d40ebd9b Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sat, 11 Jul 2026 08:22:37 -0700 Subject: [PATCH 17/19] Widen decimal wide-arithmetic helpers to native integers for the 32-bit and 64-bit formats The double-width helpers used by the decimal arithmetic operators (WideMultiply, DropDigits, WideDigitCount) carried the coefficient as a pair of TValue limbs and operated on it with half-limb decomposition and a per-digit long-division-by-ten loop. For the 32-bit and 64-bit formats the pair fits in a single wider C# integer (ulong and UInt128 respectively), so the product now comes from one native multiply and digit dropping/counting from a single native divide by 10^dropCount instead of looping. The 128-bit format has no wider native type and keeps the generic limb path. Validated bit-for-bit against the Intel Decimal Floating-Point Math Library readtest.in vectors (round-to-nearest, 2454 hex-operand cases) with no change in results, and all existing Decimal32/64/128 tests continue to pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 94 ++++++++++++++++++- 1 file changed, 90 insertions(+), 4 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index 5258b7d9b53b1e..1c7d588c871902 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.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.Text; using System.Diagnostics; using System.Numerics; @@ -974,6 +975,24 @@ private static TValue AlignmentScaleFactor(int exponent) private static void WideMultiply(TValue left, TValue right, out TValue high, out TValue low) where TValue : unmanaged, IBinaryInteger { + // For the 32-bit and 64-bit formats the full product fits in a single wider C# integer (ulong and + // UInt128 respectively), so it comes from one native multiply. The 128-bit format has no wider + // native type and uses the schoolbook half-limb decomposition below. + if (typeof(TValue) == typeof(uint)) + { + ulong product = (ulong)uint.CreateTruncating(left) * uint.CreateTruncating(right); + high = TValue.CreateTruncating(product >> 32); + low = TValue.CreateTruncating(product); + return; + } + else if (typeof(TValue) == typeof(ulong)) + { + UInt128 product = (UInt128)ulong.CreateTruncating(left) * ulong.CreateTruncating(right); + high = TValue.CreateTruncating(product >> 64); + low = TValue.CreateTruncating(product); + return; + } + int bits = TValue.Zero.GetByteCount() * 8; int half = bits / 2; TValue lowMask = (TValue.One << half) - TValue.One; @@ -999,10 +1018,12 @@ private static void WideMultiply(TValue left, TValue right, out TValue h /// returning the discarded least-significant decimal digit. Used to strip low-order digits during rounding. /// /// - /// The Intel reference implementation avoids hardware division here by multiplying with precomputed - /// reciprocals of powers of ten (e.g. bid_reciprocals10_64) and shifting. This helper instead uses - /// direct integer division for simplicity; adopting the reciprocal-multiply tables is a possible future - /// performance optimization. + /// Only the 128-bit format reaches this helper: the 32-bit and 64-bit formats widen the limb pair to a + /// single native integer and divide directly (see and + /// ). The Intel reference implementation avoids hardware + /// division here by multiplying with precomputed reciprocals of powers of ten (e.g. + /// bid_reciprocals10_64) and shifting; this helper instead uses direct integer division for + /// simplicity, and adopting the reciprocal-multiply tables is a possible future performance optimization. /// private static int WideDivideByTen(ref TValue high, ref TValue low) where TValue : unmanaged, IBinaryInteger @@ -1042,6 +1063,21 @@ private static int WideDigitCount(TValue high, TValue low) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger { + // For the 32-bit and 64-bit formats the (high, low) limb pair fits in a single wider C# integer + // (ulong and UInt128 respectively), so the digit count comes straight from the existing helpers + // instead of stripping the high limb a digit at a time. The 128-bit format has no wider native + // type and falls back to the generic limb loop below. + if (typeof(TValue) == typeof(uint)) + { + ulong wide = ((ulong)uint.CreateTruncating(high) << 32) | uint.CreateTruncating(low); + return FormattingHelpers.CountDigits(wide); + } + else if (typeof(TValue) == typeof(ulong)) + { + UInt128 wide = ((UInt128)ulong.CreateTruncating(high) << 64) | ulong.CreateTruncating(low); + return FormattingHelpers.CountDigits(wide); + } + int count = 0; while (!TValue.IsZero(high)) @@ -1064,6 +1100,56 @@ private static TValue DropDigits(ref TValue high, ref TValue low, int dr { roundDigit = 0; + if (dropCount == 0) + { + return low; + } + + // For the 32-bit and 64-bit formats the (high, low) limb pair fits in a single wider C# integer + // (ulong and UInt128 respectively), so the requested digits are dropped with one native division + // by 10^dropCount rather than a per-digit long-division loop. The remainder holds the removed + // low-order digits: its most-significant digit is the rounding digit and everything below it is + // folded into the sticky bit. The 128-bit format has no wider native type and uses the generic + // limb loop below. + if (typeof(TValue) == typeof(uint)) + { + ulong wide = ((ulong)uint.CreateTruncating(high) << 32) | uint.CreateTruncating(low); + + ulong scale = 1; + for (int i = 1; i < dropCount; i++) + { + scale *= 10; + } + ulong power = scale * 10; + + (ulong quotient, ulong remainder) = ulong.DivRem(wide, power); + roundDigit = int.CreateTruncating(remainder / scale); + sticky |= (remainder % scale) != 0; + + high = TValue.Zero; + low = TValue.CreateTruncating(quotient); + return low; + } + else if (typeof(TValue) == typeof(ulong)) + { + UInt128 wide = ((UInt128)ulong.CreateTruncating(high) << 64) | ulong.CreateTruncating(low); + + UInt128 scale = UInt128.One; + for (int i = 1; i < dropCount; i++) + { + scale *= 10; + } + UInt128 power = scale * 10; + + (UInt128 quotient, UInt128 remainder) = UInt128.DivRem(wide, power); + roundDigit = int.CreateTruncating(remainder / scale); + sticky |= (remainder % scale) != UInt128.Zero; + + high = TValue.Zero; + low = TValue.CreateTruncating(quotient); + return low; + } + for (int i = 0; i < dropCount; i++) { if (i > 0) From a4eeac34ed1299945854265f967b230dbfc4da95 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sat, 11 Jul 2026 09:27:53 -0700 Subject: [PATCH 18/19] Propagate decimal NaN payloads and match the Intel invalid-operation NaN sign Arithmetic on the decimal types now propagates a NaN operand's sign and payload per IEEE 754-2019 6.2.3 (first NaN operand wins, signaling NaNs are quieted, non-canonical payloads read as zero) via a shared PropagateNaN helper, and fresh invalid operations (Inf +/- Inf, Inf * 0, Inf / Inf, 0 / 0) now return the canonical positive quiet NaN emitted by the Intel reference rather than the negative NaN constant. Subtraction is routed through a helper that leaves a NaN operand's sign untouched so it propagates identically to addition. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 87 +++++++++++++++++-- .../src/System/Numerics/Decimal128.cs | 6 +- .../src/System/Numerics/Decimal32.cs | 4 +- .../src/System/Numerics/Decimal64.cs | 4 +- .../System/Decimal128Tests.cs | 32 +++---- .../System/Decimal32Tests.cs | 32 +++---- .../System/Decimal64Tests.cs | 32 +++---- 7 files changed, 134 insertions(+), 63 deletions(-) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs index 1c7d588c871902..1d9af3730bec1f 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -562,6 +562,41 @@ private static TValue DecimalIeee754Rounding(ref NumberBuffer return DecimalIeee754FiniteNumberBinaryEncoding(number.IsNegative, significand, exponent); } + /// + /// Produces the quiet NaN that an arithmetic operation propagates from its NaN operands, following the + /// IEEE 754-2019 §6.2.3 recommendation to preserve the payload of the first NaN operand. + /// + /// + /// The first NaN operand ( before ) supplies the sign and + /// payload of the result. A signaling NaN is quieted and a payload that is too large to be canonical + /// (greater than or equal to 10^(Precision - 1)) is discarded, matching the behavior of the Intel + /// reference implementation. + /// + private static TValue PropagateNaN(TValue left, TValue right) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `unpack_BID32`, `unpack_BID64`, and `unpack_BID128_value_BLE` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + TValue nanBits = TDecimal.IsNaN(left) ? left : right; + + // The payload occupies the trailing significand field; a value at or above the canonical + // bound is non-canonical and reads as zero. Re-encoding through the NaN combination mask + // clears the signaling bit, yielding a canonical quiet NaN that keeps the operand's sign. + TValue payload = nanBits & ((TValue.One << TDecimal.NumberBitsSignificand) - TValue.One); + + if (payload >= TDecimal.Power10(TDecimal.Precision - 1)) + { + payload = TValue.Zero; + } + + return (nanBits & TDecimal.SignMask) | TDecimal.NaNMask | payload; + } + /// /// Adds two IEEE 754 decimal values represented by their raw bit patterns and returns the /// bit pattern of the correctly rounded (round-to-nearest, ties-to-even) sum. @@ -585,7 +620,7 @@ internal static TValue AddDecimalIeee754(TValue left, TValue r if (TDecimal.IsNaN(left) || TDecimal.IsNaN(right)) { - return TDecimal.NaN; + return PropagateNaN(left, right); } if (TDecimal.IsInfinity(left)) @@ -594,7 +629,10 @@ internal static TValue AddDecimalIeee754(TValue left, TValue r // that includes at least one infinity returns that infinity (canonicalized). if (TDecimal.IsInfinity(right) && (TDecimal.IsNegative(left) != TDecimal.IsNegative(right))) { - return TDecimal.NaN; + // An invalid operation produces the canonical quiet NaN, which the Intel reference + // emits with a positive sign and empty payload (`NaNMask`), unlike the negative + // `TDecimal.NaN` constant. + return TDecimal.NaNMask; } return TDecimal.IsNegative(left) ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; } @@ -752,6 +790,32 @@ internal static TValue AddDecimalIeee754(TValue left, TValue r return NumberToDecimalIeee754BitsFromWide(resultSign, magnitudeHigh, magnitudeLow, commonExponent, sticky); } + /// + /// Subtracts one IEEE 754 decimal value from another, both represented by their raw bit patterns, and + /// returns the bit pattern of the correctly rounded (round-to-nearest, ties-to-even) difference. + /// + /// + /// Subtraction negates the right operand and defers to . The sign of a NaN + /// operand is left untouched so that a NaN result propagates the same payload and sign as addition would. + /// + internal static TValue SubtractDecimalIeee754(TValue left, TValue right) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `bid32_sub`, `bid64_sub`, and `bid128_sub` from Intel(R) Decimal Floating-Point Math Library + // Copyright (c) 2007-2025, Intel Corp. All rights reserved. + // + // Licensed under the BSD 3-Clause "New" or "Revised" License + // See THIRD-PARTY-NOTICES.TXT for the full license text + + if (!TDecimal.IsNaN(right)) + { + right ^= TDecimal.SignMask; + } + + return AddDecimalIeee754(left, right); + } + /// /// Multiplies two IEEE 754 decimal values represented by their raw bit patterns and returns the /// bit pattern of the correctly rounded (round-to-nearest, ties-to-even) product. @@ -775,7 +839,7 @@ internal static TValue MultiplyDecimalIeee754(TValue left, TVa if (TDecimal.IsNaN(left) || TDecimal.IsNaN(right)) { - return TDecimal.NaN; + return PropagateNaN(left, right); } // The sign of a product is always the exclusive-or of the operand signs, including zeros. @@ -793,7 +857,10 @@ internal static TValue MultiplyDecimalIeee754(TValue left, TVa if (otherZero) { - return TDecimal.NaN; + // An invalid operation produces the canonical quiet NaN, which the Intel reference + // emits with a positive sign and empty payload (`NaNMask`), unlike the negative + // `TDecimal.NaN` constant. + return TDecimal.NaNMask; } return resultSign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; @@ -845,7 +912,7 @@ internal static TValue DivideDecimalIeee754(TValue left, TValu if (TDecimal.IsNaN(left) || TDecimal.IsNaN(right)) { - return TDecimal.NaN; + return PropagateNaN(left, right); } // The sign of a quotient is always the exclusive-or of the operand signs, including zeros. @@ -857,7 +924,10 @@ internal static TValue DivideDecimalIeee754(TValue left, TValu // an infinity carrying the exclusive-or sign. if (TDecimal.IsInfinity(right)) { - return TDecimal.NaN; + // An invalid operation produces the canonical quiet NaN, which the Intel reference + // emits with a positive sign and empty payload (`NaNMask`), unlike the negative + // `TDecimal.NaN` constant. + return TDecimal.NaNMask; } return resultSign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; @@ -879,7 +949,10 @@ internal static TValue DivideDecimalIeee754(TValue left, TValu // carrying the exclusive-or sign (division by zero). if (TValue.IsZero(a.Significand)) { - return TDecimal.NaN; + // An invalid operation produces the canonical quiet NaN, which the Intel reference + // emits with a positive sign and empty payload (`NaNMask`), unlike the negative + // `TDecimal.NaN` constant. + return TDecimal.NaNMask; } return resultSign ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs index 9de56ff2bc50fb..363ef5e1f15158 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -272,8 +272,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The result of decrementing by one. public static Decimal128 operator --(Decimal128 value) { - UInt128 negativeOne = new UInt128(OneValue.Upper ^ SignMaskUpper, OneValue.Lower); - UInt128 result = Number.AddDecimalIeee754(new UInt128(value._upper, value._lower), negativeOne); + UInt128 result = Number.SubtractDecimalIeee754(new UInt128(value._upper, value._lower), OneValue); return new Decimal128(result); } @@ -293,8 +292,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The difference of subtracted from . public static Decimal128 operator -(Decimal128 left, Decimal128 right) { - UInt128 negatedRight = new UInt128(right._upper ^ SignMaskUpper, right._lower); - UInt128 result = Number.AddDecimalIeee754(new UInt128(left._upper, left._lower), negatedRight); + UInt128 result = Number.SubtractDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); return new Decimal128(result); } diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs index 9543f330df37f0..6209691ee7390e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -277,7 +277,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The result of decrementing by one. public static Decimal32 operator --(Decimal32 value) { - return new Decimal32(Number.AddDecimalIeee754(value._value, OneValue ^ SignMask)); + return new Decimal32(Number.SubtractDecimalIeee754(value._value, OneValue)); } /// Adds two values together to compute their sum. @@ -295,7 +295,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The difference of subtracted from . public static Decimal32 operator -(Decimal32 left, Decimal32 right) { - return new Decimal32(Number.AddDecimalIeee754(left._value, right._value ^ SignMask)); + return new Decimal32(Number.SubtractDecimalIeee754(left._value, right._value)); } /// Multiplies two values together to compute their product. diff --git a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs index 24d4a69214acc2..dba4d7a1967699 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -278,7 +278,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The result of decrementing by one. public static Decimal64 operator --(Decimal64 value) { - return new Decimal64(Number.AddDecimalIeee754(value._value, OneValue ^ SignMask)); + return new Decimal64(Number.SubtractDecimalIeee754(value._value, OneValue)); } /// Adds two values together to compute their sum. @@ -296,7 +296,7 @@ public string ToString([StringSyntax(StringSyntaxAttribute.NumericFormat)] strin /// The difference of subtracted from . public static Decimal64 operator -(Decimal64 left, Decimal64 right) { - return new Decimal64(Number.AddDecimalIeee754(left._value, right._value ^ SignMask)); + return new Decimal64(Number.SubtractDecimalIeee754(left._value, right._value)); } /// Multiplies two values together to compute their product. 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 index 8f64ae62f69d2e..4fb06d8579946c 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -596,8 +596,8 @@ public static IEnumerable op_Addition_TestData() yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // 1 + NaN -> NaN yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + 1 -> +Inf yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // 1 + +Inf -> +Inf - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf + -Inf -> NaN - yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf + +Inf -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // +Inf + -Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // -Inf + +Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf + +Inf -> +Inf yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf + -Inf -> -Inf yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 + +0 -> +0 @@ -608,7 +608,7 @@ public static IEnumerable op_Addition_TestData() yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000001) }; // 0 + 1 -> 1 yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // non-canonical +Inf + 1 -> canonical +Inf yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000005), new UInt128(0xF800000000000000, 0x0000000000000000) }; // 1 + non-canonical -Inf -> canonical -Inf - yield return new object[] { new UInt128(0x7800000000000000, 0x000000000000000F), new UInt128(0xF800000000000000, 0x0000000000000003), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // non-canonical +Inf + non-canonical -Inf -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x000000000000000F), new UInt128(0xF800000000000000, 0x0000000000000003), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // non-canonical +Inf + non-canonical -Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0x3040000000000000, 0x0000000000000003) }; // 1 + 2 -> 3 yield return new object[] { new UInt128(0x303E000000000000, 0x000000000000000F), new UInt128(0x303E000000000000, 0x0000000000000019), new UInt128(0x303E000000000000, 0x0000000000000028) }; // 1.5 + 2.5 -> 4.0 yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000001), new UInt128(0x303E000000000000, 0x0000000000000002), new UInt128(0x303E000000000000, 0x0000000000000003) }; // 0.1 + 0.2 -> 0.3 @@ -700,8 +700,8 @@ public static IEnumerable op_Subtraction_TestData() yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // 1 - +Inf -> -Inf yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf - -Inf -> +Inf yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf - +Inf -> -Inf - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf - +Inf -> NaN - yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf - -Inf -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // +Inf - +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // -Inf - -Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 - +0 -> +0 yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // -0 - -0 -> +0 (round-half-even) yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 - -0 -> +0 @@ -1009,10 +1009,10 @@ public static IEnumerable op_Multiply_TestData() yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN * 1 -> NaN yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // 1 * NaN -> NaN yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN * +Inf -> NaN - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf * +0 -> NaN - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +0 * +Inf -> NaN - yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf * +0 -> NaN - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf * -0 -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // +Inf * +0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // +0 * +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // -Inf * +0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // +Inf * -0 -> +QNaN (canonical invalid-operation result) yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf * 1 -> +Inf yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000000) }; // +Inf * -1 -> -Inf yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf * 2 -> -Inf @@ -1125,10 +1125,10 @@ public static IEnumerable op_Division_TestData() yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN / 1 -> NaN yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // 1 / NaN -> NaN yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN / +Inf -> NaN - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf / +Inf -> NaN - yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +Inf / -Inf -> NaN - yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf / +Inf -> NaN - yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -Inf / -Inf -> NaN + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // +Inf / +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // +Inf / -Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // -Inf / +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // -Inf / -Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000001), new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf / 1 -> +Inf yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000001), new UInt128(0xF800000000000000, 0x0000000000000000) }; // +Inf / -1 -> -Inf yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000002), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf / 2 -> -Inf @@ -1146,9 +1146,9 @@ public static IEnumerable op_Division_TestData() yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // -5 / +0 -> -Inf yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; // 5 / -0 -> -Inf yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000005), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; // -5 / -0 -> +Inf - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +0 / +0 -> NaN - yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // -0 / -0 -> NaN - yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; // +0 / -0 -> NaN + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // +0 / +0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // -0 / -0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // +0 / -0 -> +QNaN (canonical invalid-operation result) yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000000) }; // +0 / 5 -> +0 (ideal exp) yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0xB040000000000000, 0x0000000000000000) }; // -0 / 5 -> -0 (ideal exp, sign xor) yield return new object[] { new UInt128(0x3044000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3044000000000000, 0x0000000000000000) }; // 0e2 / 5 -> 0e2 (ideal exp) 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 index 5d962a60960978..54681ec05af3a5 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -596,8 +596,8 @@ public static IEnumerable op_Addition_TestData() yield return new object[] { 0x32800001U, 0xFC000000U, 0xFC000000U }; // 1 + NaN -> NaN yield return new object[] { 0x78000000U, 0x32800001U, 0x78000000U }; // +Inf + 1 -> +Inf yield return new object[] { 0x32800001U, 0x78000000U, 0x78000000U }; // 1 + +Inf -> +Inf - yield return new object[] { 0x78000000U, 0xF8000000U, 0xFC000000U }; // +Inf + -Inf -> NaN - yield return new object[] { 0xF8000000U, 0x78000000U, 0xFC000000U }; // -Inf + +Inf -> NaN + yield return new object[] { 0x78000000U, 0xF8000000U, 0x7C000000U }; // +Inf + -Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000U, 0x78000000U, 0x7C000000U }; // -Inf + +Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x78000000U, 0x78000000U, 0x78000000U }; // +Inf + +Inf -> +Inf yield return new object[] { 0xF8000000U, 0xF8000000U, 0xF8000000U }; // -Inf + -Inf -> -Inf yield return new object[] { 0x32800000U, 0x32800000U, 0x32800000U }; // +0 + +0 -> +0 @@ -608,7 +608,7 @@ public static IEnumerable op_Addition_TestData() yield return new object[] { 0x32800000U, 0x32800001U, 0x32800001U }; // 0 + 1 -> 1 yield return new object[] { 0x78000002U, 0x32800001U, 0x78000000U }; // non-canonical +Inf + 1 -> canonical +Inf yield return new object[] { 0x32800001U, 0xF8000005U, 0xF8000000U }; // 1 + non-canonical -Inf -> canonical -Inf - yield return new object[] { 0x7800000FU, 0xF8000003U, 0xFC000000U }; // non-canonical +Inf + non-canonical -Inf -> NaN + yield return new object[] { 0x7800000FU, 0xF8000003U, 0x7C000000U }; // non-canonical +Inf + non-canonical -Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x32800001U, 0x32800002U, 0x32800003U }; // 1 + 2 -> 3 yield return new object[] { 0x3200000FU, 0x32000019U, 0x32000028U }; // 1.5 + 2.5 -> 4.0 yield return new object[] { 0x32000001U, 0x32000002U, 0x32000003U }; // 0.1 + 0.2 -> 0.3 @@ -700,8 +700,8 @@ public static IEnumerable op_Subtraction_TestData() yield return new object[] { 0x32800001U, 0x78000000U, 0xF8000000U }; // 1 - +Inf -> -Inf yield return new object[] { 0x78000000U, 0xF8000000U, 0x78000000U }; // +Inf - -Inf -> +Inf yield return new object[] { 0xF8000000U, 0x78000000U, 0xF8000000U }; // -Inf - +Inf -> -Inf - yield return new object[] { 0x78000000U, 0x78000000U, 0xFC000000U }; // +Inf - +Inf -> NaN - yield return new object[] { 0xF8000000U, 0xF8000000U, 0xFC000000U }; // -Inf - -Inf -> NaN + yield return new object[] { 0x78000000U, 0x78000000U, 0x7C000000U }; // +Inf - +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000U, 0xF8000000U, 0x7C000000U }; // -Inf - -Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x32800000U, 0x32800000U, 0x32800000U }; // +0 - +0 -> +0 yield return new object[] { 0xB2800000U, 0xB2800000U, 0x32800000U }; // -0 - -0 -> +0 (round-half-even) yield return new object[] { 0x32800000U, 0xB2800000U, 0x32800000U }; // +0 - -0 -> +0 @@ -1009,10 +1009,10 @@ public static IEnumerable op_Multiply_TestData() yield return new object[] { 0xFC000000U, 0x32800001U, 0xFC000000U }; // NaN * 1 -> NaN yield return new object[] { 0x32800001U, 0xFC000000U, 0xFC000000U }; // 1 * NaN -> NaN yield return new object[] { 0xFC000000U, 0x78000000U, 0xFC000000U }; // NaN * +Inf -> NaN - yield return new object[] { 0x78000000U, 0x32800000U, 0xFC000000U }; // +Inf * +0 -> NaN - yield return new object[] { 0x32800000U, 0x78000000U, 0xFC000000U }; // +0 * +Inf -> NaN - yield return new object[] { 0xF8000000U, 0x32800000U, 0xFC000000U }; // -Inf * +0 -> NaN - yield return new object[] { 0x78000000U, 0xB2800000U, 0xFC000000U }; // +Inf * -0 -> NaN + yield return new object[] { 0x78000000U, 0x32800000U, 0x7C000000U }; // +Inf * +0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0x32800000U, 0x78000000U, 0x7C000000U }; // +0 * +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000U, 0x32800000U, 0x7C000000U }; // -Inf * +0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0x78000000U, 0xB2800000U, 0x7C000000U }; // +Inf * -0 -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x78000000U, 0x32800001U, 0x78000000U }; // +Inf * 1 -> +Inf yield return new object[] { 0x78000000U, 0xB2800001U, 0xF8000000U }; // +Inf * -1 -> -Inf yield return new object[] { 0xF8000000U, 0x32800002U, 0xF8000000U }; // -Inf * 2 -> -Inf @@ -1125,10 +1125,10 @@ public static IEnumerable op_Division_TestData() yield return new object[] { 0xFC000000U, 0x32800001U, 0xFC000000U }; // NaN / 1 -> NaN yield return new object[] { 0x32800001U, 0xFC000000U, 0xFC000000U }; // 1 / NaN -> NaN yield return new object[] { 0xFC000000U, 0x78000000U, 0xFC000000U }; // NaN / +Inf -> NaN - yield return new object[] { 0x78000000U, 0x78000000U, 0xFC000000U }; // +Inf / +Inf -> NaN - yield return new object[] { 0x78000000U, 0xF8000000U, 0xFC000000U }; // +Inf / -Inf -> NaN - yield return new object[] { 0xF8000000U, 0x78000000U, 0xFC000000U }; // -Inf / +Inf -> NaN - yield return new object[] { 0xF8000000U, 0xF8000000U, 0xFC000000U }; // -Inf / -Inf -> NaN + yield return new object[] { 0x78000000U, 0x78000000U, 0x7C000000U }; // +Inf / +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0x78000000U, 0xF8000000U, 0x7C000000U }; // +Inf / -Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000U, 0x78000000U, 0x7C000000U }; // -Inf / +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000U, 0xF8000000U, 0x7C000000U }; // -Inf / -Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x78000000U, 0x32800001U, 0x78000000U }; // +Inf / 1 -> +Inf yield return new object[] { 0x78000000U, 0xB2800001U, 0xF8000000U }; // +Inf / -1 -> -Inf yield return new object[] { 0xF8000000U, 0x32800002U, 0xF8000000U }; // -Inf / 2 -> -Inf @@ -1146,9 +1146,9 @@ public static IEnumerable op_Division_TestData() yield return new object[] { 0xB2800005U, 0x32800000U, 0xF8000000U }; // -5 / +0 -> -Inf yield return new object[] { 0x32800005U, 0xB2800000U, 0xF8000000U }; // 5 / -0 -> -Inf yield return new object[] { 0xB2800005U, 0xB2800000U, 0x78000000U }; // -5 / -0 -> +Inf - yield return new object[] { 0x32800000U, 0x32800000U, 0xFC000000U }; // +0 / +0 -> NaN - yield return new object[] { 0xB2800000U, 0xB2800000U, 0xFC000000U }; // -0 / -0 -> NaN - yield return new object[] { 0x32800000U, 0xB2800000U, 0xFC000000U }; // +0 / -0 -> NaN + yield return new object[] { 0x32800000U, 0x32800000U, 0x7C000000U }; // +0 / +0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xB2800000U, 0xB2800000U, 0x7C000000U }; // -0 / -0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0x32800000U, 0xB2800000U, 0x7C000000U }; // +0 / -0 -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x32800000U, 0x32800005U, 0x32800000U }; // +0 / 5 -> +0 (ideal exp) yield return new object[] { 0xB2800000U, 0x32800005U, 0xB2800000U }; // -0 / 5 -> -0 (ideal exp, sign xor) yield return new object[] { 0x33800000U, 0x32800005U, 0x33800000U }; // 0e2 / 5 -> 0e2 (ideal exp) 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 index 35d9811ebac8ef..f0169551ead09a 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -602,8 +602,8 @@ public static IEnumerable op_Addition_TestData() yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // 1 + NaN -> NaN yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // +Inf + 1 -> +Inf yield return new object[] { 0x31C00000_00000001UL, 0x78000000_00000000UL, 0x78000000_00000000UL }; // 1 + +Inf -> +Inf - yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // +Inf + -Inf -> NaN - yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // -Inf + +Inf -> NaN + yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0x7C000000_00000000UL }; // +Inf + -Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0x7C000000_00000000UL }; // -Inf + +Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0x78000000_00000000UL }; // +Inf + +Inf -> +Inf yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0xF8000000_00000000UL }; // -Inf + -Inf -> -Inf yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0x31C00000_00000000UL }; // +0 + +0 -> +0 @@ -614,7 +614,7 @@ public static IEnumerable op_Addition_TestData() yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000001UL, 0x31C00000_00000001UL }; // 0 + 1 -> 1 yield return new object[] { 0x78000000_00000002UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // non-canonical +Inf + 1 -> canonical +Inf yield return new object[] { 0x31C00000_00000001UL, 0xF8000000_00000005UL, 0xF8000000_00000000UL }; // 1 + non-canonical -Inf -> canonical -Inf - yield return new object[] { 0x78000000_0000000FUL, 0xF8000000_00000003UL, 0xFC000000_00000000UL }; // non-canonical +Inf + non-canonical -Inf -> NaN + yield return new object[] { 0x78000000_0000000FUL, 0xF8000000_00000003UL, 0x7C000000_00000000UL }; // non-canonical +Inf + non-canonical -Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x31C00000_00000001UL, 0x31C00000_00000002UL, 0x31C00000_00000003UL }; // 1 + 2 -> 3 yield return new object[] { 0x31A00000_0000000FUL, 0x31A00000_00000019UL, 0x31A00000_00000028UL }; // 1.5 + 2.5 -> 4.0 yield return new object[] { 0x31A00000_00000001UL, 0x31A00000_00000002UL, 0x31A00000_00000003UL }; // 0.1 + 0.2 -> 0.3 @@ -706,8 +706,8 @@ public static IEnumerable op_Subtraction_TestData() yield return new object[] { 0x31C00000_00000001UL, 0x78000000_00000000UL, 0xF8000000_00000000UL }; // 1 - +Inf -> -Inf yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0x78000000_00000000UL }; // +Inf - -Inf -> +Inf yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0xF8000000_00000000UL }; // -Inf - +Inf -> -Inf - yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // +Inf - +Inf -> NaN - yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // -Inf - -Inf -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0x7C000000_00000000UL }; // +Inf - +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0x7C000000_00000000UL }; // -Inf - -Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0x31C00000_00000000UL }; // +0 - +0 -> +0 yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // -0 - -0 -> +0 (round-half-even) yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, 0x31C00000_00000000UL }; // +0 - -0 -> +0 @@ -1015,10 +1015,10 @@ public static IEnumerable op_Multiply_TestData() yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, 0xFC000000_00000000UL }; // NaN * 1 -> NaN yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // 1 * NaN -> NaN yield return new object[] { 0xFC000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // NaN * +Inf -> NaN - yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000000UL, 0xFC000000_00000000UL }; // +Inf * +0 -> NaN - yield return new object[] { 0x31C00000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // +0 * +Inf -> NaN - yield return new object[] { 0xF8000000_00000000UL, 0x31C00000_00000000UL, 0xFC000000_00000000UL }; // -Inf * +0 -> NaN - yield return new object[] { 0x78000000_00000000UL, 0xB1C00000_00000000UL, 0xFC000000_00000000UL }; // +Inf * -0 -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000000UL, 0x7C000000_00000000UL }; // +Inf * +0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0x31C00000_00000000UL, 0x78000000_00000000UL, 0x7C000000_00000000UL }; // +0 * +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000_00000000UL, 0x31C00000_00000000UL, 0x7C000000_00000000UL }; // -Inf * +0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0x78000000_00000000UL, 0xB1C00000_00000000UL, 0x7C000000_00000000UL }; // +Inf * -0 -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // +Inf * 1 -> +Inf yield return new object[] { 0x78000000_00000000UL, 0xB1C00000_00000001UL, 0xF8000000_00000000UL }; // +Inf * -1 -> -Inf yield return new object[] { 0xF8000000_00000000UL, 0x31C00000_00000002UL, 0xF8000000_00000000UL }; // -Inf * 2 -> -Inf @@ -1131,10 +1131,10 @@ public static IEnumerable op_Division_TestData() yield return new object[] { 0xFC000000_00000000UL, 0x31C00000_00000001UL, 0xFC000000_00000000UL }; // NaN / 1 -> NaN yield return new object[] { 0x31C00000_00000001UL, 0xFC000000_00000000UL, 0xFC000000_00000000UL }; // 1 / NaN -> NaN yield return new object[] { 0xFC000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // NaN / +Inf -> NaN - yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // +Inf / +Inf -> NaN - yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // +Inf / -Inf -> NaN - yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0xFC000000_00000000UL }; // -Inf / +Inf -> NaN - yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0xFC000000_00000000UL }; // -Inf / -Inf -> NaN + yield return new object[] { 0x78000000_00000000UL, 0x78000000_00000000UL, 0x7C000000_00000000UL }; // +Inf / +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0x78000000_00000000UL, 0xF8000000_00000000UL, 0x7C000000_00000000UL }; // +Inf / -Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000_00000000UL, 0x78000000_00000000UL, 0x7C000000_00000000UL }; // -Inf / +Inf -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xF8000000_00000000UL, 0xF8000000_00000000UL, 0x7C000000_00000000UL }; // -Inf / -Inf -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x78000000_00000000UL, 0x31C00000_00000001UL, 0x78000000_00000000UL }; // +Inf / 1 -> +Inf yield return new object[] { 0x78000000_00000000UL, 0xB1C00000_00000001UL, 0xF8000000_00000000UL }; // +Inf / -1 -> -Inf yield return new object[] { 0xF8000000_00000000UL, 0x31C00000_00000002UL, 0xF8000000_00000000UL }; // -Inf / 2 -> -Inf @@ -1152,9 +1152,9 @@ public static IEnumerable op_Division_TestData() yield return new object[] { 0xB1C00000_00000005UL, 0x31C00000_00000000UL, 0xF8000000_00000000UL }; // -5 / +0 -> -Inf yield return new object[] { 0x31C00000_00000005UL, 0xB1C00000_00000000UL, 0xF8000000_00000000UL }; // 5 / -0 -> -Inf yield return new object[] { 0xB1C00000_00000005UL, 0xB1C00000_00000000UL, 0x78000000_00000000UL }; // -5 / -0 -> +Inf - yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0xFC000000_00000000UL }; // +0 / +0 -> NaN - yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000000UL, 0xFC000000_00000000UL }; // -0 / -0 -> NaN - yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, 0xFC000000_00000000UL }; // +0 / -0 -> NaN + yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000000UL, 0x7C000000_00000000UL }; // +0 / +0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0xB1C00000_00000000UL, 0xB1C00000_00000000UL, 0x7C000000_00000000UL }; // -0 / -0 -> +QNaN (canonical invalid-operation result) + yield return new object[] { 0x31C00000_00000000UL, 0xB1C00000_00000000UL, 0x7C000000_00000000UL }; // +0 / -0 -> +QNaN (canonical invalid-operation result) yield return new object[] { 0x31C00000_00000000UL, 0x31C00000_00000005UL, 0x31C00000_00000000UL }; // +0 / 5 -> +0 (ideal exp) yield return new object[] { 0xB1C00000_00000000UL, 0x31C00000_00000005UL, 0xB1C00000_00000000UL }; // -0 / 5 -> -0 (ideal exp, sign xor) yield return new object[] { 0x32000000_00000000UL, 0x31C00000_00000005UL, 0x32000000_00000000UL }; // 0e2 / 5 -> 0e2 (ideal exp) From 12e7997f58b20d524f5ab93b64b91ea3454e7f02 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Sat, 11 Jul 2026 10:07:03 -0700 Subject: [PATCH 19/19] Cross-validate decimal operators against the Intel reference vectors Add an opt-in test data source that streams the operator vectors out of the readtest.in file shipped with the Intel(R) Decimal Floating-Point Math Library and validates the ported Decimal32/64/128 arithmetic and comparison operators bit-for-bit against them. The file is large and not redistributed, so the six theories are gated on readtest.in being present next to the test assembly (AppContext.BaseDirectory) and skip otherwise. An interested developer copies the file into the output directory to opt in, keeping the exhaustive validation available for local and outer-loop runs without redistributing the file or adding cost to every test pass. Gating on a deliberately dropped-in file rather than an environment variable naming an arbitrary path avoids reading from a location an unrelated process could influence and is no worse than shipping the file. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System.Runtime.Tests.csproj | 1 + .../System/Decimal128Tests.cs | 40 ++++ .../System/Decimal32Tests.cs | 40 ++++ .../System/Decimal64Tests.cs | 40 ++++ .../System/DecimalIeee754IntelTestData.cs | 221 ++++++++++++++++++ 5 files changed, 342 insertions(+) create mode 100644 src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs 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 d527ddcca62fce..a1ac2dff4a8e11 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 @@ -85,6 +85,7 @@ + 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 index 4fb06d8579946c..6e41ab22f78caf 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs @@ -1528,5 +1528,45 @@ public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, Nu Assert.False(Decimal128.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out int bytesConsumed)); Assert.Equal(0, bytesConsumed); } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128Arithmetic), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void op_Arithmetic_IntelReferenceVectors(string operation, UInt128 left, UInt128 right, UInt128 expected) + { + Decimal128 l = Unsafe.BitCast(left); + Decimal128 r = Unsafe.BitCast(right); + + Decimal128 result = operation switch + { + "add" => l + r, + "sub" => l - r, + "mul" => l * r, + "div" => l / r, + _ => throw new InvalidOperationException($"Unexpected operation '{operation}'."), + }; + + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128Comparison), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void op_Comparison_IntelReferenceVectors(string operation, UInt128 left, UInt128 right, bool expected) + { + Decimal128 l = Unsafe.BitCast(left); + Decimal128 r = Unsafe.BitCast(right); + + bool result = operation switch + { + "quiet_equal" => l == r, + "quiet_not_equal" => l != r, + "quiet_less" => l < r, + "quiet_greater" => l > r, + "quiet_less_equal" => l <= r, + "quiet_greater_equal" => l >= r, + _ => throw new InvalidOperationException($"Unexpected operation '{operation}'."), + }; + + Assert.Equal(expected, result); + } } } 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 index 54681ec05af3a5..c5cac32caa6af8 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs @@ -1528,5 +1528,45 @@ public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, Nu Assert.False(Decimal32.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out int bytesConsumed)); Assert.Equal(0, bytesConsumed); } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32Arithmetic), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void op_Arithmetic_IntelReferenceVectors(string operation, uint left, uint right, uint expected) + { + Decimal32 l = Unsafe.BitCast(left); + Decimal32 r = Unsafe.BitCast(right); + + Decimal32 result = operation switch + { + "add" => l + r, + "sub" => l - r, + "mul" => l * r, + "div" => l / r, + _ => throw new InvalidOperationException($"Unexpected operation '{operation}'."), + }; + + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32Comparison), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void op_Comparison_IntelReferenceVectors(string operation, uint left, uint right, bool expected) + { + Decimal32 l = Unsafe.BitCast(left); + Decimal32 r = Unsafe.BitCast(right); + + bool result = operation switch + { + "quiet_equal" => l == r, + "quiet_not_equal" => l != r, + "quiet_less" => l < r, + "quiet_greater" => l > r, + "quiet_less_equal" => l <= r, + "quiet_greater_equal" => l >= r, + _ => throw new InvalidOperationException($"Unexpected operation '{operation}'."), + }; + + Assert.Equal(expected, result); + } } } 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 index f0169551ead09a..c000eca6134046 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs @@ -1534,5 +1534,45 @@ public static void Parse_AllowTrailingInvalidCharacters_Invalid(string value, Nu Assert.False(Decimal64.TryParse(utf8Bytes.AsSpan(), style, provider, out result, out int bytesConsumed)); Assert.Equal(0, bytesConsumed); } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64Arithmetic), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void op_Arithmetic_IntelReferenceVectors(string operation, ulong left, ulong right, ulong expected) + { + Decimal64 l = Unsafe.BitCast(left); + Decimal64 r = Unsafe.BitCast(right); + + Decimal64 result = operation switch + { + "add" => l + r, + "sub" => l - r, + "mul" => l * r, + "div" => l / r, + _ => throw new InvalidOperationException($"Unexpected operation '{operation}'."), + }; + + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64Comparison), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void op_Comparison_IntelReferenceVectors(string operation, ulong left, ulong right, bool expected) + { + Decimal64 l = Unsafe.BitCast(left); + Decimal64 r = Unsafe.BitCast(right); + + bool result = operation switch + { + "quiet_equal" => l == r, + "quiet_not_equal" => l != r, + "quiet_less" => l < r, + "quiet_greater" => l > r, + "quiet_less_equal" => l <= r, + "quiet_greater_equal" => l >= r, + _ => throw new InvalidOperationException($"Unexpected operation '{operation}'."), + }; + + Assert.Equal(expected, result); + } } } diff --git a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs new file mode 100644 index 00000000000000..ef9994795e9bdb --- /dev/null +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs @@ -0,0 +1,221 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.Collections.Generic; +using System.Globalization; +using System.IO; + +namespace System.Tests +{ + /// + /// Streams operator test vectors out of the readtest.in file that ships with the Intel(R) + /// Decimal Floating-Point Math Library so the ported , + /// , and operators can be + /// cross-validated bit-for-bit against the reference implementation. + /// + /// + /// The file is very large (tens of thousands of vectors spanning hundreds of operations) and is not + /// redistributed with the runtime, so the theories that consume this data are gated on the + /// condition and skip unless an interested developer copies readtest.in + /// next to the test assembly (that is, into ). This keeps the + /// exhaustive validation available for local or outer-loop runs without redistributing the file or + /// paying its cost on every test pass. Gating on a file the developer deliberately drops into the + /// output directory (rather than an environment variable naming an arbitrary path) avoids reading a + /// file from a location an unrelated process could influence and is no worse than shipping it. + /// + /// Each line is whitespace separated as op rnd a b result flags. Only round-to-nearest-even + /// (rnd == 0) rows whose operands are bracketed hex bit patterns are consumed; rows using + /// decimal-string or named operands (for example QNaN or Infinity), other rounding + /// modes, and the trailing IEEE exception-flags column are ignored. Bid128 operands appear both as + /// [hi,lo] (arithmetic) and [32hex] (comparison); both encodings are handled. + /// + public static class DecimalIeee754IntelTestData + { + private const string ReadTestFileName = "readtest.in"; + + private static readonly string? s_readTestPath = ResolveReadTestPath(); + + private static readonly HashSet s_bid32Arithmetic = new() { "bid32_add", "bid32_sub", "bid32_mul", "bid32_div" }; + private static readonly HashSet s_bid64Arithmetic = new() { "bid64_add", "bid64_sub", "bid64_mul", "bid64_div" }; + private static readonly HashSet s_bid128Arithmetic = new() { "bid128_add", "bid128_sub", "bid128_mul", "bid128_div" }; + + private static readonly HashSet s_bid32Comparison = new() { "bid32_quiet_equal", "bid32_quiet_not_equal", "bid32_quiet_less", "bid32_quiet_greater", "bid32_quiet_less_equal", "bid32_quiet_greater_equal" }; + private static readonly HashSet s_bid64Comparison = new() { "bid64_quiet_equal", "bid64_quiet_not_equal", "bid64_quiet_less", "bid64_quiet_greater", "bid64_quiet_less_equal", "bid64_quiet_greater_equal" }; + private static readonly HashSet s_bid128Comparison = new() { "bid128_quiet_equal", "bid128_quiet_not_equal", "bid128_quiet_less", "bid128_quiet_greater", "bid128_quiet_less_equal", "bid128_quiet_greater_equal" }; + + /// + /// Gets a value indicating whether the Intel readtest.in reference vectors are available, + /// gating the theories that consume them. + /// + public static bool IsAvailable => s_readTestPath is not null; + + public static IEnumerable Decimal32Arithmetic() + { + foreach (string[] fields in EnumerateRows(s_bid32Arithmetic)) + { + if (TryParseBid32(fields[2], out uint left) && TryParseBid32(fields[3], out uint right) && TryParseBid32(fields[4], out uint expected)) + { + yield return new object[] { OperationSuffix(fields[0]), left, right, expected }; + } + } + } + + public static IEnumerable Decimal64Arithmetic() + { + foreach (string[] fields in EnumerateRows(s_bid64Arithmetic)) + { + if (TryParseBid64(fields[2], out ulong left) && TryParseBid64(fields[3], out ulong right) && TryParseBid64(fields[4], out ulong expected)) + { + yield return new object[] { OperationSuffix(fields[0]), left, right, expected }; + } + } + } + + public static IEnumerable Decimal128Arithmetic() + { + foreach (string[] fields in EnumerateRows(s_bid128Arithmetic)) + { + if (TryParseBid128(fields[2], out UInt128 left) && TryParseBid128(fields[3], out UInt128 right) && TryParseBid128(fields[4], out UInt128 expected)) + { + yield return new object[] { OperationSuffix(fields[0]), left, right, expected }; + } + } + } + + public static IEnumerable Decimal32Comparison() + { + foreach (string[] fields in EnumerateRows(s_bid32Comparison)) + { + if (TryParseBid32(fields[2], out uint left) && TryParseBid32(fields[3], out uint right) && TryParseComparisonResult(fields[4], out bool expected)) + { + yield return new object[] { OperationSuffix(fields[0]), left, right, expected }; + } + } + } + + public static IEnumerable Decimal64Comparison() + { + foreach (string[] fields in EnumerateRows(s_bid64Comparison)) + { + if (TryParseBid64(fields[2], out ulong left) && TryParseBid64(fields[3], out ulong right) && TryParseComparisonResult(fields[4], out bool expected)) + { + yield return new object[] { OperationSuffix(fields[0]), left, right, expected }; + } + } + } + + public static IEnumerable Decimal128Comparison() + { + foreach (string[] fields in EnumerateRows(s_bid128Comparison)) + { + if (TryParseBid128(fields[2], out UInt128 left) && TryParseBid128(fields[3], out UInt128 right) && TryParseComparisonResult(fields[4], out bool expected)) + { + yield return new object[] { OperationSuffix(fields[0]), left, right, expected }; + } + } + } + + private static IEnumerable EnumerateRows(HashSet operations) + { + string? path = s_readTestPath; + + if (path is null) + { + yield break; + } + + foreach (string line in File.ReadLines(path)) + { + string[] fields = line.Split((char[]?)null, StringSplitOptions.RemoveEmptyEntries); + + // A usable row is `op 0 a b result flags`; anything shorter, in a different rounding mode, + // or naming an operation we do not care about is skipped. + if ((fields.Length >= 5) && (fields[1] == "0") && operations.Contains(fields[0])) + { + yield return fields; + } + } + } + + private static string OperationSuffix(string operation) => operation.Substring(operation.IndexOf('_') + 1); + + private static bool TryParseComparisonResult(string token, out bool value) + { + switch (token) + { + case "0": + value = false; + return true; + + case "1": + value = true; + return true; + + default: + value = false; + return false; + } + } + + private static bool TryParseBid32(string token, out uint value) + { + value = 0; + + if ((token.Length < 2) || (token[0] != '[') || (token[^1] != ']')) + { + return false; + } + + // Operands are not always zero-padded to the full width, so parse the inner hex directly and + // let uint.TryParse reject anything wider than the type. + return uint.TryParse(token.AsSpan(1, token.Length - 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value); + } + + private static bool TryParseBid64(string token, out ulong value) + { + value = 0; + + if ((token.Length < 2) || (token[0] != '[') || (token[^1] != ']')) + { + return false; + } + + return ulong.TryParse(token.AsSpan(1, token.Length - 2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value); + } + + private static bool TryParseBid128(string token, out UInt128 value) + { + value = default; + + if ((token.Length < 2) || (token[0] != '[') || (token[^1] != ']')) + { + return false; + } + + ReadOnlySpan inner = token.AsSpan(1, token.Length - 2); + int comma = inner.IndexOf(','); + + if (comma >= 0) + { + // Bid128 arithmetic operands are encoded as `[hi,lo]`. + if (!ulong.TryParse(inner.Slice(0, comma), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out ulong upper) || + !ulong.TryParse(inner.Slice(comma + 1), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out ulong lower)) + { + return false; + } + + value = new UInt128(upper, lower); + return true; + } + + // Bid128 comparison operands and every bid128 result are encoded as a single `[hex]`. + return UInt128.TryParse(inner, NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture, out value); + } + + private static string? ResolveReadTestPath() + { + string path = Path.Combine(AppContext.BaseDirectory, ReadTestFileName); + return File.Exists(path) ? path : null; + } + } +}