From 40a369e49bbf6f22792af48266fedcc3652beb64 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 00:35:31 -0700 Subject: [PATCH 01/14] Implement IFloatingPointConstants for the IEEE 754 decimal types E, Pi, and Tau already exist as public constants; this just declares the interface on Decimal32/64/128. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/Decimal128.cs | 1 + .../System.Private.CoreLib/src/System/Numerics/Decimal32.cs | 1 + .../System.Private.CoreLib/src/System/Numerics/Decimal64.cs | 1 + src/libraries/System.Runtime/ref/System.Runtime.cs | 6 +++--- 4 files changed, 6 insertions(+), 3 deletions(-) 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 ecb10988e1c726..eb1a6d570de18b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -12,6 +12,7 @@ public readonly struct Decimal128 : IComparable, IComparable, IEquatable, + IFloatingPointConstants, INumberBase, ISpanFormattable, ISpanParsable, 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 9b4d66fc367434..115c64cf1b37ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -14,6 +14,7 @@ public readonly struct Decimal32 : IComparable, IComparable, IEquatable, + IFloatingPointConstants, INumberBase, ISpanFormattable, ISpanParsable, 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 8cb058bdb819ac..9cf282839a2cb5 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -12,6 +12,7 @@ public readonly struct Decimal64 : IComparable, IComparable, IEquatable, + IFloatingPointConstants, INumberBase, ISpanFormattable, ISpanParsable, diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 7cee29cf852df5..dc293db679447f 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11429,7 +11429,7 @@ public static void HtmlEncode(string? value, System.IO.TextWriter output) { } } namespace System.Numerics { - public readonly partial struct Decimal128 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal128 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal128 E { get { throw null; } } @@ -11601,7 +11601,7 @@ namespace System.Numerics public static bool TryParsePartial([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal128 result, out int charsConsumed) { throw null; } } - public readonly partial struct Decimal32 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal32 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal32 E { get { throw null; } } @@ -11777,7 +11777,7 @@ namespace System.Numerics public static bool TryParsePartial([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal32 result, out int charsConsumed) { throw null; } } - public readonly partial struct Decimal64 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal64 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal64 E { get { throw null; } } From 154954640a129bffa9e4bbfc2d4a2a648f3113bd Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 00:37:50 -0700 Subject: [PATCH 02/14] Implement ISignedNumber for the IEEE 754 decimal types NegativeOne already exists as a public constant; this just declares the interface on Decimal32/64/128. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/Decimal128.cs | 1 + .../System.Private.CoreLib/src/System/Numerics/Decimal32.cs | 1 + .../System.Private.CoreLib/src/System/Numerics/Decimal64.cs | 1 + src/libraries/System.Runtime/ref/System.Runtime.cs | 6 +++--- 4 files changed, 6 insertions(+), 3 deletions(-) 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 eb1a6d570de18b..7e3a0ddb78e566 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -14,6 +14,7 @@ public readonly struct Decimal128 IEquatable, IFloatingPointConstants, INumberBase, + ISignedNumber, ISpanFormattable, ISpanParsable, IMinMaxValue, 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 115c64cf1b37ff..d579c918341ed0 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -16,6 +16,7 @@ public readonly struct Decimal32 IEquatable, IFloatingPointConstants, INumberBase, + ISignedNumber, ISpanFormattable, ISpanParsable, IMinMaxValue, 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 9cf282839a2cb5..0c72bb031bb2ea 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -14,6 +14,7 @@ public readonly struct Decimal64 IEquatable, IFloatingPointConstants, INumberBase, + ISignedNumber, ISpanFormattable, ISpanParsable, IMinMaxValue, diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index dc293db679447f..9a716b213ad46f 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11429,7 +11429,7 @@ public static void HtmlEncode(string? value, System.IO.TextWriter output) { } } namespace System.Numerics { - public readonly partial struct Decimal128 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal128 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal128 E { get { throw null; } } @@ -11601,7 +11601,7 @@ namespace System.Numerics public static bool TryParsePartial([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal128 result, out int charsConsumed) { throw null; } } - public readonly partial struct Decimal32 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal32 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal32 E { get { throw null; } } @@ -11777,7 +11777,7 @@ namespace System.Numerics public static bool TryParsePartial([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal32 result, out int charsConsumed) { throw null; } } - public readonly partial struct Decimal64 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal64 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal64 E { get { throw null; } } From 09143bca15a4f65e2e6228d8b5b945f268d27ff1 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 00:48:34 -0700 Subject: [PATCH 03/14] Add the modulus operator for the IEEE 754 decimal types Implements the truncated remainder (x - Truncate(x / y) * y) matching the C# floating-point % operator on double/float/Half, not the round-to-nearest IEEE 754 remainder. The result carries the dividend's sign and is computed exactly at the preferred exponent min(exp(x), exp(y)) by reducing the dividend coefficient modulo the divisor coefficient with word-level integer arithmetic. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 100 ++++++++++++++++++ .../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 + 5 files changed, 131 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 465052fd4a9eb5..6868d82e6e2a25 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -1015,6 +1015,106 @@ internal static TValue DivideDecimalIeee754(TValue left, TValu return NumberToDecimalIeee754BitsFromWide(resultSign, TValue.Zero, quotient, quotientExponent, remainderNonZero); } + /// + /// Computes the truncated remainder (the % operator) of two IEEE 754 decimal values represented by their + /// raw bit patterns: x - Truncate(x / y) * y, matching the C# floating-point % operator on + /// // (and not the round-to-nearest + /// IEEE 754 remainder operation). + /// + /// + /// The result carries the sign of the dividend, has magnitude strictly less than |y|, and is always + /// exact. It is computed at the IEEE 754 preferred exponent min(exp(x), exp(y)) by reducing the + /// dividend coefficient modulo the divisor coefficient. Every intermediate value stays within a single limb: + /// the running remainder is always below the divisor, so scaling it up one decimal digit at a time and taking + /// the remainder never overflows. This mirrors the special-case handling of the Intel reference implementation. + /// + internal static TValue RemainderDecimalIeee754(TValue left, TValue right) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + // This code is based on `bid32_fmod`, `bid64_fmod`, and `bid128_fmod` 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 PropagateNaN(left, right); + } + + // The remainder always carries the sign of the dividend. + bool resultSign = TDecimal.IsNegative(left); + + if (TDecimal.IsInfinity(left)) + { + // Infinity has no finite remainder; the operation is invalid and produces the canonical quiet NaN. + return TDecimal.NaNMask; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(left); + + if (TDecimal.IsInfinity(right)) + { + // A finite value has itself as its remainder modulo infinity; re-encode to a canonical form. + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, a.Significand, a.UnbiasedExponent); + } + + DecodedDecimalIeee754 b = UnpackDecimalIeee754(right); + + if (TValue.IsZero(b.Significand)) + { + // A remainder with a zero divisor is invalid and produces the canonical quiet NaN. + return TDecimal.NaNMask; + } + + // The preferred exponent of the remainder is the smaller of the two operand exponents. + int resultExponent = Math.Min(a.UnbiasedExponent, b.UnbiasedExponent); + + if (TValue.IsZero(a.Significand)) + { + // Zero modulo any non-zero value is a signed zero at the preferred exponent. + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, TValue.Zero, resultExponent); + } + + TValue ten = TValue.CreateTruncating(10); + TValue remainder; + + if (a.UnbiasedExponent >= b.UnbiasedExponent) + { + // Both coefficients fit a single limb, so reduce `a.Significand * 10^(ea - eb)` modulo `b.Significand` + // by scaling the running remainder up one decimal digit at a time. The remainder stays below the + // divisor, so `remainder * 10` never exceeds the integer width. + // + // TODO: This scales one decimal digit per iteration, where-as Intel's `bidNN_fmod` scales the dividend + // by up to `Precision` digits per iteration. Porting that chunked reduction would cut the loop count + // for large exponent gaps at the cost of a wider intermediate for Decimal128. + remainder = a.Significand % b.Significand; + + for (int i = 0; i < a.UnbiasedExponent - b.UnbiasedExponent; i++) + { + remainder = (remainder * ten) % b.Significand; + } + } + else + { + // The divisor's coefficient is scaled up by `10^(eb - ea)`. When that scaled divisor cannot fit the + // format precision it necessarily exceeds the dividend coefficient, so the dividend is the remainder. + int gap = b.UnbiasedExponent - a.UnbiasedExponent; + + if (TDecimal.CountDigits(b.Significand) + gap > TDecimal.Precision) + { + remainder = a.Significand; + } + else + { + remainder = a.Significand % (b.Significand * TDecimal.Power10(gap)); + } + } + + return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, remainder, resultExponent); + } + /// /// 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, 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 7e3a0ddb78e566..4ceb5caf92b2a9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -340,6 +340,16 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS return new Decimal128(result); } + /// Divides two values together to compute their remainder. + /// The value which divides. + /// The value which divides . + /// The remainder of divided by . + public static Decimal128 operator %(Decimal128 left, Decimal128 right) + { + UInt128 result = Number.RemainderDecimalIeee754(new UInt128(left._upper, left._lower), new UInt128(right._upper, right._lower)); + return new Decimal128(result); + } + // // Explicit conversions to Decimal128 // 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 d579c918341ed0..bb0eb00d3b02b9 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -340,6 +340,15 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS return new Decimal32(Number.DivideDecimalIeee754(left._value, right._value)); } + /// Divides two values together to compute their remainder. + /// The value which divides. + /// The value which divides . + /// The remainder of divided by . + public static Decimal32 operator %(Decimal32 left, Decimal32 right) + { + return new Decimal32(Number.RemainderDecimalIeee754(left._value, right._value)); + } + // // Explicit conversions to Decimal32 // 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 0c72bb031bb2ea..25673b0036f12c 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -341,6 +341,15 @@ public bool TryFormat(Span utf8Destination, out int bytesWritten, [StringS return new Decimal64(Number.DivideDecimalIeee754(left._value, right._value)); } + /// Divides two values together to compute their remainder. + /// The value which divides. + /// The value which divides . + /// The remainder of divided by . + public static Decimal64 operator %(Decimal64 left, Decimal64 right) + { + return new Decimal64(Number.RemainderDecimalIeee754(left._value, right._value)); + } + // // Explicit conversions to Decimal64 // diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 9a716b213ad46f..1693e8919215a5 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11559,6 +11559,7 @@ namespace System.Numerics 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 left, System.Numerics.Decimal128 right) { throw null; } public static System.Numerics.Decimal128 operator -(System.Numerics.Decimal128 value) { throw null; } @@ -11735,6 +11736,7 @@ namespace System.Numerics 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 left, System.Numerics.Decimal32 right) { throw null; } public static System.Numerics.Decimal32 operator -(System.Numerics.Decimal32 value) { throw null; } @@ -11909,6 +11911,7 @@ namespace System.Numerics 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 left, System.Numerics.Decimal64 right) { throw null; } public static System.Numerics.Decimal64 operator -(System.Numerics.Decimal64 value) { throw null; } From b4343a00679be3d0864db0cd9fe32858d6b9e945 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 00:52:10 -0700 Subject: [PATCH 04/14] Implement INumber for the IEEE 754 decimal types All INumber members (Clamp/Max/Min/Sign/CopySign and friends) already exist as public static members; the modulus operator and relational comparison operators satisfy the remaining IModulusOperators/IComparisonOperators requirements, so this declares the interface on Decimal32/64/128. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Numerics/Decimal128.cs | 1 + .../System.Private.CoreLib/src/System/Numerics/Decimal32.cs | 1 + .../System.Private.CoreLib/src/System/Numerics/Decimal64.cs | 1 + src/libraries/System.Runtime/ref/System.Runtime.cs | 6 +++--- 4 files changed, 6 insertions(+), 3 deletions(-) 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 4ceb5caf92b2a9..725b213679db51 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs @@ -13,6 +13,7 @@ public readonly struct Decimal128 IComparable, IEquatable, IFloatingPointConstants, + INumber, INumberBase, ISignedNumber, ISpanFormattable, 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 bb0eb00d3b02b9..82ff8909bfc33a 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs @@ -15,6 +15,7 @@ public readonly struct Decimal32 IComparable, IEquatable, IFloatingPointConstants, + INumber, INumberBase, ISignedNumber, ISpanFormattable, 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 25673b0036f12c..a9d5dea85c7371 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs @@ -13,6 +13,7 @@ public readonly struct Decimal64 IComparable, IEquatable, IFloatingPointConstants, + INumber, INumberBase, ISignedNumber, ISpanFormattable, diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index 1693e8919215a5..a2b5b08c8d01f8 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11429,7 +11429,7 @@ public static void HtmlEncode(string? value, System.IO.TextWriter output) { } } namespace System.Numerics { - public readonly partial struct Decimal128 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal128 : 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.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal128 E { get { throw null; } } @@ -11602,7 +11602,7 @@ namespace System.Numerics public static bool TryParsePartial([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal128 result, out int charsConsumed) { throw null; } } - public readonly partial struct Decimal32 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal32 : 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.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal32 E { get { throw null; } } @@ -11779,7 +11779,7 @@ namespace System.Numerics public static bool TryParsePartial([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal32 result, out int charsConsumed) { throw null; } } - public readonly partial struct Decimal64 : 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.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal64 : 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.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal64 E { get { throw null; } } From 36c2087aaf8880205044837c0b6f7910f8c65725 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 01:20:30 -0700 Subject: [PATCH 05/14] Implement IFloatingPoint for the IEEE 754 decimal types Collapses the IFloatingPointConstants/INumber/INumberBase/ISignedNumber declarations into IFloatingPoint on Decimal32/64/128 and adds the member set: Ceiling/Floor/Truncate/Round, ConvertToInteger(Native), and the IFloatingPoint exponent/significand writers. Exponent is exposed as int (its UnbiasedExponent type) so NaN/Inf exponent fields don't overflow a narrower type; the significand is the full unpacked coefficient. Round shares Number.RoundDecimalIeee754. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 100 +++++++++++++++ .../src/System/Numerics/Decimal128.cs | 115 +++++++++++++++++- .../src/System/Numerics/Decimal32.cs | 115 +++++++++++++++++- .../src/System/Numerics/Decimal64.cs | 115 +++++++++++++++++- .../System.Runtime/ref/System.Runtime.cs | 57 ++++++++- 5 files changed, 487 insertions(+), 15 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 6868d82e6e2a25..fc5077c940f95d 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -1115,6 +1115,106 @@ internal static TValue RemainderDecimalIeee754(TValue left, TV return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, remainder, resultExponent); } + /// + /// Rounds a finite value to fractional digits under . + /// The value is coefficient * 10^exponent; when its quantum exponent is already at or above + /// -digits there is nothing to round and it is returned unchanged. Otherwise the digits below + /// 10^(-digits) are discarded and the retained coefficient is incremented per . + /// Every intermediate fits a single limb: the divisor is at most 10^(Precision - 1) and dividing by + /// at least ten keeps the rounded coefficient below MaxSignificand. + /// + internal static TValue RoundDecimalIeee754(TValue bits, int digits, MidpointRounding mode) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + if (digits < 0) + { + ThrowHelper.ThrowArgumentOutOfRange_RoundingDigits(nameof(digits)); + } + + if ((uint)mode > (uint)MidpointRounding.ToPositiveInfinity) + { + ThrowHelper.ThrowArgumentException_InvalidEnumValue(mode); + } + + if (TDecimal.IsNaN(bits)) + { + return bits; + } + + if (TDecimal.IsInfinity(bits)) + { + // Canonicalize so a non-canonical infinity operand rounds to the canonical infinity. + return TDecimal.IsNegative(bits) ? TDecimal.NegativeInfinity : TDecimal.PositiveInfinity; + } + + DecodedDecimalIeee754 a = UnpackDecimalIeee754(bits); + int targetExponent = -digits; + + if (a.UnbiasedExponent >= targetExponent) + { + // The quantum is already at or coarser than the requested precision; nothing is discarded. Re-encode so + // a non-canonical operand (coefficient above the format maximum, unpacked to zero) is returned canonical. + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, a.Significand, a.UnbiasedExponent); + } + + int drop = targetExponent - a.UnbiasedExponent; + int coefficientDigits = TValue.IsZero(a.Significand) ? 0 : TDecimal.CountDigits(a.Significand); + + TValue five = TValue.CreateTruncating(5); + TValue quotient; + + // Sign of (discarded - half quantum): negative below the midpoint, zero at it, positive above it. + int discardedComparedToHalf; + bool discardedNonZero; + + if (drop >= coefficientDigits) + { + // Every coefficient digit is discarded, so the retained value is zero before rounding. + quotient = TValue.Zero; + + if (drop > coefficientDigits) + { + // The whole coefficient is strictly less than half of the discarded quantum. + discardedComparedToHalf = -1; + discardedNonZero = !TValue.IsZero(a.Significand); + } + else + { + TValue half = five * TDecimal.Power10(coefficientDigits - 1); + discardedComparedToHalf = a.Significand.CompareTo(half); + discardedNonZero = true; + } + } + else + { + TValue divisor = TDecimal.Power10(drop); + quotient = a.Significand / divisor; + + TValue discarded = a.Significand - (quotient * divisor); + TValue half = five * TDecimal.Power10(drop - 1); + discardedComparedToHalf = discarded.CompareTo(half); + discardedNonZero = !TValue.IsZero(discarded); + } + + bool roundAwayFromZero = mode switch + { + MidpointRounding.ToEven => (discardedComparedToHalf > 0) || ((discardedComparedToHalf == 0) && !TValue.IsZero(quotient & TValue.One)), + MidpointRounding.AwayFromZero => discardedComparedToHalf >= 0, + MidpointRounding.ToZero => false, + MidpointRounding.ToNegativeInfinity => discardedNonZero && a.Signed, + MidpointRounding.ToPositiveInfinity => discardedNonZero && !a.Signed, + _ => throw new UnreachableException(), + }; + + if (roundAwayFromZero) + { + quotient += TValue.One; + } + + return DecimalIeee754FiniteNumberBinaryEncoding(a.Signed, quotient, targetExponent); + } + /// /// 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, 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 725b213679db51..2bfa96b4ce84af 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.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.Binary; using System.Buffers.Text; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -12,10 +13,7 @@ public readonly struct Decimal128 : IComparable, IComparable, IEquatable, - IFloatingPointConstants, - INumber, - INumberBase, - ISignedNumber, + IFloatingPoint, ISpanFormattable, ISpanParsable, IMinMaxValue, @@ -745,6 +743,115 @@ public static Decimal128 Parse(ReadOnlySpan utf8Text, NumberStyles style = /// Gets the mathematical constant tau. public static Decimal128 Tau => new Decimal128(TauValue); + // + // IFloatingPoint + // + + /// + public static Decimal128 Ceiling(Decimal128 x) => new Decimal128(Number.RoundDecimalIeee754(new UInt128(x._upper, x._lower), 0, MidpointRounding.ToPositiveInfinity)); + + /// + public static TInteger ConvertToInteger(Decimal128 value) + where TInteger : IBinaryInteger => TInteger.CreateSaturating(value); + + /// + public static TInteger ConvertToIntegerNative(Decimal128 value) + where TInteger : IBinaryInteger => TInteger.CreateSaturating(value); + + /// + public static Decimal128 Floor(Decimal128 x) => new Decimal128(Number.RoundDecimalIeee754(new UInt128(x._upper, x._lower), 0, MidpointRounding.ToNegativeInfinity)); + + /// + public static Decimal128 Round(Decimal128 x) => new Decimal128(Number.RoundDecimalIeee754(new UInt128(x._upper, x._lower), 0, MidpointRounding.ToEven)); + + /// + public static Decimal128 Round(Decimal128 x, int digits) => new Decimal128(Number.RoundDecimalIeee754(new UInt128(x._upper, x._lower), digits, MidpointRounding.ToEven)); + + /// + public static Decimal128 Round(Decimal128 x, MidpointRounding mode) => new Decimal128(Number.RoundDecimalIeee754(new UInt128(x._upper, x._lower), 0, mode)); + + /// + public static Decimal128 Round(Decimal128 x, int digits, MidpointRounding mode) => new Decimal128(Number.RoundDecimalIeee754(new UInt128(x._upper, x._lower), digits, mode)); + + /// + public static Decimal128 Truncate(Decimal128 x) => new Decimal128(Number.RoundDecimalIeee754(new UInt128(x._upper, x._lower), 0, MidpointRounding.ToZero)); + + /// + int IFloatingPoint.GetExponentByteCount() => sizeof(int); + + /// + int IFloatingPoint.GetExponentShortestBitLength() + { + int exponent = Number.UnpackDecimalIeee754(new UInt128(_upper, _lower)).UnbiasedExponent; + + if (exponent >= 0) + { + return (sizeof(int) * 8) - int.LeadingZeroCount(exponent); + } + else + { + return (sizeof(int) * 8) + 1 - int.LeadingZeroCount(~exponent); + } + } + + /// + int IFloatingPoint.GetSignificandBitLength() => 113; + + /// + int IFloatingPoint.GetSignificandByteCount() => Unsafe.SizeOf(); + + /// + bool IFloatingPoint.TryWriteExponentBigEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteInt32BigEndian(destination, Number.UnpackDecimalIeee754(new UInt128(_upper, _lower)).UnbiasedExponent)) + { + bytesWritten = sizeof(int); + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IFloatingPoint.TryWriteExponentLittleEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteInt32LittleEndian(destination, Number.UnpackDecimalIeee754(new UInt128(_upper, _lower)).UnbiasedExponent)) + { + bytesWritten = sizeof(int); + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IFloatingPoint.TryWriteSignificandBigEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteUInt128BigEndian(destination, Number.UnpackDecimalIeee754(new UInt128(_upper, _lower)).Significand)) + { + bytesWritten = Unsafe.SizeOf(); + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteUInt128LittleEndian(destination, Number.UnpackDecimalIeee754(new UInt128(_upper, _lower)).Significand)) + { + bytesWritten = Unsafe.SizeOf(); + return true; + } + + bytesWritten = 0; + return false; + } + /// Computes the absolute of a value. /// The value for which to get its absolute. /// The absolute of . 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 82ff8909bfc33a..8000356c12d92b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.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.Binary; using System.Buffers.Text; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -14,10 +15,7 @@ public readonly struct Decimal32 : IComparable, IComparable, IEquatable, - IFloatingPointConstants, - INumber, - INumberBase, - ISignedNumber, + IFloatingPoint, ISpanFormattable, ISpanParsable, IMinMaxValue, @@ -768,6 +766,115 @@ public static Decimal32 Parse(ReadOnlySpan utf8Text, NumberStyles style = /// Gets the mathematical constant tau. public static Decimal32 Tau => new Decimal32(TauValue); + // + // IFloatingPoint + // + + /// + public static Decimal32 Ceiling(Decimal32 x) => new Decimal32(Number.RoundDecimalIeee754(x._value, 0, MidpointRounding.ToPositiveInfinity)); + + /// + public static TInteger ConvertToInteger(Decimal32 value) + where TInteger : IBinaryInteger => TInteger.CreateSaturating(value); + + /// + public static TInteger ConvertToIntegerNative(Decimal32 value) + where TInteger : IBinaryInteger => TInteger.CreateSaturating(value); + + /// + public static Decimal32 Floor(Decimal32 x) => new Decimal32(Number.RoundDecimalIeee754(x._value, 0, MidpointRounding.ToNegativeInfinity)); + + /// + public static Decimal32 Round(Decimal32 x) => new Decimal32(Number.RoundDecimalIeee754(x._value, 0, MidpointRounding.ToEven)); + + /// + public static Decimal32 Round(Decimal32 x, int digits) => new Decimal32(Number.RoundDecimalIeee754(x._value, digits, MidpointRounding.ToEven)); + + /// + public static Decimal32 Round(Decimal32 x, MidpointRounding mode) => new Decimal32(Number.RoundDecimalIeee754(x._value, 0, mode)); + + /// + public static Decimal32 Round(Decimal32 x, int digits, MidpointRounding mode) => new Decimal32(Number.RoundDecimalIeee754(x._value, digits, mode)); + + /// + public static Decimal32 Truncate(Decimal32 x) => new Decimal32(Number.RoundDecimalIeee754(x._value, 0, MidpointRounding.ToZero)); + + /// + int IFloatingPoint.GetExponentByteCount() => sizeof(int); + + /// + int IFloatingPoint.GetExponentShortestBitLength() + { + int exponent = Number.UnpackDecimalIeee754(_value).UnbiasedExponent; + + if (exponent >= 0) + { + return (sizeof(int) * 8) - int.LeadingZeroCount(exponent); + } + else + { + return (sizeof(int) * 8) + 1 - int.LeadingZeroCount(~exponent); + } + } + + /// + int IFloatingPoint.GetSignificandBitLength() => 24; + + /// + int IFloatingPoint.GetSignificandByteCount() => sizeof(uint); + + /// + bool IFloatingPoint.TryWriteExponentBigEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteInt32BigEndian(destination, Number.UnpackDecimalIeee754(_value).UnbiasedExponent)) + { + bytesWritten = sizeof(int); + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IFloatingPoint.TryWriteExponentLittleEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteInt32LittleEndian(destination, Number.UnpackDecimalIeee754(_value).UnbiasedExponent)) + { + bytesWritten = sizeof(int); + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IFloatingPoint.TryWriteSignificandBigEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteUInt32BigEndian(destination, Number.UnpackDecimalIeee754(_value).Significand)) + { + bytesWritten = sizeof(uint); + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteUInt32LittleEndian(destination, Number.UnpackDecimalIeee754(_value).Significand)) + { + bytesWritten = sizeof(uint); + return true; + } + + bytesWritten = 0; + return false; + } + /// Computes the absolute of a value. /// The value for which to get its absolute. /// The absolute of . 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 a9d5dea85c7371..1b08a963722056 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.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.Binary; using System.Buffers.Text; using System.Diagnostics.CodeAnalysis; using System.Globalization; @@ -12,10 +13,7 @@ public readonly struct Decimal64 : IComparable, IComparable, IEquatable, - IFloatingPointConstants, - INumber, - INumberBase, - ISignedNumber, + IFloatingPoint, ISpanFormattable, ISpanParsable, IMinMaxValue, @@ -759,6 +757,115 @@ public static Decimal64 Parse(ReadOnlySpan utf8Text, NumberStyles style = /// Gets the mathematical constant tau. public static Decimal64 Tau => new Decimal64(TauValue); + // + // IFloatingPoint + // + + /// + public static Decimal64 Ceiling(Decimal64 x) => new Decimal64(Number.RoundDecimalIeee754(x._value, 0, MidpointRounding.ToPositiveInfinity)); + + /// + public static TInteger ConvertToInteger(Decimal64 value) + where TInteger : IBinaryInteger => TInteger.CreateSaturating(value); + + /// + public static TInteger ConvertToIntegerNative(Decimal64 value) + where TInteger : IBinaryInteger => TInteger.CreateSaturating(value); + + /// + public static Decimal64 Floor(Decimal64 x) => new Decimal64(Number.RoundDecimalIeee754(x._value, 0, MidpointRounding.ToNegativeInfinity)); + + /// + public static Decimal64 Round(Decimal64 x) => new Decimal64(Number.RoundDecimalIeee754(x._value, 0, MidpointRounding.ToEven)); + + /// + public static Decimal64 Round(Decimal64 x, int digits) => new Decimal64(Number.RoundDecimalIeee754(x._value, digits, MidpointRounding.ToEven)); + + /// + public static Decimal64 Round(Decimal64 x, MidpointRounding mode) => new Decimal64(Number.RoundDecimalIeee754(x._value, 0, mode)); + + /// + public static Decimal64 Round(Decimal64 x, int digits, MidpointRounding mode) => new Decimal64(Number.RoundDecimalIeee754(x._value, digits, mode)); + + /// + public static Decimal64 Truncate(Decimal64 x) => new Decimal64(Number.RoundDecimalIeee754(x._value, 0, MidpointRounding.ToZero)); + + /// + int IFloatingPoint.GetExponentByteCount() => sizeof(int); + + /// + int IFloatingPoint.GetExponentShortestBitLength() + { + int exponent = Number.UnpackDecimalIeee754(_value).UnbiasedExponent; + + if (exponent >= 0) + { + return (sizeof(int) * 8) - int.LeadingZeroCount(exponent); + } + else + { + return (sizeof(int) * 8) + 1 - int.LeadingZeroCount(~exponent); + } + } + + /// + int IFloatingPoint.GetSignificandBitLength() => 54; + + /// + int IFloatingPoint.GetSignificandByteCount() => sizeof(ulong); + + /// + bool IFloatingPoint.TryWriteExponentBigEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteInt32BigEndian(destination, Number.UnpackDecimalIeee754(_value).UnbiasedExponent)) + { + bytesWritten = sizeof(int); + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IFloatingPoint.TryWriteExponentLittleEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteInt32LittleEndian(destination, Number.UnpackDecimalIeee754(_value).UnbiasedExponent)) + { + bytesWritten = sizeof(int); + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IFloatingPoint.TryWriteSignificandBigEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteUInt64BigEndian(destination, Number.UnpackDecimalIeee754(_value).Significand)) + { + bytesWritten = sizeof(ulong); + return true; + } + + bytesWritten = 0; + return false; + } + + /// + bool IFloatingPoint.TryWriteSignificandLittleEndian(Span destination, out int bytesWritten) + { + if (BinaryPrimitives.TryWriteUInt64LittleEndian(destination, Number.UnpackDecimalIeee754(_value).Significand)) + { + bytesWritten = sizeof(ulong); + return true; + } + + bytesWritten = 0; + return false; + } + /// Computes the absolute of a value. /// The value for which to get its absolute. /// The absolute of . diff --git a/src/libraries/System.Runtime/ref/System.Runtime.cs b/src/libraries/System.Runtime/ref/System.Runtime.cs index a2b5b08c8d01f8..21f8d460f3f0fb 100644 --- a/src/libraries/System.Runtime/ref/System.Runtime.cs +++ b/src/libraries/System.Runtime/ref/System.Runtime.cs @@ -11429,7 +11429,7 @@ public static void HtmlEncode(string? value, System.IO.TextWriter output) { } } namespace System.Numerics { - public readonly partial struct Decimal128 : 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.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal128 : 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.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal128 E { get { throw null; } } @@ -11449,16 +11449,20 @@ namespace System.Numerics public static System.Numerics.Decimal128 Tau { get { throw null; } } public static System.Numerics.Decimal128 Zero { get { throw null; } } public static System.Numerics.Decimal128 Abs(System.Numerics.Decimal128 value) { throw null; } + public static System.Numerics.Decimal128 Ceiling(System.Numerics.Decimal128 x) { throw null; } public static System.Numerics.Decimal128 Clamp(System.Numerics.Decimal128 value, System.Numerics.Decimal128 min, System.Numerics.Decimal128 max) { throw null; } public static System.Numerics.Decimal128 ClampNative(System.Numerics.Decimal128 value, System.Numerics.Decimal128 min, System.Numerics.Decimal128 max) { throw null; } public int CompareTo(System.Numerics.Decimal128 other) { throw null; } public int CompareTo(object? value) { throw null; } + public static TInteger ConvertToIntegerNative(System.Numerics.Decimal128 value) where TInteger : System.Numerics.IBinaryInteger { throw null; } + public static TInteger ConvertToInteger(System.Numerics.Decimal128 value) where TInteger : System.Numerics.IBinaryInteger { throw null; } public static System.Numerics.Decimal128 CopySign(System.Numerics.Decimal128 value, System.Numerics.Decimal128 sign) { throw null; } public static System.Numerics.Decimal128 CreateChecked(TOther value) where TOther : System.Numerics.INumberBase { throw null; } public static System.Numerics.Decimal128 CreateSaturating(TOther value) where TOther : System.Numerics.INumberBase { throw null; } public static System.Numerics.Decimal128 CreateTruncating(TOther value) where TOther : System.Numerics.INumberBase { 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 static System.Numerics.Decimal128 Floor(System.Numerics.Decimal128 x) { throw null; } public override int GetHashCode() { throw null; } public static bool IsEvenInteger(System.Numerics.Decimal128 value) { throw null; } public static bool IsFinite(System.Numerics.Decimal128 value) { throw null; } @@ -11572,7 +11576,19 @@ namespace System.Numerics 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 static System.Numerics.Decimal128 Round(System.Numerics.Decimal128 x) { throw null; } + public static System.Numerics.Decimal128 Round(System.Numerics.Decimal128 x, int digits) { throw null; } + public static System.Numerics.Decimal128 Round(System.Numerics.Decimal128 x, int digits, System.MidpointRounding mode) { throw null; } + public static System.Numerics.Decimal128 Round(System.Numerics.Decimal128 x, System.MidpointRounding mode) { throw null; } public static int Sign(System.Numerics.Decimal128 value) { throw null; } + int System.Numerics.IFloatingPoint.GetExponentByteCount() { throw null; } + int System.Numerics.IFloatingPoint.GetExponentShortestBitLength() { throw null; } + int System.Numerics.IFloatingPoint.GetSignificandBitLength() { throw null; } + int System.Numerics.IFloatingPoint.GetSignificandByteCount() { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteExponentBigEndian(System.Span destination, out int bytesWritten) { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteExponentLittleEndian(System.Span destination, out int bytesWritten) { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteSignificandBigEndian(System.Span destination, out int bytesWritten) { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteSignificandLittleEndian(System.Span destination, out int bytesWritten) { throw null; } static bool System.Numerics.INumberBase.IsCanonical(System.Numerics.Decimal128 value) { throw null; } static bool System.Numerics.INumberBase.IsComplexNumber(System.Numerics.Decimal128 value) { throw null; } static bool System.Numerics.INumberBase.IsImaginaryNumber(System.Numerics.Decimal128 value) { throw null; } @@ -11587,6 +11603,7 @@ namespace System.Numerics public string ToString(System.IFormatProvider? provider) { 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 System.Numerics.Decimal128 Truncate(System.Numerics.Decimal128 x) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal128 result) { throw null; } @@ -11602,7 +11619,7 @@ namespace System.Numerics public static bool TryParsePartial([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal128 result, out int charsConsumed) { throw null; } } - public readonly partial struct Decimal32 : 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.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal32 : 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.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal32 E { get { throw null; } } @@ -11622,16 +11639,20 @@ namespace System.Numerics public static System.Numerics.Decimal32 Tau { get { throw null; } } public static System.Numerics.Decimal32 Zero { get { throw null; } } public static System.Numerics.Decimal32 Abs(System.Numerics.Decimal32 value) { throw null; } + public static System.Numerics.Decimal32 Ceiling(System.Numerics.Decimal32 x) { throw null; } public static System.Numerics.Decimal32 Clamp(System.Numerics.Decimal32 value, System.Numerics.Decimal32 min, System.Numerics.Decimal32 max) { throw null; } public static System.Numerics.Decimal32 ClampNative(System.Numerics.Decimal32 value, System.Numerics.Decimal32 min, System.Numerics.Decimal32 max) { throw null; } public int CompareTo(System.Numerics.Decimal32 other) { throw null; } public int CompareTo(object? value) { throw null; } + public static TInteger ConvertToIntegerNative(System.Numerics.Decimal32 value) where TInteger : System.Numerics.IBinaryInteger { throw null; } + public static TInteger ConvertToInteger(System.Numerics.Decimal32 value) where TInteger : System.Numerics.IBinaryInteger { throw null; } public static System.Numerics.Decimal32 CopySign(System.Numerics.Decimal32 value, System.Numerics.Decimal32 sign) { throw null; } public static System.Numerics.Decimal32 CreateChecked(TOther value) where TOther : System.Numerics.INumberBase { throw null; } public static System.Numerics.Decimal32 CreateSaturating(TOther value) where TOther : System.Numerics.INumberBase { throw null; } public static System.Numerics.Decimal32 CreateTruncating(TOther value) where TOther : System.Numerics.INumberBase { 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 static System.Numerics.Decimal32 Floor(System.Numerics.Decimal32 x) { throw null; } public override int GetHashCode() { throw null; } public static bool IsEvenInteger(System.Numerics.Decimal32 value) { throw null; } public static bool IsFinite(System.Numerics.Decimal32 value) { throw null; } @@ -11749,7 +11770,19 @@ namespace System.Numerics 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 static System.Numerics.Decimal32 Round(System.Numerics.Decimal32 x) { throw null; } + public static System.Numerics.Decimal32 Round(System.Numerics.Decimal32 x, int digits) { throw null; } + public static System.Numerics.Decimal32 Round(System.Numerics.Decimal32 x, int digits, System.MidpointRounding mode) { throw null; } + public static System.Numerics.Decimal32 Round(System.Numerics.Decimal32 x, System.MidpointRounding mode) { throw null; } public static int Sign(System.Numerics.Decimal32 value) { throw null; } + int System.Numerics.IFloatingPoint.GetExponentByteCount() { throw null; } + int System.Numerics.IFloatingPoint.GetExponentShortestBitLength() { throw null; } + int System.Numerics.IFloatingPoint.GetSignificandBitLength() { throw null; } + int System.Numerics.IFloatingPoint.GetSignificandByteCount() { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteExponentBigEndian(System.Span destination, out int bytesWritten) { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteExponentLittleEndian(System.Span destination, out int bytesWritten) { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteSignificandBigEndian(System.Span destination, out int bytesWritten) { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteSignificandLittleEndian(System.Span destination, out int bytesWritten) { throw null; } static bool System.Numerics.INumberBase.IsCanonical(System.Numerics.Decimal32 value) { throw null; } static bool System.Numerics.INumberBase.IsComplexNumber(System.Numerics.Decimal32 value) { throw null; } static bool System.Numerics.INumberBase.IsImaginaryNumber(System.Numerics.Decimal32 value) { throw null; } @@ -11764,6 +11797,7 @@ namespace System.Numerics public string ToString(System.IFormatProvider? provider) { 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 System.Numerics.Decimal32 Truncate(System.Numerics.Decimal32 x) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal32 result) { throw null; } @@ -11779,7 +11813,7 @@ namespace System.Numerics public static bool TryParsePartial([System.Diagnostics.CodeAnalysis.NotNullWhenAttribute(true)] string? s, System.Globalization.NumberStyles style, System.IFormatProvider? provider, out System.Numerics.Decimal32 result, out int charsConsumed) { throw null; } } - public readonly partial struct Decimal64 : 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.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators + public readonly partial struct Decimal64 : 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.IComparisonOperators, System.Numerics.IDecrementOperators, System.Numerics.IDivisionOperators, System.Numerics.IEqualityOperators, System.Numerics.IFloatingPoint, System.Numerics.IFloatingPointConstants, System.Numerics.IIncrementOperators, System.Numerics.IMinMaxValue, System.Numerics.IModulusOperators, System.Numerics.IMultiplicativeIdentity, System.Numerics.IMultiplyOperators, System.Numerics.INumber, System.Numerics.INumberBase, System.Numerics.ISignedNumber, System.Numerics.ISubtractionOperators, System.Numerics.IUnaryNegationOperators, System.Numerics.IUnaryPlusOperators { private readonly int _dummyPrimitive; public static System.Numerics.Decimal64 E { get { throw null; } } @@ -11799,16 +11833,20 @@ namespace System.Numerics public static System.Numerics.Decimal64 Tau { get { throw null; } } public static System.Numerics.Decimal64 Zero { get { throw null; } } public static System.Numerics.Decimal64 Abs(System.Numerics.Decimal64 value) { throw null; } + public static System.Numerics.Decimal64 Ceiling(System.Numerics.Decimal64 x) { throw null; } public static System.Numerics.Decimal64 Clamp(System.Numerics.Decimal64 value, System.Numerics.Decimal64 min, System.Numerics.Decimal64 max) { throw null; } public static System.Numerics.Decimal64 ClampNative(System.Numerics.Decimal64 value, System.Numerics.Decimal64 min, System.Numerics.Decimal64 max) { throw null; } public int CompareTo(System.Numerics.Decimal64 other) { throw null; } public int CompareTo(object? value) { throw null; } + public static TInteger ConvertToIntegerNative(System.Numerics.Decimal64 value) where TInteger : System.Numerics.IBinaryInteger { throw null; } + public static TInteger ConvertToInteger(System.Numerics.Decimal64 value) where TInteger : System.Numerics.IBinaryInteger { throw null; } public static System.Numerics.Decimal64 CopySign(System.Numerics.Decimal64 value, System.Numerics.Decimal64 sign) { throw null; } public static System.Numerics.Decimal64 CreateChecked(TOther value) where TOther : System.Numerics.INumberBase { throw null; } public static System.Numerics.Decimal64 CreateSaturating(TOther value) where TOther : System.Numerics.INumberBase { throw null; } public static System.Numerics.Decimal64 CreateTruncating(TOther value) where TOther : System.Numerics.INumberBase { 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 static System.Numerics.Decimal64 Floor(System.Numerics.Decimal64 x) { throw null; } public override int GetHashCode() { throw null; } public static bool IsEvenInteger(System.Numerics.Decimal64 value) { throw null; } public static bool IsFinite(System.Numerics.Decimal64 value) { throw null; } @@ -11924,7 +11962,19 @@ namespace System.Numerics 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 static System.Numerics.Decimal64 Round(System.Numerics.Decimal64 x) { throw null; } + public static System.Numerics.Decimal64 Round(System.Numerics.Decimal64 x, int digits) { throw null; } + public static System.Numerics.Decimal64 Round(System.Numerics.Decimal64 x, int digits, System.MidpointRounding mode) { throw null; } + public static System.Numerics.Decimal64 Round(System.Numerics.Decimal64 x, System.MidpointRounding mode) { throw null; } public static int Sign(System.Numerics.Decimal64 value) { throw null; } + int System.Numerics.IFloatingPoint.GetExponentByteCount() { throw null; } + int System.Numerics.IFloatingPoint.GetExponentShortestBitLength() { throw null; } + int System.Numerics.IFloatingPoint.GetSignificandBitLength() { throw null; } + int System.Numerics.IFloatingPoint.GetSignificandByteCount() { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteExponentBigEndian(System.Span destination, out int bytesWritten) { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteExponentLittleEndian(System.Span destination, out int bytesWritten) { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteSignificandBigEndian(System.Span destination, out int bytesWritten) { throw null; } + bool System.Numerics.IFloatingPoint.TryWriteSignificandLittleEndian(System.Span destination, out int bytesWritten) { throw null; } static bool System.Numerics.INumberBase.IsCanonical(System.Numerics.Decimal64 value) { throw null; } static bool System.Numerics.INumberBase.IsComplexNumber(System.Numerics.Decimal64 value) { throw null; } static bool System.Numerics.INumberBase.IsImaginaryNumber(System.Numerics.Decimal64 value) { throw null; } @@ -11939,6 +11989,7 @@ namespace System.Numerics public string ToString(System.IFormatProvider? provider) { 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 System.Numerics.Decimal64 Truncate(System.Numerics.Decimal64 x) { throw null; } public bool TryFormat(System.Span utf8Destination, out int bytesWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public bool TryFormat(System.Span destination, out int charsWritten, [System.Diagnostics.CodeAnalysis.StringSyntaxAttribute("NumericFormat")] System.ReadOnlySpan format = default(System.ReadOnlySpan), System.IFormatProvider? provider = null) { throw null; } public static bool TryParse(System.ReadOnlySpan utf8Text, System.IFormatProvider? provider, [System.Diagnostics.CodeAnalysis.MaybeNullWhenAttribute(false)] out System.Numerics.Decimal64 result) { throw null; } From 3be51846d9ea30f72d65744100e481cb8070c9f7 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 01:20:37 -0700 Subject: [PATCH 06/14] Add IFloatingPoint tests for the IEEE 754 decimal types Covers Round (all midpoint modes, curated NaN/Inf/signed-zero/tie edge cases plus oracle-derived random vectors), operator %, the Ceiling/Floor/Truncate/ Round convenience overloads, and the exponent/significand writers + ConvertToInteger. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System/Decimal128Tests.cs | 157 +++++++++++++++++- .../System/Decimal32Tests.cs | 157 +++++++++++++++++- .../System/Decimal64Tests.cs | 157 +++++++++++++++++- 3 files changed, 468 insertions(+), 3 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 f86fbcb5ae524f..dd66f605535a34 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,6 +1,7 @@ -// 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.Buffers.Binary; using System.Collections.Generic; using System.Globalization; using System.Numerics; @@ -2531,5 +2532,159 @@ public static void CreateToSystemDecimalTest() Assert.Throws(() => decimal.CreateChecked(Decimal128.MaxValue)); Assert.Throws(() => decimal.CreateChecked(Decimal128.NaN)); } + + public static IEnumerable op_Modulus_TestData() + { + yield return new object[] { new UInt128(0x4810000000000000, 0x0000000000000000), new UInt128(0x990E000000000000, 0x00727CEB5CF62962), new UInt128(0x190E000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x02DC000000000000, 0x000000000000C323), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x02DC000000000000, 0x000000000000C323) }; + yield return new object[] { new UInt128(0x0820000000000000, 0x000C7F077915197C), new UInt128(0x557C000000000000, 0x0B1E07FE86637A2F), new UInt128(0x0820000000000000, 0x000C7F077915197C) }; + yield return new object[] { new UInt128(0x19D815CE6B5B34C3, 0x8F50B6B1CC625385), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xC35E00001C26FEE5, 0x1B671598D84E4FDE), new UInt128(0xD6D6000000000000, 0x00000001979A8758), new UInt128(0xC35E00001C26FEE5, 0x1B671598D84E4FDE) }; + yield return new object[] { new UInt128(0x36840000000810A5, 0xB6D5CBDA7C41FE15), new UInt128(0xA602000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x4BE200000000885C, 0x1D87CD3186D0C6CC), new UInt128(0x8180000000000000, 0x0000000000006BD7), new UInt128(0x0180000000000000, 0x00000000000009B8) }; + yield return new object[] { new UInt128(0xA9100000000001C7, 0x5A954BD2316F738B), new UInt128(0x4E34000000000AB0, 0x19F2EB310DEB90C4), new UInt128(0xA9100000000001C7, 0x5A954BD2316F738B) }; + yield return new object[] { new UInt128(0x5B74000000BE82C5, 0xEEE7839C9CF8EDF7), new UInt128(0x4030000000000000, 0x001FFAF9A71D2883), new UInt128(0x4030000000000000, 0x001CD7AC6FE3A534) }; + yield return new object[] { new UInt128(0x8102000A63BCF670, 0xD60A6355E652AD8A), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x901C000000000000, 0x4C27457B78954A33), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xBE8800005BE01275, 0xF6418621117238DD), new UInt128(0xD932000000000000, 0x00000000000D8679), new UInt128(0xBE8800005BE01275, 0xF6418621117238DD) }; + yield return new object[] { new UInt128(0xB250000000000000, 0x0000000000000000), new UInt128(0x2504000000000000, 0x000000000002E306), new UInt128(0xA504000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x1344000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xDE2B69A7FDF37D69, 0x0C83953B47DD2851), new UInt128(0x0DB804DBC574207F, 0x31843184B6F0A80A), new UInt128(0x8DB8016B5216022A, 0x9B020AB9F393AFFC) }; + yield return new object[] { new UInt128(0xC38400001491446B, 0x673D6453971DBCAB), new UInt128(0x1A4A000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x2720000000000000, 0x0000097407D8946B), new UInt128(0xB5F4000000000000, 0x00000000000AF371), new UInt128(0x2720000000000000, 0x0000097407D8946B) }; + yield return new object[] { new UInt128(0x9BF6000000000000, 0x1A30D732F747DD0E), new UInt128(0xA3B8000ABA01A581, 0x4406F1E2E09B39B9), new UInt128(0x9BF6000000000000, 0x1A30D732F747DD0E) }; + yield return new object[] { new UInt128(0x0092000000000000, 0x00000000399224C3), new UInt128(0x5AEC000000000000, 0x0000096C2B8F87F0), new UInt128(0x0092000000000000, 0x00000000399224C3) }; + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x8958000000000000, 0x000000000FC25A7C), new UInt128(0x7C00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x57B2000000000000, 0x000F5DCED6652344), new UInt128(0xB5A2000000001139, 0x85837D2F6B305C5F), new UInt128(0x35A20000000009F6, 0x20430D5F4C647EA6) }; + yield return new object[] { new UInt128(0xAD78000000000000, 0x0000000000000000), new UInt128(0x346E000000000016, 0xF78007A0158F736C), new UInt128(0xAD78000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xA7B4000000000000, 0x0000000000000001), new UInt128(0x26B4000000000000, 0x03345E83946E53FD), new UInt128(0xA6B4000000000000, 0x007817AA3A3AB10A) }; + yield return new object[] { new UInt128(0x0DE2000000000003, 0xF7F0A35B2122BACC), new UInt128(0x40B6000000000000, 0x00000001C0645AF2), new UInt128(0x0DE2000000000003, 0xF7F0A35B2122BACC) }; + yield return new object[] { new UInt128(0xA984000591CB546F, 0xCDCB05B27845D2D7), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xA984000591CB546F, 0xCDCB05B27845D2D7) }; + yield return new object[] { new UInt128(0x99D4000000000000, 0x0000000000000000), new UInt128(0x01CA000000000000, 0x00003FCDF3F54CA3), new UInt128(0x81CA000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x5ECE0059773868D9, 0xD79D80205EF487AD), new UInt128(0x575C000000000000, 0x000000000000250A), new UInt128(0x575C000000000000, 0x000000000000012C) }; + yield return new object[] { new UInt128(0xAA26000000000000, 0x0000134F75405BA4), new UInt128(0x977C000000000000, 0x000003CC1F1EFDB2), new UInt128(0x977C000000000000, 0x000001BF28D406A8) }; + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x97D0000000000000, 0x0000000000000347), new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x546C000000000000, 0x00000000000000C7), new UInt128(0xD59C000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0x52DC000000000000, 0x0000000000000000), new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x4BAC0000000015DC, 0xF9962B8B9EA1F26D), new UInt128(0x40D2000000000000, 0x0000000000000007), new UInt128(0x40D2000000000000, 0x0000000000000002) }; + yield return new object[] { new UInt128(0xBABA045882CF1D86, 0x95BA36F8663F9C4C), new UInt128(0x8220000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x386E00000000003C, 0x845CF4E0C470005B), new UInt128(0x7C00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x5DDD4F2CD7014F7C, 0xF0B4AEB4AEDB5010), new UInt128(0x8AA20000000001C6, 0x4D4C69C05E1AEEC9), new UInt128(0x0AA200000000012C, 0xBDCF5BE2645FEAEF) }; + yield return new object[] { new UInt128(0x9CF600000023177C, 0x325CB9124638EB43), new UInt128(0xC77E000000000000, 0x0000000000000009), new UInt128(0x9CF600000023177C, 0x325CB9124638EB43) }; + yield return new object[] { new UInt128(0x3A72000000000000, 0x0000000000000000), new UInt128(0x3752000000000000, 0x000000B07C9C82E5), new UInt128(0x3752000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), new UInt128(0xB3E800008DD69B34, 0x8898B05BA9A564C1), new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xBA20000000000000, 0x0000000000001C83), new UInt128(0x3D1C000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; + } + + [Theory] + [MemberData(nameof(op_Modulus_TestData))] + public static void op_Modulus(UInt128 left, UInt128 right, UInt128 expected) + { + Decimal128 result = Unsafe.BitCast(left) % Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + public static IEnumerable RoundToDigits_TestData() + { + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), 0, MidpointRounding.ToEven, new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN passes through + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), 3, MidpointRounding.AwayFromZero, new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf passes through + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), 2, MidpointRounding.ToPositiveInfinity, new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf passes through + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000019), 0, MidpointRounding.ToEven, new UInt128(0x3040000000000000, 0x0000000000000002) }; // 2.5 -> 2 (ToEven) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000019), 0, MidpointRounding.AwayFromZero, new UInt128(0x3040000000000000, 0x0000000000000003) }; // 2.5 -> 3 (AwayFromZero) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000023), 0, MidpointRounding.ToEven, new UInt128(0x3040000000000000, 0x0000000000000004) }; // 3.5 -> 4 (ToEven) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000005), 0, MidpointRounding.ToEven, new UInt128(0x3040000000000000, 0x0000000000000000) }; // 0.5 -> 0 (ToEven) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000005), 0, MidpointRounding.ToPositiveInfinity, new UInt128(0x3040000000000000, 0x0000000000000001) }; // 0.5 -> 1 (ToPositiveInfinity) + yield return new object[] { new UInt128(0xB03E000000000000, 0x0000000000000005), 0, MidpointRounding.ToNegativeInfinity, new UInt128(0xB040000000000000, 0x0000000000000001) }; // -0.5 -> -1 (ToNegativeInfinity) + yield return new object[] { new UInt128(0xB03C000000000000, 0x0000000000000019), 0, MidpointRounding.ToPositiveInfinity, new UInt128(0xB040000000000000, 0x0000000000000000) }; // -0.25 -> -0 (ToPositiveInfinity) + yield return new object[] { new UInt128(0xB03C000000000000, 0x0000000000000019), 0, MidpointRounding.ToNegativeInfinity, new UInt128(0xB040000000000000, 0x0000000000000001) }; // -0.25 -> -1 (ToNegativeInfinity) + yield return new object[] { new UInt128(0x303C000000000000, 0x0000000000000019), 0, MidpointRounding.ToZero, new UInt128(0x3040000000000000, 0x0000000000000000) }; // 0.25 -> 0 (ToZero) + yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000005), 5, MidpointRounding.ToEven, new UInt128(0x303E000000000000, 0x0000000000000005) }; // already finer than target, no-op + yield return new object[] { new UInt128(0xB03E000000000000, 0x0000000000000000), 0, MidpointRounding.ToNegativeInfinity, new UInt128(0xB040000000000000, 0x0000000000000000) }; // -0 stays -0 (ToNegativeInfinity) + yield return new object[] { new UInt128(0x303A000000000000, 0x000000000001E240), 2, MidpointRounding.ToEven, new UInt128(0x303C000000000000, 0x000000000000303A) }; // 123.456 -> 123.46 (ToEven) + yield return new object[] { new UInt128(0xACA200001A64F0B2, 0x47116E8F4AA7655A), 1, MidpointRounding.AwayFromZero, new UInt128(0xB03E000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xBBD6000000000000, 0x0000000000000000), 34, MidpointRounding.AwayFromZero, new UInt128(0xBBD6000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x5CB4020BD4E73D0E, 0x6B8B8C501BF89C14), 19, MidpointRounding.ToEven, new UInt128(0x5CB4020BD4E73D0E, 0x6B8B8C501BF89C14) }; + yield return new object[] { new UInt128(0x4C48000000000000, 0x00000001DD06A8E5), 32, MidpointRounding.ToZero, new UInt128(0x4C48000000000000, 0x00000001DD06A8E5) }; + yield return new object[] { new UInt128(0x5008000000000000, 0x00000000008BDB96), 22, MidpointRounding.ToZero, new UInt128(0x5008000000000000, 0x00000000008BDB96) }; + yield return new object[] { new UInt128(0x44A600000004A2E4, 0x946727A4C20797B4), 35, MidpointRounding.AwayFromZero, new UInt128(0x44A600000004A2E4, 0x946727A4C20797B4) }; + yield return new object[] { new UInt128(0xA5940002963B08E8, 0x1CFEC92070EC50BB), 15, MidpointRounding.AwayFromZero, new UInt128(0xB022000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x9EE8000000000000, 0x0000000000000000), 34, MidpointRounding.ToPositiveInfinity, new UInt128(0xAFFC000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x81E4000000000000, 0x000000000000F435), 9, MidpointRounding.ToNegativeInfinity, new UInt128(0xB02E000000000000, 0x0000000000000001) }; + yield return new object[] { new UInt128(0x4878DE405BD7331D, 0x0541CCBC7509BA76), 8, MidpointRounding.ToNegativeInfinity, new UInt128(0x4878DE405BD7331D, 0x0541CCBC7509BA76) }; + yield return new object[] { new UInt128(0x1A40000000000000, 0x0000000000208C95), 30, MidpointRounding.ToZero, new UInt128(0x3004000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), 32, MidpointRounding.ToZero, new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x83BC000000000000, 0x246A51A7EDEE468A), 16, MidpointRounding.AwayFromZero, new UInt128(0xB020000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), 36, MidpointRounding.ToZero, new UInt128(0x7800000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x2A6C000000000018, 0x524FE2F25ACC517F), 13, MidpointRounding.ToEven, new UInt128(0x3026000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x4E8E295B67DFC0C6, 0xE4BC3F5859D95A1C), 18, MidpointRounding.AwayFromZero, new UInt128(0x4E8E295B67DFC0C6, 0xE4BC3F5859D95A1C) }; + yield return new object[] { new UInt128(0xC234000000000000, 0x00000000003638E6), 7, MidpointRounding.ToZero, new UInt128(0xC234000000000000, 0x00000000003638E6) }; + yield return new object[] { new UInt128(0x2448000000000003, 0xC16B45BF142025EA), 24, MidpointRounding.ToPositiveInfinity, new UInt128(0x3010000000000000, 0x0000000000000001) }; + yield return new object[] { new UInt128(0x4F96000000000000, 0x000000000000027D), 6, MidpointRounding.ToZero, new UInt128(0x4F96000000000000, 0x000000000000027D) }; + yield return new object[] { new UInt128(0xCC54000000000000, 0x00000000005DFBBC), 16, MidpointRounding.ToEven, new UInt128(0xCC54000000000000, 0x00000000005DFBBC) }; + yield return new object[] { new UInt128(0x8030000000000000, 0x000000000000002F), 13, MidpointRounding.ToEven, new UInt128(0xB026000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), 20, MidpointRounding.ToZero, new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x1F64000000000000, 0x0000000000000000), 14, MidpointRounding.ToZero, new UInt128(0x3024000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x2D9C000000000000, 0x000000000000AF5A), 11, MidpointRounding.ToEven, new UInt128(0x302A000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xAF9C000000000000, 0x0000000000000032), 24, MidpointRounding.ToPositiveInfinity, new UInt128(0xB010000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x5F9E0000000003FC, 0x7B93612B2A7B1A77), 20, MidpointRounding.ToZero, new UInt128(0x5F9E0000000003FC, 0x7B93612B2A7B1A77) }; + yield return new object[] { new UInt128(0x5ABE000000000000, 0x0000090FF790C39F), 36, MidpointRounding.ToEven, new UInt128(0x5ABE000000000000, 0x0000090FF790C39F) }; + yield return new object[] { new UInt128(0x1F34000000000000, 0x0000000000704EFF), 34, MidpointRounding.ToZero, new UInt128(0x2FFC000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0xB088000000000000, 0x00000007B16C6B74), 27, MidpointRounding.ToZero, new UInt128(0xB088000000000000, 0x00000007B16C6B74) }; + yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), 9, MidpointRounding.ToZero, new UInt128(0xF800000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x8848000001DF35DC, 0xB97C8D8D51686009), 13, MidpointRounding.ToNegativeInfinity, new UInt128(0xB026000000000000, 0x0000000000000001) }; + yield return new object[] { new UInt128(0x2412000000017DA7, 0xA70161F30FBAD560), 13, MidpointRounding.AwayFromZero, new UInt128(0x3026000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x43A4007DC21E3069, 0xD3DC36E844F4E8E4), 16, MidpointRounding.ToZero, new UInt128(0x43A4007DC21E3069, 0xD3DC36E844F4E8E4) }; + yield return new object[] { new UInt128(0x38E4000000027ED7, 0x90B063487ECA3640), 20, MidpointRounding.ToEven, new UInt128(0x38E4000000027ED7, 0x90B063487ECA3640) }; + yield return new object[] { new UInt128(0xC326000000000000, 0x000000021EBC0119), 30, MidpointRounding.AwayFromZero, new UInt128(0xC326000000000000, 0x000000021EBC0119) }; + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), 12, MidpointRounding.ToNegativeInfinity, new UInt128(0xFC00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x4A40000000000000, 0x000000000000038D), 11, MidpointRounding.ToNegativeInfinity, new UInt128(0x4A40000000000000, 0x000000000000038D) }; + yield return new object[] { new UInt128(0x3382D503E84E1B51, 0x5301BCC58952CAB2), 29, MidpointRounding.ToEven, new UInt128(0x3382D503E84E1B51, 0x5301BCC58952CAB2) }; + yield return new object[] { new UInt128(0x9FBE000000000000, 0x0000000000000363), 24, MidpointRounding.ToEven, new UInt128(0xB010000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x374A000000000000, 0x000000A466DBFDD5), 21, MidpointRounding.ToZero, new UInt128(0x374A000000000000, 0x000000A466DBFDD5) }; + } + + [Theory] + [MemberData(nameof(RoundToDigits_TestData))] + public static void RoundToDigits(UInt128 value, int digits, MidpointRounding mode, UInt128 expected) + { + Decimal128 result = Decimal128.Round(Unsafe.BitCast(value), digits, mode); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Fact] + public static void RoundConvenienceOverloads() + { + Decimal128 x = Unsafe.BitCast(new UInt128(0x303E000000000000, 0x0000000000000019)); // 2.5 + + Assert.Equal(Decimal128.Round(x, 0, MidpointRounding.ToPositiveInfinity), Decimal128.Ceiling(x)); + Assert.Equal(Decimal128.Round(x, 0, MidpointRounding.ToNegativeInfinity), Decimal128.Floor(x)); + Assert.Equal(Decimal128.Round(x, 0, MidpointRounding.ToZero), Decimal128.Truncate(x)); + Assert.Equal(Decimal128.Round(x, 0, MidpointRounding.ToEven), Decimal128.Round(x)); + Assert.Equal(Decimal128.Round(x, 0, MidpointRounding.AwayFromZero), Decimal128.Round(x, MidpointRounding.AwayFromZero)); + Assert.Equal(Decimal128.Round(x, 2, MidpointRounding.ToEven), Decimal128.Round(x, 2)); + } + + [Fact] + public static void IFloatingPoint_ExponentAndSignificand() + { + IFloatingPoint value = Unsafe.BitCast(new UInt128(0x303C000000000000, 0x0000000000003039)); // 123.45 + + Assert.Equal(sizeof(int), value.GetExponentByteCount()); + Assert.Equal(Unsafe.SizeOf(), value.GetSignificandByteCount()); + + Span exponent = stackalloc byte[value.GetExponentByteCount()]; + Assert.True(value.TryWriteExponentLittleEndian(exponent, out int exponentWritten)); + Assert.Equal(sizeof(int), exponentWritten); + Assert.Equal(-2, BinaryPrimitives.ReadInt32LittleEndian(exponent)); + + Span significand = stackalloc byte[value.GetSignificandByteCount()]; + Assert.True(value.TryWriteSignificandLittleEndian(significand, out int significandWritten)); + Assert.Equal(Unsafe.SizeOf(), significandWritten); + Assert.Equal((UInt128)12345, BinaryPrimitives.ReadUInt128LittleEndian(significand)); + + Assert.Equal(123, Decimal128.ConvertToInteger(Unsafe.BitCast(new UInt128(0x303C000000000000, 0x0000000000003039)))); + } + } } 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 28e8231006acd7..a2808494feea0c 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,6 +1,7 @@ -// 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.Buffers.Binary; using System.Collections.Generic; using System.Globalization; using System.Numerics; @@ -2531,5 +2532,159 @@ public static void CreateToSystemDecimalTest() Assert.Throws(() => decimal.CreateChecked(Decimal32.MaxValue)); Assert.Throws(() => decimal.CreateChecked(Decimal32.NaN)); } + + public static IEnumerable op_Modulus_TestData() + { + yield return new object[] { 0x30800000U, 0x3B802494U, 0x30800000U }; + yield return new object[] { 0x03800001U, 0xD9816ABDU, 0x03800001U }; + yield return new object[] { 0x220005D4U, 0x9C814F1CU, 0x1C80D12CU }; + yield return new object[] { 0xBE8024DDU, 0xFC000000U, 0xFC000000U }; + yield return new object[] { 0x0719EB11U, 0xF11248EDU, 0x0719EB11U }; + yield return new object[] { 0x588E93BAU, 0x09800000U, 0x7C000000U }; + yield return new object[] { 0x3F8E737BU, 0x59800005U, 0x3F8E737BU }; + yield return new object[] { 0xBD0CC3EAU, 0xD600096BU, 0xBD0CC3EAU }; + yield return new object[] { 0xD5811B28U, 0x6245C247U, 0x895CDC6DU }; + yield return new object[] { 0x4B000008U, 0xF8000000U, 0x4B000008U }; + yield return new object[] { 0xC0800011U, 0xD7000000U, 0x7C000000U }; + yield return new object[] { 0xCD824997U, 0x0E801B4AU, 0x8E800298U }; + yield return new object[] { 0x58800000U, 0x2B001201U, 0x2B000000U }; + yield return new object[] { 0x44009111U, 0x3D800000U, 0x7C000000U }; + yield return new object[] { 0x11000052U, 0x4E936B7DU, 0x11000052U }; + yield return new object[] { 0xF8000000U, 0xFC000000U, 0xFC000000U }; + yield return new object[] { 0xDF815AC4U, 0xFC000000U, 0xFC000000U }; + yield return new object[] { 0x070008A0U, 0xC3064EB3U, 0x070008A0U }; + yield return new object[] { 0xDD000004U, 0xDC000009U, 0xDC000004U }; + yield return new object[] { 0xDA08333FU, 0xCA800056U, 0xCA800036U }; + yield return new object[] { 0xAA001D5CU, 0x86827828U, 0x86816690U }; + yield return new object[] { 0x9F85DA07U, 0x389FE11CU, 0x9F85DA07U }; + yield return new object[] { 0xD70013AAU, 0xC5844302U, 0xC581626CU }; + yield return new object[] { 0xAA80890CU, 0xFC000000U, 0xFC000000U }; + yield return new object[] { 0x96800218U, 0x3881296CU, 0x96800218U }; + yield return new object[] { 0x4F000000U, 0x19000001U, 0x19000000U }; + yield return new object[] { 0x9E800008U, 0x800D7106U, 0x8008A836U }; + yield return new object[] { 0xF8000000U, 0xCC000000U, 0x7C000000U }; + yield return new object[] { 0x120DBC9EU, 0xD60049FBU, 0x120DBC9EU }; + yield return new object[] { 0x92000C66U, 0xF8000000U, 0x92000C66U }; + yield return new object[] { 0x41CFB625U, 0xF8000000U, 0x41CFB625U }; + yield return new object[] { 0x912AE74FU, 0xFC000000U, 0xFC000000U }; + yield return new object[] { 0xA10018DBU, 0xA380107BU, 0xA10018DBU }; + yield return new object[] { 0x78000000U, 0x8D0137A3U, 0x7C000000U }; + yield return new object[] { 0xFC000000U, 0xE8E1DBD4U, 0xFC000000U }; + yield return new object[] { 0x42000000U, 0xB2800000U, 0x7C000000U }; + yield return new object[] { 0xAE800000U, 0x53800007U, 0xAE800000U }; + yield return new object[] { 0xB1800002U, 0xFC000000U, 0xFC000000U }; + yield return new object[] { 0x98000D0EU, 0xF8000000U, 0x98000D0EU }; + yield return new object[] { 0x84C31037U, 0x97000D9FU, 0x84C31037U }; + } + + [Theory] + [MemberData(nameof(op_Modulus_TestData))] + public static void op_Modulus(uint left, uint right, uint expected) + { + Decimal32 result = Unsafe.BitCast(left) % Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + public static IEnumerable RoundToDigits_TestData() + { + yield return new object[] { 0xFC000000U, 0, MidpointRounding.ToEven, 0xFC000000U }; // NaN passes through + yield return new object[] { 0x78000000U, 3, MidpointRounding.AwayFromZero, 0x78000000U }; // +Inf passes through + yield return new object[] { 0xF8000000U, 2, MidpointRounding.ToPositiveInfinity, 0xF8000000U }; // -Inf passes through + yield return new object[] { 0x32000019U, 0, MidpointRounding.ToEven, 0x32800002U }; // 2.5 -> 2 (ToEven) + yield return new object[] { 0x32000019U, 0, MidpointRounding.AwayFromZero, 0x32800003U }; // 2.5 -> 3 (AwayFromZero) + yield return new object[] { 0x32000023U, 0, MidpointRounding.ToEven, 0x32800004U }; // 3.5 -> 4 (ToEven) + yield return new object[] { 0x32000005U, 0, MidpointRounding.ToEven, 0x32800000U }; // 0.5 -> 0 (ToEven) + yield return new object[] { 0x32000005U, 0, MidpointRounding.ToPositiveInfinity, 0x32800001U }; // 0.5 -> 1 (ToPositiveInfinity) + yield return new object[] { 0xB2000005U, 0, MidpointRounding.ToNegativeInfinity, 0xB2800001U }; // -0.5 -> -1 (ToNegativeInfinity) + yield return new object[] { 0xB1800019U, 0, MidpointRounding.ToPositiveInfinity, 0xB2800000U }; // -0.25 -> -0 (ToPositiveInfinity) + yield return new object[] { 0xB1800019U, 0, MidpointRounding.ToNegativeInfinity, 0xB2800001U }; // -0.25 -> -1 (ToNegativeInfinity) + yield return new object[] { 0x31800019U, 0, MidpointRounding.ToZero, 0x32800000U }; // 0.25 -> 0 (ToZero) + yield return new object[] { 0x32000005U, 5, MidpointRounding.ToEven, 0x32000005U }; // already finer than target, no-op + yield return new object[] { 0xB2000000U, 0, MidpointRounding.ToNegativeInfinity, 0xB2800000U }; // -0 stays -0 (ToNegativeInfinity) + yield return new object[] { 0x3101E240U, 2, MidpointRounding.ToEven, 0x3180303AU }; // 123.456 -> 123.46 (ToEven) + yield return new object[] { 0x2280020DU, 7, MidpointRounding.ToNegativeInfinity, 0x2F000000U }; + yield return new object[] { 0xD9800006U, 8, MidpointRounding.ToNegativeInfinity, 0xD9800006U }; + yield return new object[] { 0xB4001656U, 3, MidpointRounding.ToZero, 0xB4001656U }; + yield return new object[] { 0x9604DAA5U, 4, MidpointRounding.AwayFromZero, 0xB0800000U }; + yield return new object[] { 0x2880005AU, 8, MidpointRounding.ToZero, 0x2E800000U }; + yield return new object[] { 0xA0000037U, 5, MidpointRounding.ToEven, 0xB0000000U }; + yield return new object[] { 0xA8000000U, 3, MidpointRounding.ToPositiveInfinity, 0xB1000000U }; + yield return new object[] { 0x2A8B1C9BU, 6, MidpointRounding.ToNegativeInfinity, 0x2F800000U }; + yield return new object[] { 0x060006D9U, 2, MidpointRounding.ToEven, 0x31800000U }; + yield return new object[] { 0x9A000289U, 2, MidpointRounding.ToNegativeInfinity, 0xB1800001U }; + yield return new object[] { 0x1D80009CU, 2, MidpointRounding.ToZero, 0x31800000U }; + yield return new object[] { 0x5981B2C3U, 8, MidpointRounding.ToPositiveInfinity, 0x5981B2C3U }; + yield return new object[] { 0x188EEC20U, 2, MidpointRounding.ToPositiveInfinity, 0x31800001U }; + yield return new object[] { 0x5B01FC6AU, 0, MidpointRounding.ToNegativeInfinity, 0x5B01FC6AU }; + yield return new object[] { 0x57801F86U, 1, MidpointRounding.ToEven, 0x57801F86U }; + yield return new object[] { 0x28800000U, 1, MidpointRounding.ToNegativeInfinity, 0x32000000U }; + yield return new object[] { 0x5B01246AU, 7, MidpointRounding.AwayFromZero, 0x5B01246AU }; + yield return new object[] { 0x2A000000U, 3, MidpointRounding.ToNegativeInfinity, 0x31000000U }; + yield return new object[] { 0xAC800000U, 3, MidpointRounding.ToNegativeInfinity, 0xB1000000U }; + yield return new object[] { 0x26B74A57U, 0, MidpointRounding.ToZero, 0x32800000U }; + yield return new object[] { 0xFC000000U, 7, MidpointRounding.ToEven, 0xFC000000U }; + yield return new object[] { 0xDC800000U, 3, MidpointRounding.ToZero, 0xDC800000U }; + yield return new object[] { 0xAE000000U, 7, MidpointRounding.ToEven, 0xAF000000U }; + yield return new object[] { 0x39000000U, 8, MidpointRounding.ToNegativeInfinity, 0x39000000U }; + yield return new object[] { 0x82800000U, 5, MidpointRounding.ToEven, 0xB0000000U }; + yield return new object[] { 0x96800042U, 3, MidpointRounding.ToPositiveInfinity, 0xB1000000U }; + yield return new object[] { 0x4B8009DAU, 6, MidpointRounding.ToPositiveInfinity, 0x4B8009DAU }; + yield return new object[] { 0xB3800014U, 2, MidpointRounding.ToEven, 0xB3800014U }; + yield return new object[] { 0xA90015F5U, 4, MidpointRounding.ToNegativeInfinity, 0xB0800001U }; + yield return new object[] { 0x78000000U, 3, MidpointRounding.ToZero, 0x78000000U }; + yield return new object[] { 0xD4801DA3U, 5, MidpointRounding.ToEven, 0xD4801DA3U }; + yield return new object[] { 0x08016897U, 1, MidpointRounding.ToPositiveInfinity, 0x32000001U }; + yield return new object[] { 0xB5000000U, 0, MidpointRounding.ToZero, 0xB5000000U }; + yield return new object[] { 0xFC000000U, 2, MidpointRounding.ToNegativeInfinity, 0xFC000000U }; + yield return new object[] { 0xD02E0F8DU, 6, MidpointRounding.ToZero, 0xD02E0F8DU }; + yield return new object[] { 0x9980236DU, 6, MidpointRounding.AwayFromZero, 0xAF800000U }; + yield return new object[] { 0x3980E252U, 7, MidpointRounding.ToEven, 0x3980E252U }; + yield return new object[] { 0x9B8B73CFU, 6, MidpointRounding.ToEven, 0xAF800000U }; + yield return new object[] { 0x2E00034EU, 0, MidpointRounding.AwayFromZero, 0x32800000U }; + yield return new object[] { 0xAE8C7F3EU, 9, MidpointRounding.ToPositiveInfinity, 0xAE8C7F3EU }; + } + + [Theory] + [MemberData(nameof(RoundToDigits_TestData))] + public static void RoundToDigits(uint value, int digits, MidpointRounding mode, uint expected) + { + Decimal32 result = Decimal32.Round(Unsafe.BitCast(value), digits, mode); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Fact] + public static void RoundConvenienceOverloads() + { + Decimal32 x = Unsafe.BitCast(0x32000019U); // 2.5 + + Assert.Equal(Decimal32.Round(x, 0, MidpointRounding.ToPositiveInfinity), Decimal32.Ceiling(x)); + Assert.Equal(Decimal32.Round(x, 0, MidpointRounding.ToNegativeInfinity), Decimal32.Floor(x)); + Assert.Equal(Decimal32.Round(x, 0, MidpointRounding.ToZero), Decimal32.Truncate(x)); + Assert.Equal(Decimal32.Round(x, 0, MidpointRounding.ToEven), Decimal32.Round(x)); + Assert.Equal(Decimal32.Round(x, 0, MidpointRounding.AwayFromZero), Decimal32.Round(x, MidpointRounding.AwayFromZero)); + Assert.Equal(Decimal32.Round(x, 2, MidpointRounding.ToEven), Decimal32.Round(x, 2)); + } + + [Fact] + public static void IFloatingPoint_ExponentAndSignificand() + { + IFloatingPoint value = Unsafe.BitCast(0x31803039U); // 123.45 + + Assert.Equal(sizeof(int), value.GetExponentByteCount()); + Assert.Equal(sizeof(uint), value.GetSignificandByteCount()); + + Span exponent = stackalloc byte[value.GetExponentByteCount()]; + Assert.True(value.TryWriteExponentLittleEndian(exponent, out int exponentWritten)); + Assert.Equal(sizeof(int), exponentWritten); + Assert.Equal(-2, BinaryPrimitives.ReadInt32LittleEndian(exponent)); + + Span significand = stackalloc byte[value.GetSignificandByteCount()]; + Assert.True(value.TryWriteSignificandLittleEndian(significand, out int significandWritten)); + Assert.Equal(sizeof(uint), significandWritten); + Assert.Equal(12345u, BinaryPrimitives.ReadUInt32LittleEndian(significand)); + + Assert.Equal(123, Decimal32.ConvertToInteger(Unsafe.BitCast(0x31803039U))); + } + } } 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 d7772530c8730a..4fdbf92d715c2c 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,6 +1,7 @@ -// 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.Buffers.Binary; using System.Collections.Generic; using System.Globalization; using System.Numerics; @@ -2537,5 +2538,159 @@ public static void CreateToSystemDecimalTest() Assert.Throws(() => decimal.CreateChecked(Decimal64.MaxValue)); Assert.Throws(() => decimal.CreateChecked(Decimal64.NaN)); } + + public static IEnumerable op_Modulus_TestData() + { + yield return new object[] { 0x41A00000002DEBDBUL, 0x17C0000000000000UL, 0x7C00000000000000UL }; + yield return new object[] { 0xCA2000000000004FUL, 0x7800000000000000UL, 0xCA2000000000004FUL }; + yield return new object[] { 0xB6600000004150C1UL, 0xBCC0000000002448UL, 0xB6600000004150C1UL }; + yield return new object[] { 0x7800000000000000UL, 0xB1E00000000019C3UL, 0x7C00000000000000UL }; + yield return new object[] { 0x4960000000000000UL, 0x0DC0000000000024UL, 0x0DC0000000000000UL }; + yield return new object[] { 0x10EE600FD19029CBUL, 0xA6A000000094E674UL, 0x10EE600FD19029CBUL }; + yield return new object[] { 0x85CF1FFC772F327BUL, 0xBBA00000268EDEFFUL, 0x85CF1FFC772F327BUL }; + yield return new object[] { 0x48A0000000060DE1UL, 0xB5E01EFC5808BE55UL, 0x35E00BCDA6CEDA71UL }; + yield return new object[] { 0x2D80000000000012UL, 0xD6600000007B80C8UL, 0x2D80000000000012UL }; + yield return new object[] { 0xAFE0000000000000UL, 0x36E0000000000019UL, 0xAFE0000000000000UL }; + yield return new object[] { 0x33227424D5C43DDEUL, 0xCDE0029AD1B2EAB5UL, 0x33227424D5C43DDEUL }; + yield return new object[] { 0x81A0000000000000UL, 0xDFFE6402B104AB83UL, 0x81A0000000000000UL }; + yield return new object[] { 0xF800000000000000UL, 0xFC00000000000000UL, 0xFC00000000000000UL }; + yield return new object[] { 0x8A20000000001260UL, 0x18641BBD81D53E7DUL, 0x8A20000000001260UL }; + yield return new object[] { 0x8D000000000D7F91UL, 0xB960000141EB09DAUL, 0x8D000000000D7F91UL }; + yield return new object[] { 0xE6C19C4850147174UL, 0xB3000000000026E1UL, 0xE6C19C4850147174UL }; + yield return new object[] { 0xC2C000000000003AUL, 0x5FE003C84E63DFC8UL, 0xC2C000000000003AUL }; + yield return new object[] { 0x38C02B1AFA429C27UL, 0x5F800000000038C0UL, 0x38C02B1AFA429C27UL }; + yield return new object[] { 0xBA4000E2D684A0C5UL, 0xCFDEA1F334F310E9UL, 0xBA4000E2D684A0C5UL }; + yield return new object[] { 0x2900000000000000UL, 0x222000040FAA1C34UL, 0x2220000000000000UL }; + yield return new object[] { 0x1D400000006FF219UL, 0x52C00739A4B8C5B2UL, 0x1D400000006FF219UL }; + yield return new object[] { 0xA620000000046867UL, 0xD100000000000004UL, 0xA620000000046867UL }; + yield return new object[] { 0xD5A005E5E3DDA2EDUL, 0xFC00000000000000UL, 0xFC00000000000000UL }; + yield return new object[] { 0xA3400014152667BAUL, 0xCCE0000000000000UL, 0x7C00000000000000UL }; + yield return new object[] { 0x4140000000000000UL, 0x42C000000000001CUL, 0x4140000000000000UL }; + yield return new object[] { 0x07C0047B67EC371CUL, 0x2520000000000000UL, 0x7C00000000000000UL }; + yield return new object[] { 0x8EE2BC0886D8B658UL, 0x9E00000000000000UL, 0x7C00000000000000UL }; + yield return new object[] { 0x15A0000000000007UL, 0x80E0000000002291UL, 0x00E0000000000E1AUL }; + yield return new object[] { 0xC7E0000000000E7CUL, 0x54800000030D836EUL, 0xC7E0000000000E7CUL }; + yield return new object[] { 0x4BE031B4C864F3BEUL, 0xC920000000000007UL, 0x4920000000000000UL }; + yield return new object[] { 0x2BA0009305476749UL, 0x1800000000000294UL, 0x180000000000003CUL }; + yield return new object[] { 0xCB00207A9585137BUL, 0x95C0000000000000UL, 0x7C00000000000000UL }; + yield return new object[] { 0x9920000000000B0EUL, 0x1C40000003C50B28UL, 0x9920000000000B0EUL }; + yield return new object[] { 0x91A0000000000002UL, 0x524000000000003EUL, 0x91A0000000000002UL }; + yield return new object[] { 0x0100000000275140UL, 0xB2A008E9FA40DF3FUL, 0x0100000000275140UL }; + yield return new object[] { 0xBBE00002515A612BUL, 0xAFC0000000000351UL, 0xAFC000000000022CUL }; + yield return new object[] { 0x16E0000000000000UL, 0xAA800004CECAD583UL, 0x16E0000000000000UL }; + yield return new object[] { 0xD5400522C65A0499UL, 0x2CC0000000000017UL, 0xACC000000000000DUL }; + yield return new object[] { 0x0680000000000000UL, 0xEDE9769371960A39UL, 0x0680000000000000UL }; + yield return new object[] { 0x4580000000000000UL, 0x45600000000006C2UL, 0x4560000000000000UL }; + } + + [Theory] + [MemberData(nameof(op_Modulus_TestData))] + public static void op_Modulus(ulong left, ulong right, ulong expected) + { + Decimal64 result = Unsafe.BitCast(left) % Unsafe.BitCast(right); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + public static IEnumerable RoundToDigits_TestData() + { + yield return new object[] { 0xFC00000000000000UL, 0, MidpointRounding.ToEven, 0xFC00000000000000UL }; // NaN passes through + yield return new object[] { 0x7800000000000000UL, 3, MidpointRounding.AwayFromZero, 0x7800000000000000UL }; // +Inf passes through + yield return new object[] { 0xF800000000000000UL, 2, MidpointRounding.ToPositiveInfinity, 0xF800000000000000UL }; // -Inf passes through + yield return new object[] { 0x31A0000000000019UL, 0, MidpointRounding.ToEven, 0x31C0000000000002UL }; // 2.5 -> 2 (ToEven) + yield return new object[] { 0x31A0000000000019UL, 0, MidpointRounding.AwayFromZero, 0x31C0000000000003UL }; // 2.5 -> 3 (AwayFromZero) + yield return new object[] { 0x31A0000000000023UL, 0, MidpointRounding.ToEven, 0x31C0000000000004UL }; // 3.5 -> 4 (ToEven) + yield return new object[] { 0x31A0000000000005UL, 0, MidpointRounding.ToEven, 0x31C0000000000000UL }; // 0.5 -> 0 (ToEven) + yield return new object[] { 0x31A0000000000005UL, 0, MidpointRounding.ToPositiveInfinity, 0x31C0000000000001UL }; // 0.5 -> 1 (ToPositiveInfinity) + yield return new object[] { 0xB1A0000000000005UL, 0, MidpointRounding.ToNegativeInfinity, 0xB1C0000000000001UL }; // -0.5 -> -1 (ToNegativeInfinity) + yield return new object[] { 0xB180000000000019UL, 0, MidpointRounding.ToPositiveInfinity, 0xB1C0000000000000UL }; // -0.25 -> -0 (ToPositiveInfinity) + yield return new object[] { 0xB180000000000019UL, 0, MidpointRounding.ToNegativeInfinity, 0xB1C0000000000001UL }; // -0.25 -> -1 (ToNegativeInfinity) + yield return new object[] { 0x3180000000000019UL, 0, MidpointRounding.ToZero, 0x31C0000000000000UL }; // 0.25 -> 0 (ToZero) + yield return new object[] { 0x31A0000000000005UL, 5, MidpointRounding.ToEven, 0x31A0000000000005UL }; // already finer than target, no-op + yield return new object[] { 0xB1A0000000000000UL, 0, MidpointRounding.ToNegativeInfinity, 0xB1C0000000000000UL }; // -0 stays -0 (ToNegativeInfinity) + yield return new object[] { 0x316000000001E240UL, 2, MidpointRounding.ToEven, 0x318000000000303AUL }; // 123.456 -> 123.46 (ToEven) + yield return new object[] { 0x5164296458D07D2CUL, 4, MidpointRounding.ToZero, 0x5164296458D07D2CUL }; + yield return new object[] { 0x3840000000000000UL, 5, MidpointRounding.ToPositiveInfinity, 0x3840000000000000UL }; + yield return new object[] { 0xFC00000000000000UL, 12, MidpointRounding.ToNegativeInfinity, 0xFC00000000000000UL }; + yield return new object[] { 0xC820008FE40A9785UL, 11, MidpointRounding.ToZero, 0xC820008FE40A9785UL }; + yield return new object[] { 0x9640000000000000UL, 0, MidpointRounding.ToZero, 0xB1C0000000000000UL }; + yield return new object[] { 0xF800000000000000UL, 2, MidpointRounding.AwayFromZero, 0xF800000000000000UL }; + yield return new object[] { 0xD2200012C6ADE789UL, 15, MidpointRounding.AwayFromZero, 0xD2200012C6ADE789UL }; + yield return new object[] { 0xD4C0000000097DE0UL, 16, MidpointRounding.ToPositiveInfinity, 0xD4C0000000097DE0UL }; + yield return new object[] { 0x37C002682D7CA496UL, 9, MidpointRounding.ToZero, 0x37C002682D7CA496UL }; + yield return new object[] { 0xF800000000000000UL, 12, MidpointRounding.AwayFromZero, 0xF800000000000000UL }; + yield return new object[] { 0x3B0000002F8737D0UL, 5, MidpointRounding.ToZero, 0x3B0000002F8737D0UL }; + yield return new object[] { 0x7800000000000000UL, 12, MidpointRounding.ToZero, 0x7800000000000000UL }; + yield return new object[] { 0xA1A00000000BA750UL, 15, MidpointRounding.ToZero, 0xAFE0000000000000UL }; + yield return new object[] { 0xA380118354CB3560UL, 6, MidpointRounding.ToPositiveInfinity, 0xB100000000000000UL }; + yield return new object[] { 0x51C000118D08CAA9UL, 7, MidpointRounding.ToZero, 0x51C000118D08CAA9UL }; + yield return new object[] { 0x0EE00000000923B9UL, 15, MidpointRounding.ToPositiveInfinity, 0x2FE0000000000001UL }; + yield return new object[] { 0x99C0000000000105UL, 18, MidpointRounding.ToPositiveInfinity, 0xAF80000000000000UL }; + yield return new object[] { 0x13A0000000A6B74AUL, 16, MidpointRounding.ToEven, 0x2FC0000000000000UL }; + yield return new object[] { 0x4D200010544A7EF9UL, 6, MidpointRounding.ToZero, 0x4D200010544A7EF9UL }; + yield return new object[] { 0xAFE129C247897D17UL, 15, MidpointRounding.ToZero, 0xAFE129C247897D17UL }; + yield return new object[] { 0x39E0000018DBDAC4UL, 18, MidpointRounding.ToZero, 0x39E0000018DBDAC4UL }; + yield return new object[] { 0x1CE0466A2E2FA67DUL, 1, MidpointRounding.ToEven, 0x31A0000000000000UL }; + yield return new object[] { 0xD6A000000005F3B6UL, 9, MidpointRounding.AwayFromZero, 0xD6A000000005F3B6UL }; + yield return new object[] { 0x5980000001992B99UL, 9, MidpointRounding.ToNegativeInfinity, 0x5980000001992B99UL }; + yield return new object[] { 0x98801012060B07C9UL, 7, MidpointRounding.ToNegativeInfinity, 0xB0E0000000000001UL }; + yield return new object[] { 0xDFE00000330EA053UL, 1, MidpointRounding.ToPositiveInfinity, 0xDFE00000330EA053UL }; + yield return new object[] { 0x5800000000000000UL, 15, MidpointRounding.ToNegativeInfinity, 0x5800000000000000UL }; + yield return new object[] { 0xA1C0000000000000UL, 9, MidpointRounding.ToNegativeInfinity, 0xB0A0000000000000UL }; + yield return new object[] { 0x31A000F013649DACUL, 10, MidpointRounding.ToZero, 0x31A000F013649DACUL }; + yield return new object[] { 0x59000039FBCD6B26UL, 11, MidpointRounding.ToPositiveInfinity, 0x59000039FBCD6B26UL }; + yield return new object[] { 0xDF200000000000BFUL, 5, MidpointRounding.ToZero, 0xDF200000000000BFUL }; + yield return new object[] { 0xB8600323BC8172A8UL, 11, MidpointRounding.ToZero, 0xB8600323BC8172A8UL }; + yield return new object[] { 0x0E633EA0E618A8C5UL, 1, MidpointRounding.ToZero, 0x31A0000000000000UL }; + yield return new object[] { 0xDF00007875FC51AAUL, 5, MidpointRounding.AwayFromZero, 0xDF00007875FC51AAUL }; + yield return new object[] { 0xBF200000033876D7UL, 5, MidpointRounding.ToPositiveInfinity, 0xBF200000033876D7UL }; + yield return new object[] { 0x8A6000000000282FUL, 13, MidpointRounding.ToZero, 0xB020000000000000UL }; + yield return new object[] { 0x0FA022B13F5EAC78UL, 0, MidpointRounding.ToPositiveInfinity, 0x31C0000000000001UL }; + yield return new object[] { 0xC840000000000000UL, 18, MidpointRounding.ToPositiveInfinity, 0xC840000000000000UL }; + yield return new object[] { 0x00C00006906B8E09UL, 0, MidpointRounding.ToNegativeInfinity, 0x31C0000000000000UL }; + yield return new object[] { 0x3500000000574DB9UL, 5, MidpointRounding.ToEven, 0x3500000000574DB9UL }; + } + + [Theory] + [MemberData(nameof(RoundToDigits_TestData))] + public static void RoundToDigits(ulong value, int digits, MidpointRounding mode, ulong expected) + { + Decimal64 result = Decimal64.Round(Unsafe.BitCast(value), digits, mode); + Assert.Equal(expected, Unsafe.BitCast(result)); + } + + [Fact] + public static void RoundConvenienceOverloads() + { + Decimal64 x = Unsafe.BitCast(0x31A0000000000019UL); // 2.5 + + Assert.Equal(Decimal64.Round(x, 0, MidpointRounding.ToPositiveInfinity), Decimal64.Ceiling(x)); + Assert.Equal(Decimal64.Round(x, 0, MidpointRounding.ToNegativeInfinity), Decimal64.Floor(x)); + Assert.Equal(Decimal64.Round(x, 0, MidpointRounding.ToZero), Decimal64.Truncate(x)); + Assert.Equal(Decimal64.Round(x, 0, MidpointRounding.ToEven), Decimal64.Round(x)); + Assert.Equal(Decimal64.Round(x, 0, MidpointRounding.AwayFromZero), Decimal64.Round(x, MidpointRounding.AwayFromZero)); + Assert.Equal(Decimal64.Round(x, 2, MidpointRounding.ToEven), Decimal64.Round(x, 2)); + } + + [Fact] + public static void IFloatingPoint_ExponentAndSignificand() + { + IFloatingPoint value = Unsafe.BitCast(0x3180000000003039UL); // 123.45 + + Assert.Equal(sizeof(int), value.GetExponentByteCount()); + Assert.Equal(sizeof(ulong), value.GetSignificandByteCount()); + + Span exponent = stackalloc byte[value.GetExponentByteCount()]; + Assert.True(value.TryWriteExponentLittleEndian(exponent, out int exponentWritten)); + Assert.Equal(sizeof(int), exponentWritten); + Assert.Equal(-2, BinaryPrimitives.ReadInt32LittleEndian(exponent)); + + Span significand = stackalloc byte[value.GetSignificandByteCount()]; + Assert.True(value.TryWriteSignificandLittleEndian(significand, out int significandWritten)); + Assert.Equal(sizeof(ulong), significandWritten); + Assert.Equal(12345ul, BinaryPrimitives.ReadUInt64LittleEndian(significand)); + + Assert.Equal(123, Decimal64.ConvertToInteger(Unsafe.BitCast(0x3180000000003039UL))); + } + } } From 00bad88d02502f8d148afef116302e120703cd26 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 01:41:44 -0700 Subject: [PATCH 07/14] Cross-validate the decimal modulus and round operators against the Intel reference vectors Wires bidNN_fmod and bidNN_round_integral_* from the Intel readtest.in vectors into the existing DecimalIeee754IntelTestData harness. The round vectors surfaced two canonicalization gaps in RoundDecimalIeee754 (non-canonical finite and infinity operands were returned verbatim), now fixed in the implementation. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System/Decimal128Tests.cs | 30 +++++++ .../System/Decimal32Tests.cs | 30 +++++++ .../System/Decimal64Tests.cs | 30 +++++++ .../System/DecimalIeee754IntelTestData.cs | 80 +++++++++++++++++++ 4 files changed, 170 insertions(+) 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 dd66f605535a34..8c2c1bde22bd66 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 @@ -1549,6 +1549,16 @@ public static void op_Arithmetic_IntelReferenceVectors(string operation, UInt128 Assert.Equal(expected, Unsafe.BitCast(result)); } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128Modulus), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void op_Modulus_IntelReferenceVectors(UInt128 left, UInt128 right, UInt128 expected) + { + Decimal128 l = Unsafe.BitCast(left); + Decimal128 r = Unsafe.BitCast(right); + + Assert.Equal(expected, Unsafe.BitCast(l % r)); + } + [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) @@ -1586,6 +1596,26 @@ public static void UnaryOperation_IntelReferenceVectors(string operation, UInt12 Assert.Equal(expected, Unsafe.BitCast(result)); } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128RoundIntegral), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void RoundIntegral_IntelReferenceVectors(string operation, UInt128 value, UInt128 expected) + { + Decimal128 v = Unsafe.BitCast(value); + + Decimal128 result = operation switch + { + "round_integral_exact" => Decimal128.Round(v, 0, MidpointRounding.ToEven), + "round_integral_nearest_even" => Decimal128.Round(v, 0, MidpointRounding.ToEven), + "round_integral_nearest_away" => Decimal128.Round(v, 0, MidpointRounding.AwayFromZero), + "round_integral_negative" => Decimal128.Floor(v), + "round_integral_positive" => Decimal128.Ceiling(v), + "round_integral_zero" => Decimal128.Truncate(v), + _ => throw new InvalidOperationException($"Unexpected operation '{operation}'."), + }; + + Assert.Equal(expected, Unsafe.BitCast(result)); + } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] [MemberData(nameof(DecimalIeee754IntelTestData.Decimal128BinaryValue), MemberType = typeof(DecimalIeee754IntelTestData))] public static void BinaryValueOperation_IntelReferenceVectors(string operation, UInt128 left, UInt128 right, UInt128 expected) 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 a2808494feea0c..6c79056b73922f 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 @@ -1549,6 +1549,16 @@ public static void op_Arithmetic_IntelReferenceVectors(string operation, uint le Assert.Equal(expected, Unsafe.BitCast(result)); } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32Modulus), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void op_Modulus_IntelReferenceVectors(uint left, uint right, uint expected) + { + Decimal32 l = Unsafe.BitCast(left); + Decimal32 r = Unsafe.BitCast(right); + + Assert.Equal(expected, Unsafe.BitCast(l % r)); + } + [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) @@ -1586,6 +1596,26 @@ public static void UnaryOperation_IntelReferenceVectors(string operation, uint v Assert.Equal(expected, Unsafe.BitCast(result)); } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32RoundIntegral), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void RoundIntegral_IntelReferenceVectors(string operation, uint value, uint expected) + { + Decimal32 v = Unsafe.BitCast(value); + + Decimal32 result = operation switch + { + "round_integral_exact" => Decimal32.Round(v, 0, MidpointRounding.ToEven), + "round_integral_nearest_even" => Decimal32.Round(v, 0, MidpointRounding.ToEven), + "round_integral_nearest_away" => Decimal32.Round(v, 0, MidpointRounding.AwayFromZero), + "round_integral_negative" => Decimal32.Floor(v), + "round_integral_positive" => Decimal32.Ceiling(v), + "round_integral_zero" => Decimal32.Truncate(v), + _ => throw new InvalidOperationException($"Unexpected operation '{operation}'."), + }; + + Assert.Equal(expected, Unsafe.BitCast(result)); + } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] [MemberData(nameof(DecimalIeee754IntelTestData.Decimal32BinaryValue), MemberType = typeof(DecimalIeee754IntelTestData))] public static void BinaryValueOperation_IntelReferenceVectors(string operation, uint left, uint right, uint expected) 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 4fdbf92d715c2c..73aafe90337509 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 @@ -1555,6 +1555,16 @@ public static void op_Arithmetic_IntelReferenceVectors(string operation, ulong l Assert.Equal(expected, Unsafe.BitCast(result)); } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64Modulus), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void op_Modulus_IntelReferenceVectors(ulong left, ulong right, ulong expected) + { + Decimal64 l = Unsafe.BitCast(left); + Decimal64 r = Unsafe.BitCast(right); + + Assert.Equal(expected, Unsafe.BitCast(l % r)); + } + [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) @@ -1592,6 +1602,26 @@ public static void UnaryOperation_IntelReferenceVectors(string operation, ulong Assert.Equal(expected, Unsafe.BitCast(result)); } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] + [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64RoundIntegral), MemberType = typeof(DecimalIeee754IntelTestData))] + public static void RoundIntegral_IntelReferenceVectors(string operation, ulong value, ulong expected) + { + Decimal64 v = Unsafe.BitCast(value); + + Decimal64 result = operation switch + { + "round_integral_exact" => Decimal64.Round(v, 0, MidpointRounding.ToEven), + "round_integral_nearest_even" => Decimal64.Round(v, 0, MidpointRounding.ToEven), + "round_integral_nearest_away" => Decimal64.Round(v, 0, MidpointRounding.AwayFromZero), + "round_integral_negative" => Decimal64.Floor(v), + "round_integral_positive" => Decimal64.Ceiling(v), + "round_integral_zero" => Decimal64.Truncate(v), + _ => throw new InvalidOperationException($"Unexpected operation '{operation}'."), + }; + + Assert.Equal(expected, Unsafe.BitCast(result)); + } + [ConditionalTheory(typeof(DecimalIeee754IntelTestData), nameof(DecimalIeee754IntelTestData.IsAvailable))] [MemberData(nameof(DecimalIeee754IntelTestData.Decimal64BinaryValue), MemberType = typeof(DecimalIeee754IntelTestData))] public static void BinaryValueOperation_IntelReferenceVectors(string operation, ulong left, ulong right, ulong expected) 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 index b09102848f230f..155a71063afe89 100644 --- a/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs +++ b/src/libraries/System.Runtime/tests/System.Runtime.Tests/System/DecimalIeee754IntelTestData.cs @@ -95,6 +95,18 @@ public static class DecimalIeee754IntelTestData private static readonly HashSet s_bid64Cross = new() { "bid32_to_bid64", "bid128_to_bid64" }; private static readonly HashSet s_bid128Cross = new() { "bid32_to_bid128", "bid64_to_bid128" }; + // Truncating remainder (fmod, the C# `%` operator), not the round-to-nearest IEEE 754 remainder. + private static readonly HashSet s_bid32Modulus = new() { "bid32_fmod" }; + private static readonly HashSet s_bid64Modulus = new() { "bid64_fmod" }; + private static readonly HashSet s_bid128Modulus = new() { "bid128_fmod" }; + + // Round to an integral value under each rounding mode, mapping onto the .NET Round/Ceiling/Floor/Truncate + // surface. `round_integral_exact` takes the mode from the rounding-context column, so only its + // round-to-nearest-even (rnd == 0) rows are consumed here; the mode-named variants ignore that column. + private static readonly HashSet s_bid32RoundIntegral = new() { "bid32_round_integral_exact", "bid32_round_integral_nearest_even", "bid32_round_integral_nearest_away", "bid32_round_integral_negative", "bid32_round_integral_positive", "bid32_round_integral_zero" }; + private static readonly HashSet s_bid64RoundIntegral = new() { "bid64_round_integral_exact", "bid64_round_integral_nearest_even", "bid64_round_integral_nearest_away", "bid64_round_integral_negative", "bid64_round_integral_positive", "bid64_round_integral_zero" }; + private static readonly HashSet s_bid128RoundIntegral = new() { "bid128_round_integral_exact", "bid128_round_integral_nearest_even", "bid128_round_integral_nearest_away", "bid128_round_integral_negative", "bid128_round_integral_positive", "bid128_round_integral_zero" }; + /// /// Gets a value indicating whether the Intel readtest.in reference vectors are available, /// gating the theories that consume them. @@ -431,6 +443,74 @@ public static IEnumerable Decimal128Cross() } } + public static IEnumerable Decimal32Modulus() + { + foreach (string[] fields in EnumerateRows(s_bid32Modulus)) + { + if (TryParseBid32(fields[2], out uint left) && TryParseBid32(fields[3], out uint right) && TryParseBid32(fields[4], out uint expected)) + { + yield return new object[] { left, right, expected }; + } + } + } + + public static IEnumerable Decimal64Modulus() + { + foreach (string[] fields in EnumerateRows(s_bid64Modulus)) + { + if (TryParseBid64(fields[2], out ulong left) && TryParseBid64(fields[3], out ulong right) && TryParseBid64(fields[4], out ulong expected)) + { + yield return new object[] { left, right, expected }; + } + } + } + + public static IEnumerable Decimal128Modulus() + { + foreach (string[] fields in EnumerateRows(s_bid128Modulus)) + { + if (TryParseBid128(fields[2], out UInt128 left) && TryParseBid128(fields[3], out UInt128 right) && TryParseBid128(fields[4], out UInt128 expected)) + { + yield return new object[] { left, right, expected }; + } + } + } + + // NaN operands are skipped: rounding leaves the value's payload untouched, but Intel canonicalizes and quiets + // NaNs so its result column would not match the raw operand bits. The mode is taken from the operation name. + public static IEnumerable Decimal32RoundIntegral() + { + foreach (string[] fields in EnumerateRows(s_bid32RoundIntegral)) + { + if (TryParseBid32(fields[2], out uint value) && !IsBid32NaN(value) && TryParseBid32(fields[3], out uint expected)) + { + yield return new object[] { OperationSuffix(fields[0]), value, expected }; + } + } + } + + public static IEnumerable Decimal64RoundIntegral() + { + foreach (string[] fields in EnumerateRows(s_bid64RoundIntegral)) + { + if (TryParseBid64(fields[2], out ulong value) && !IsBid64NaN(value) && TryParseBid64(fields[3], out ulong expected)) + { + yield return new object[] { OperationSuffix(fields[0]), value, expected }; + } + } + } + + public static IEnumerable Decimal128RoundIntegral() + { + foreach (string[] fields in EnumerateRows(s_bid128RoundIntegral)) + { + if (TryParseBid128(fields[2], out UInt128 value) && !IsBid128NaN(value) && TryParseBid128(fields[3], out UInt128 expected)) + { + yield return new object[] { OperationSuffix(fields[0]), value, expected }; + } + } + } + // For `bidNN_from_` the integer source type is the trailing token; for `bidNN_to__int` it is the // third underscore-separated token; for the binary and cross families it is the leading or third token. private static string IntegerSourceType(string operation) => operation.Substring(operation.LastIndexOf('_') + 1); From f4b9926848cf9cc22dea261d83006103dcf2db8d Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 10:57:31 -0700 Subject: [PATCH 08/14] Fold multiple digits per step in the decimal remainder reduction The reduction loop scaled the running remainder one decimal digit at a time, so a large exponent gap spun far more iterations than necessary and kept going after the remainder reached zero. Fold as many trailing zeros per step as the running product can hold without overflowing the integer width (up to the largest cached power of ten) and stop once the remainder is zero. A small divisor now reaches the full cached power; output is bit-exact-unchanged against the Intel reference vectors. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 29 ++++++++++++------- 1 file changed, 19 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 fc5077c940f95d..0286886ebb589e 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -1030,7 +1030,7 @@ internal static TValue DivideDecimalIeee754(TValue left, TValu /// internal static TValue RemainderDecimalIeee754(TValue left, TValue right) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo - where TValue : unmanaged, IBinaryInteger + where TValue : unmanaged, IBinaryInteger, IMinMaxValue { // This code is based on `bid32_fmod`, `bid64_fmod`, and `bid128_fmod` from Intel(R) Decimal Floating-Point Math Library // Copyright (c) 2007-2025, Intel Corp. All rights reserved. @@ -1077,23 +1077,32 @@ internal static TValue RemainderDecimalIeee754(TValue left, TV return DecimalIeee754FiniteNumberBinaryEncoding(resultSign, TValue.Zero, resultExponent); } - TValue ten = TValue.CreateTruncating(10); TValue remainder; if (a.UnbiasedExponent >= b.UnbiasedExponent) { - // Both coefficients fit a single limb, so reduce `a.Significand * 10^(ea - eb)` modulo `b.Significand` - // by scaling the running remainder up one decimal digit at a time. The remainder stays below the - // divisor, so `remainder * 10` never exceeds the integer width. + // Reduce `a.Significand * 10^(ea - eb)` modulo `b.Significand`. The remainder always stays below the + // divisor, so `remainder * 10^chunk` fits the integer width as long as `10^chunk <= MaxValue / divisor`. + // Fold that many trailing zeros per step (capped at the largest cached power of ten). Modular + // arithmetic lets each step absorb several digits at once, so a small divisor reaches the full cached + // power while a large one folds only a handful. Once the remainder hits zero it stays zero, so stop. // - // TODO: This scales one decimal digit per iteration, where-as Intel's `bidNN_fmod` scales the dividend - // by up to `Precision` digits per iteration. Porting that chunked reduction would cut the loop count - // for large exponent gaps at the cost of a wider intermediate for Decimal128. + // TODO: A wider intermediate (as Intel's `bidNN_fmod` uses for Decimal128) would let every step fold + // the full `Precision` digits regardless of divisor magnitude, shaving the loop count for large gaps. remainder = a.Significand % b.Significand; - for (int i = 0; i < a.UnbiasedExponent - b.UnbiasedExponent; i++) + int chunk = 1; + TValue chunkLimit = TValue.MaxValue / b.Significand; + + while ((chunk < TDecimal.Precision - 1) && (TDecimal.Power10(chunk + 1) <= chunkLimit)) + { + chunk++; + } + + for (int gap = a.UnbiasedExponent - b.UnbiasedExponent; (gap > 0) && !TValue.IsZero(remainder); gap -= chunk) { - remainder = (remainder * ten) % b.Significand; + int step = Math.Min(chunk, gap); + remainder = (remainder * TDecimal.Power10(step)) % b.Significand; } } else From 0da9c089e02b19db6537dd42f4816ed63aa9fe1b Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 11:39:52 -0700 Subject: [PATCH 09/14] Remove the erroneously introduced byte order marks from the decimal test files Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../tests/System.Runtime.Tests/System/Decimal128Tests.cs | 2 +- .../tests/System.Runtime.Tests/System/Decimal32Tests.cs | 2 +- .../tests/System.Runtime.Tests/System/Decimal64Tests.cs | 2 +- 3 files changed, 3 insertions(+), 3 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 8c2c1bde22bd66..9ef5de2e2b3605 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.Buffers.Binary; 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 6c79056b73922f..64b5d0c4cd07e5 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.Buffers.Binary; 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 73aafe90337509..121b37b7ab5413 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.Buffers.Binary; From cb50b9f2e4eee12f14a8334d3ff1153a6a012fa3 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 11:52:09 -0700 Subject: [PATCH 10/14] Add exponent and significand bit-length test coverage for the IEEE 754 decimal types Covers GetExponentShortestBitLength (both exponent-sign branches), GetSignificandBitLength, and the big-endian significand and exponent writers. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../System/Decimal128Tests.cs | 17 +++++++++++++++++ .../System/Decimal32Tests.cs | 17 +++++++++++++++++ .../System/Decimal64Tests.cs | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) 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 9ef5de2e2b3605..ab29588d3a4dad 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 @@ -2713,6 +2713,23 @@ public static void IFloatingPoint_ExponentAndSignificand() Assert.Equal(Unsafe.SizeOf(), significandWritten); Assert.Equal((UInt128)12345, BinaryPrimitives.ReadUInt128LittleEndian(significand)); + Assert.Equal(2, value.GetExponentShortestBitLength()); + Assert.Equal(113, value.GetSignificandBitLength()); + + Span exponentBigEndian = stackalloc byte[value.GetExponentByteCount()]; + Assert.True(value.TryWriteExponentBigEndian(exponentBigEndian, out exponentWritten)); + Assert.Equal(sizeof(int), exponentWritten); + Assert.Equal(-2, BinaryPrimitives.ReadInt32BigEndian(exponentBigEndian)); + + Span significandBigEndian = stackalloc byte[value.GetSignificandByteCount()]; + Assert.True(value.TryWriteSignificandBigEndian(significandBigEndian, out significandWritten)); + Assert.Equal(Unsafe.SizeOf(), significandWritten); + Assert.Equal((UInt128)12345, BinaryPrimitives.ReadUInt128BigEndian(significandBigEndian)); + + // A non-negative exponent exercises the other GetExponentShortestBitLength branch. + IFloatingPoint integer = Unsafe.BitCast(new UInt128(0x3040000000000000, 0x0000000000003039)); // 12345 + Assert.Equal(0, integer.GetExponentShortestBitLength()); + Assert.Equal(123, Decimal128.ConvertToInteger(Unsafe.BitCast(new UInt128(0x303C000000000000, 0x0000000000003039)))); } 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 64b5d0c4cd07e5..2b383c76a6f1f1 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 @@ -2713,6 +2713,23 @@ public static void IFloatingPoint_ExponentAndSignificand() Assert.Equal(sizeof(uint), significandWritten); Assert.Equal(12345u, BinaryPrimitives.ReadUInt32LittleEndian(significand)); + Assert.Equal(2, value.GetExponentShortestBitLength()); + Assert.Equal(24, value.GetSignificandBitLength()); + + Span exponentBigEndian = stackalloc byte[value.GetExponentByteCount()]; + Assert.True(value.TryWriteExponentBigEndian(exponentBigEndian, out exponentWritten)); + Assert.Equal(sizeof(int), exponentWritten); + Assert.Equal(-2, BinaryPrimitives.ReadInt32BigEndian(exponentBigEndian)); + + Span significandBigEndian = stackalloc byte[value.GetSignificandByteCount()]; + Assert.True(value.TryWriteSignificandBigEndian(significandBigEndian, out significandWritten)); + Assert.Equal(sizeof(uint), significandWritten); + Assert.Equal(12345u, BinaryPrimitives.ReadUInt32BigEndian(significandBigEndian)); + + // A non-negative exponent exercises the other GetExponentShortestBitLength branch. + IFloatingPoint integer = Unsafe.BitCast(0x32803039U); // 12345 + Assert.Equal(0, integer.GetExponentShortestBitLength()); + Assert.Equal(123, Decimal32.ConvertToInteger(Unsafe.BitCast(0x31803039U))); } 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 121b37b7ab5413..f9f75e41df0f4f 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 @@ -2719,6 +2719,23 @@ public static void IFloatingPoint_ExponentAndSignificand() Assert.Equal(sizeof(ulong), significandWritten); Assert.Equal(12345ul, BinaryPrimitives.ReadUInt64LittleEndian(significand)); + Assert.Equal(2, value.GetExponentShortestBitLength()); + Assert.Equal(54, value.GetSignificandBitLength()); + + Span exponentBigEndian = stackalloc byte[value.GetExponentByteCount()]; + Assert.True(value.TryWriteExponentBigEndian(exponentBigEndian, out exponentWritten)); + Assert.Equal(sizeof(int), exponentWritten); + Assert.Equal(-2, BinaryPrimitives.ReadInt32BigEndian(exponentBigEndian)); + + Span significandBigEndian = stackalloc byte[value.GetSignificandByteCount()]; + Assert.True(value.TryWriteSignificandBigEndian(significandBigEndian, out significandWritten)); + Assert.Equal(sizeof(ulong), significandWritten); + Assert.Equal(12345ul, BinaryPrimitives.ReadUInt64BigEndian(significandBigEndian)); + + // A non-negative exponent exercises the other GetExponentShortestBitLength branch. + IFloatingPoint integer = Unsafe.BitCast(0x31C0000000003039UL); // 12345 + Assert.Equal(0, integer.GetExponentShortestBitLength()); + Assert.Equal(123, Decimal64.ConvertToInteger(Unsafe.BitCast(0x3180000000003039UL))); } From 7b0173801f98c940c72da5150d1ffe842fb9a6b4 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 11:52:09 -0700 Subject: [PATCH 11/14] Update the decimal remainder remarks to match the chunked reduction Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 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 0286886ebb589e..66b8756d82ad62 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -1025,8 +1025,10 @@ internal static TValue DivideDecimalIeee754(TValue left, TValu /// The result carries the sign of the dividend, has magnitude strictly less than |y|, and is always /// exact. It is computed at the IEEE 754 preferred exponent min(exp(x), exp(y)) by reducing the /// dividend coefficient modulo the divisor coefficient. Every intermediate value stays within a single limb: - /// the running remainder is always below the divisor, so scaling it up one decimal digit at a time and taking - /// the remainder never overflows. This mirrors the special-case handling of the Intel reference implementation. + /// the running remainder is always below the divisor, so each step scales it up by as many trailing zeros as + /// keep the product within the integer width (capped at the largest cached power of ten) before taking the + /// remainder again, stopping once the remainder reaches zero. This mirrors the behavior of the Intel reference + /// implementation. /// internal static TValue RemainderDecimalIeee754(TValue left, TValue right) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo From 8d226f2488c6df33ec904c2d6daed30e69a732a0 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 12:20:22 -0700 Subject: [PATCH 12/14] Canonicalize the NaN operand when rounding the IEEE 754 decimal types Ceiling/Floor/Round/Truncate propagated a NaN operand's bits unchanged, so a signaling or out-of-range-payload NaN escaped as a non-canonical result. Route it through PropagateNaN, matching the binary operators. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 3 ++- .../tests/System.Runtime.Tests/System/Decimal128Tests.cs | 5 ++++- .../tests/System.Runtime.Tests/System/Decimal32Tests.cs | 5 ++++- .../tests/System.Runtime.Tests/System/Decimal64Tests.cs | 5 ++++- 4 files changed, 14 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 66b8756d82ad62..79f30a8c910d95 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -1150,7 +1150,8 @@ internal static TValue RoundDecimalIeee754(TValue bits, int di if (TDecimal.IsNaN(bits)) { - return bits; + // Canonicalize so a signaling or out-of-range-payload NaN operand rounds to the canonical quiet NaN. + return PropagateNaN(bits, bits); } if (TDecimal.IsInfinity(bits)) 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 ab29588d3a4dad..078381c31650a3 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 @@ -2617,7 +2617,10 @@ public static void op_Modulus(UInt128 left, UInt128 right, UInt128 expected) public static IEnumerable RoundToDigits_TestData() { - yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), 0, MidpointRounding.ToEven, new UInt128(0xFC00000000000000, 0x0000000000000000) }; // NaN passes through + yield return new object[] { new UInt128(0xFC00000000000000, 0x0000000000000000), 0, MidpointRounding.ToEven, new UInt128(0xFC00000000000000, 0x0000000000000000) }; // canonical NaN passes through + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), 0, MidpointRounding.ToEven, new UInt128(0x7C00000000000000, 0x0000000000001234) }; // signaling NaN -> quiet NaN (payload preserved) + yield return new object[] { new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), 3, MidpointRounding.ToZero, new UInt128(0x7C00000000000000, 0x0000000000000000) }; // out-of-range NaN payload cleared + yield return new object[] { new UInt128(0xFE00000000000000, 0x0000000000000000), 2, MidpointRounding.ToNegativeInfinity, new UInt128(0xFC00000000000000, 0x0000000000000000) }; // negative signaling NaN -> quiet NaN (sign preserved) yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), 3, MidpointRounding.AwayFromZero, new UInt128(0x7800000000000000, 0x0000000000000000) }; // +Inf passes through yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), 2, MidpointRounding.ToPositiveInfinity, new UInt128(0xF800000000000000, 0x0000000000000000) }; // -Inf passes through yield return new object[] { new UInt128(0x303E000000000000, 0x0000000000000019), 0, MidpointRounding.ToEven, new UInt128(0x3040000000000000, 0x0000000000000002) }; // 2.5 -> 2 (ToEven) 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 2b383c76a6f1f1..34fd85b541abdd 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 @@ -2617,7 +2617,10 @@ public static void op_Modulus(uint left, uint right, uint expected) public static IEnumerable RoundToDigits_TestData() { - yield return new object[] { 0xFC000000U, 0, MidpointRounding.ToEven, 0xFC000000U }; // NaN passes through + yield return new object[] { 0xFC000000U, 0, MidpointRounding.ToEven, 0xFC000000U }; // canonical NaN passes through + yield return new object[] { 0x7E001234U, 0, MidpointRounding.ToEven, 0x7C001234U }; // signaling NaN -> quiet NaN (payload preserved) + yield return new object[] { 0x7C0FFFFFU, 3, MidpointRounding.ToZero, 0x7C000000U }; // out-of-range NaN payload cleared + yield return new object[] { 0xFE000000U, 2, MidpointRounding.ToNegativeInfinity, 0xFC000000U }; // negative signaling NaN -> quiet NaN (sign preserved) yield return new object[] { 0x78000000U, 3, MidpointRounding.AwayFromZero, 0x78000000U }; // +Inf passes through yield return new object[] { 0xF8000000U, 2, MidpointRounding.ToPositiveInfinity, 0xF8000000U }; // -Inf passes through yield return new object[] { 0x32000019U, 0, MidpointRounding.ToEven, 0x32800002U }; // 2.5 -> 2 (ToEven) 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 f9f75e41df0f4f..e609f31e565f20 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 @@ -2623,7 +2623,10 @@ public static void op_Modulus(ulong left, ulong right, ulong expected) public static IEnumerable RoundToDigits_TestData() { - yield return new object[] { 0xFC00000000000000UL, 0, MidpointRounding.ToEven, 0xFC00000000000000UL }; // NaN passes through + yield return new object[] { 0xFC00000000000000UL, 0, MidpointRounding.ToEven, 0xFC00000000000000UL }; // canonical NaN passes through + yield return new object[] { 0x7E00000000001234UL, 0, MidpointRounding.ToEven, 0x7C00000000001234UL }; // signaling NaN -> quiet NaN (payload preserved) + yield return new object[] { 0x7C03FFFFFFFFFFFFUL, 3, MidpointRounding.ToZero, 0x7C00000000000000UL }; // out-of-range NaN payload cleared + yield return new object[] { 0xFE00000000000000UL, 2, MidpointRounding.ToNegativeInfinity, 0xFC00000000000000UL }; // negative signaling NaN -> quiet NaN (sign preserved) yield return new object[] { 0x7800000000000000UL, 3, MidpointRounding.AwayFromZero, 0x7800000000000000UL }; // +Inf passes through yield return new object[] { 0xF800000000000000UL, 2, MidpointRounding.ToPositiveInfinity, 0xF800000000000000UL }; // -Inf passes through yield return new object[] { 0x31A0000000000019UL, 0, MidpointRounding.ToEven, 0x31C0000000000002UL }; // 2.5 -> 2 (ToEven) From 94d0c69879342280577668cb6024701f6fb96e91 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 13:07:29 -0700 Subject: [PATCH 13/14] Use DivRem when splitting the rounded coefficient Replace the manual divide and multiply-subtract with TValue.DivRem, matching the remainder computation used elsewhere in the file. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 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 79f30a8c910d95..f0ef5d9c45b2ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -1201,9 +1201,9 @@ internal static TValue RoundDecimalIeee754(TValue bits, int di else { TValue divisor = TDecimal.Power10(drop); - quotient = a.Significand / divisor; + TValue discarded; + (quotient, discarded) = TValue.DivRem(a.Significand, divisor); - TValue discarded = a.Significand - (quotient * divisor); TValue half = five * TDecimal.Power10(drop - 1); discardedComparedToHalf = discarded.CompareTo(half); discardedNonZero = !TValue.IsZero(discarded); From afc13a30e4002b0f8bd127ca7741b0eb3b3b0b83 Mon Sep 17 00:00:00 2001 From: Tanner Gooding Date: Thu, 16 Jul 2026 13:25:55 -0700 Subject: [PATCH 14/14] Canonicalize the NaN result of the IEEE 754 decimal minimum/maximum family IEEE 754-2019 5.1 requires general-computational operations to produce canonical results, so a returned NaN operand must be re-canonicalized. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> --- .../src/System/Number.DecimalIeee754.cs | 40 ++++++++++++------- .../System/Decimal128Tests.cs | 20 ++++++++++ .../System/Decimal32Tests.cs | 20 ++++++++++ .../System/Decimal64Tests.cs | 20 ++++++++++ 4 files changed, 86 insertions(+), 14 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 f0ef5d9c45b2ff..8c7c95bfdbf1ad 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs @@ -597,6 +597,18 @@ private static TValue PropagateNaN(TValue left, TValue right) return (nanBits & TDecimal.SignMask) | TDecimal.NaNMask | payload; } + /// + /// Returns unchanged when it is a number, or the canonical quiet NaN when it is a + /// NaN. The minimum/maximum family selects one operand to return; routing that operand through this helper + /// canonicalizes a NaN result as IEEE 754-2019 §5.1 requires without disturbing the numeric selection. + /// + private static TValue CanonicalizeIfNaN(TValue bits) + where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo + where TValue : unmanaged, IBinaryInteger + { + return TDecimal.IsNaN(bits) ? PropagateNaN(bits, bits) : bits; + } + /// /// 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. @@ -1787,7 +1799,7 @@ internal static TValue MaxMagnitudeDecimalIeee754(TValue x, TV if (GreaterThanDecimalIeee754(ax, ay) || TDecimal.IsNaN(ax)) { - return x; + return CanonicalizeIfNaN(x); } if (EqualsDecimalIeee754(ax, ay)) @@ -1795,7 +1807,7 @@ internal static TValue MaxMagnitudeDecimalIeee754(TValue x, TV return TDecimal.IsNegative(x) ? y : x; } - return y; + return CanonicalizeIfNaN(y); } internal static TValue MinMagnitudeDecimalIeee754(TValue x, TValue y) @@ -1807,7 +1819,7 @@ internal static TValue MinMagnitudeDecimalIeee754(TValue x, TV if (LessThanDecimalIeee754(ax, ay) || TDecimal.IsNaN(ax)) { - return x; + return CanonicalizeIfNaN(x); } if (EqualsDecimalIeee754(ax, ay)) @@ -1815,7 +1827,7 @@ internal static TValue MinMagnitudeDecimalIeee754(TValue x, TV return TDecimal.IsNegative(x) ? x : y; } - return y; + return CanonicalizeIfNaN(y); } internal static TValue MaxMagnitudeNumberDecimalIeee754(TValue x, TValue y) @@ -1827,7 +1839,7 @@ internal static TValue MaxMagnitudeNumberDecimalIeee754(TValue if (GreaterThanDecimalIeee754(ax, ay) || TDecimal.IsNaN(ay)) { - return x; + return CanonicalizeIfNaN(x); } if (EqualsDecimalIeee754(ax, ay)) @@ -1847,7 +1859,7 @@ internal static TValue MinMagnitudeNumberDecimalIeee754(TValue if (LessThanDecimalIeee754(ax, ay) || TDecimal.IsNaN(ay)) { - return x; + return CanonicalizeIfNaN(x); } if (EqualsDecimalIeee754(ax, ay)) @@ -1868,12 +1880,12 @@ internal static TValue MaxDecimalIeee754(TValue x, TValue y) { if (TDecimal.IsNaN(x)) { - return x; + return PropagateNaN(x, x); } if (TDecimal.IsNaN(y)) { - return y; + return PropagateNaN(y, y); } if (!EqualsDecimalIeee754(x, y)) @@ -1890,12 +1902,12 @@ internal static TValue MinDecimalIeee754(TValue x, TValue y) { if (TDecimal.IsNaN(x)) { - return x; + return PropagateNaN(x, x); } if (TDecimal.IsNaN(y)) { - return y; + return PropagateNaN(y, y); } if (!EqualsDecimalIeee754(x, y)) @@ -1910,14 +1922,14 @@ internal static TValue MaxNativeDecimalIeee754(TValue x, TValu where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger { - return GreaterThanDecimalIeee754(x, y) ? x : y; + return CanonicalizeIfNaN(GreaterThanDecimalIeee754(x, y) ? x : y); } internal static TValue MinNativeDecimalIeee754(TValue x, TValue y) where TDecimal : unmanaged, IDecimalIeee754ParseAndFormatInfo where TValue : unmanaged, IBinaryInteger { - return LessThanDecimalIeee754(x, y) ? x : y; + return CanonicalizeIfNaN(LessThanDecimalIeee754(x, y) ? x : y); } internal static TValue MaxNumberDecimalIeee754(TValue x, TValue y) @@ -1931,7 +1943,7 @@ internal static TValue MaxNumberDecimalIeee754(TValue x, TValu return LessThanDecimalIeee754(y, x) ? x : y; } - return x; + return CanonicalizeIfNaN(x); } return TDecimal.IsNegative(y) ? x : y; @@ -1948,7 +1960,7 @@ internal static TValue MinNumberDecimalIeee754(TValue x, TValu return LessThanDecimalIeee754(x, y) ? x : y; } - return x; + return CanonicalizeIfNaN(x); } return TDecimal.IsNegative(x) ? x : y; 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 078381c31650a3..0f20a8b5eb1960 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 @@ -2046,6 +2046,8 @@ public static IEnumerable MaxMagnitude_TestData() yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000003), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000005), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // non-canonical NaN operand is canonicalized + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2071,6 +2073,8 @@ public static IEnumerable MinMagnitude_TestData() yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000003), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000005), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000005) }; + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // non-canonical NaN operand is canonicalized + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2096,6 +2100,8 @@ public static IEnumerable MaxMagnitudeNumber_TestData() yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000003), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000003) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000005), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000005) }; // non-canonical NaN dropped in favor of the number + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // both NaN -> first operand canonicalized } [Theory] @@ -2121,6 +2127,8 @@ public static IEnumerable MinMagnitudeNumber_TestData() yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000003), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x3040000000000000, 0x0000000000000003) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0xB040000000000000, 0x0000000000000005), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xB040000000000000, 0x0000000000000005) }; + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000005) }; // non-canonical NaN dropped in favor of the number + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // both NaN -> first operand canonicalized } [Theory] @@ -2175,6 +2183,8 @@ public static IEnumerable Max_TestData() yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // non-canonical NaN operand is canonicalized + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2210,6 +2220,8 @@ public static IEnumerable Min_TestData() yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // non-canonical NaN operand is canonicalized + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2245,6 +2257,8 @@ public static IEnumerable MaxNative_TestData() yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // NaN operand wins and is canonicalized + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // both NaN -> second operand canonicalized } [Theory] @@ -2280,6 +2294,8 @@ public static IEnumerable MinNative_TestData() yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // NaN operand wins and is canonicalized + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000000000) }; // both NaN -> second operand canonicalized } [Theory] @@ -2315,6 +2331,8 @@ public static IEnumerable MaxNumber_TestData() yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000005) }; // non-canonical NaN dropped in favor of the number + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // both NaN -> first operand canonicalized } [Theory] @@ -2350,6 +2368,8 @@ public static IEnumerable MinNumber_TestData() yield return new object[] { new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7800000000000000, 0x0000000000000000), new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0x7800000000000000, 0x0000000000000000) }; yield return new object[] { new UInt128(0x7C00000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000), new UInt128(0xF800000000000000, 0x0000000000000000) }; + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x3040000000000000, 0x0000000000000005), new UInt128(0x3040000000000000, 0x0000000000000005) }; // non-canonical NaN dropped in favor of the number + yield return new object[] { new UInt128(0x7E00000000000000, 0x0000000000001234), new UInt128(0x7C003FFFFFFFFFFF, 0xFFFFFFFFFFFFFFFF), new UInt128(0x7C00000000000000, 0x0000000000001234) }; // both NaN -> first operand canonicalized } [Theory] 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 34fd85b541abdd..0598858a3ddf17 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 @@ -2045,6 +2045,8 @@ public static IEnumerable MaxMagnitude_TestData() yield return new object[] { 0x32800003U, 0x7C000000U, 0x7C000000U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0x7C000000U }; yield return new object[] { 0xB2800005U, 0x78000000U, 0x78000000U }; + yield return new object[] { 0x7E001234U, 0x32800005U, 0x7C001234U }; // non-canonical NaN operand is canonicalized + yield return new object[] { 0x32800005U, 0x7C0FFFFFU, 0x7C000000U }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2070,6 +2072,8 @@ public static IEnumerable MinMagnitude_TestData() yield return new object[] { 0x32800003U, 0x7C000000U, 0x7C000000U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0x7C000000U }; yield return new object[] { 0xB2800005U, 0x78000000U, 0xB2800005U }; + yield return new object[] { 0x7E001234U, 0x32800005U, 0x7C001234U }; // non-canonical NaN operand is canonicalized + yield return new object[] { 0x32800005U, 0x7C0FFFFFU, 0x7C000000U }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2095,6 +2099,8 @@ public static IEnumerable MaxMagnitudeNumber_TestData() yield return new object[] { 0x32800003U, 0x7C000000U, 0x32800003U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0xF8000000U }; yield return new object[] { 0xB2800005U, 0x78000000U, 0x78000000U }; + yield return new object[] { 0x7E001234U, 0x32800005U, 0x32800005U }; // non-canonical NaN dropped in favor of the number + yield return new object[] { 0x7E001234U, 0x7C0FFFFFU, 0x7C001234U }; // both NaN -> first operand canonicalized } [Theory] @@ -2120,6 +2126,8 @@ public static IEnumerable MinMagnitudeNumber_TestData() yield return new object[] { 0x32800003U, 0x7C000000U, 0x32800003U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0xF8000000U }; yield return new object[] { 0xB2800005U, 0x78000000U, 0xB2800005U }; + yield return new object[] { 0x7E001234U, 0x32800005U, 0x32800005U }; // non-canonical NaN dropped in favor of the number + yield return new object[] { 0x7E001234U, 0x7C0FFFFFU, 0x7C001234U }; // both NaN -> first operand canonicalized } [Theory] @@ -2174,6 +2182,8 @@ public static IEnumerable Max_TestData() yield return new object[] { 0xF8000000U, 0x78000000U, 0x78000000U }; yield return new object[] { 0x78000000U, 0x7C000000U, 0x7C000000U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0x7C000000U }; + yield return new object[] { 0x7E001234U, 0x32800005U, 0x7C001234U }; // non-canonical NaN operand is canonicalized + yield return new object[] { 0x32800005U, 0x7C0FFFFFU, 0x7C000000U }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2209,6 +2219,8 @@ public static IEnumerable Min_TestData() yield return new object[] { 0xF8000000U, 0x78000000U, 0xF8000000U }; yield return new object[] { 0x78000000U, 0x7C000000U, 0x7C000000U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0x7C000000U }; + yield return new object[] { 0x7E001234U, 0x32800005U, 0x7C001234U }; // non-canonical NaN operand is canonicalized + yield return new object[] { 0x32800005U, 0x7C0FFFFFU, 0x7C000000U }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2244,6 +2256,8 @@ public static IEnumerable MaxNative_TestData() yield return new object[] { 0xF8000000U, 0x78000000U, 0x78000000U }; yield return new object[] { 0x78000000U, 0x7C000000U, 0x7C000000U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0xF8000000U }; + yield return new object[] { 0x32800005U, 0x7E001234U, 0x7C001234U }; // NaN operand wins and is canonicalized + yield return new object[] { 0x7E001234U, 0x7C0FFFFFU, 0x7C000000U }; // both NaN -> second operand canonicalized } [Theory] @@ -2279,6 +2293,8 @@ public static IEnumerable MinNative_TestData() yield return new object[] { 0xF8000000U, 0x78000000U, 0xF8000000U }; yield return new object[] { 0x78000000U, 0x7C000000U, 0x7C000000U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0xF8000000U }; + yield return new object[] { 0x32800005U, 0x7E001234U, 0x7C001234U }; // NaN operand wins and is canonicalized + yield return new object[] { 0x7E001234U, 0x7C0FFFFFU, 0x7C000000U }; // both NaN -> second operand canonicalized } [Theory] @@ -2314,6 +2330,8 @@ public static IEnumerable MaxNumber_TestData() yield return new object[] { 0xF8000000U, 0x78000000U, 0x78000000U }; yield return new object[] { 0x78000000U, 0x7C000000U, 0x78000000U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0xF8000000U }; + yield return new object[] { 0x7E001234U, 0x32800005U, 0x32800005U }; // non-canonical NaN dropped in favor of the number + yield return new object[] { 0x7E001234U, 0x7C0FFFFFU, 0x7C001234U }; // both NaN -> first operand canonicalized } [Theory] @@ -2349,6 +2367,8 @@ public static IEnumerable MinNumber_TestData() yield return new object[] { 0xF8000000U, 0x78000000U, 0xF8000000U }; yield return new object[] { 0x78000000U, 0x7C000000U, 0x78000000U }; yield return new object[] { 0x7C000000U, 0xF8000000U, 0xF8000000U }; + yield return new object[] { 0x7E001234U, 0x32800005U, 0x32800005U }; // non-canonical NaN dropped in favor of the number + yield return new object[] { 0x7E001234U, 0x7C0FFFFFU, 0x7C001234U }; // both NaN -> first operand canonicalized } [Theory] 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 e609f31e565f20..d24dc9e51dc78d 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 @@ -2049,6 +2049,8 @@ public static IEnumerable MaxMagnitude_TestData() yield return new object[] { 0x31C0000000000003UL, 0x7C00000000000000UL, 0x7C00000000000000UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0x7C00000000000000UL }; yield return new object[] { 0xB1C0000000000005UL, 0x7800000000000000UL, 0x7800000000000000UL }; + yield return new object[] { 0x7E00000000001234UL, 0x31C0000000000005UL, 0x7C00000000001234UL }; // non-canonical NaN operand is canonicalized + yield return new object[] { 0x31C0000000000005UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000000000UL }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2074,6 +2076,8 @@ public static IEnumerable MinMagnitude_TestData() yield return new object[] { 0x31C0000000000003UL, 0x7C00000000000000UL, 0x7C00000000000000UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0x7C00000000000000UL }; yield return new object[] { 0xB1C0000000000005UL, 0x7800000000000000UL, 0xB1C0000000000005UL }; + yield return new object[] { 0x7E00000000001234UL, 0x31C0000000000005UL, 0x7C00000000001234UL }; // non-canonical NaN operand is canonicalized + yield return new object[] { 0x31C0000000000005UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000000000UL }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2099,6 +2103,8 @@ public static IEnumerable MaxMagnitudeNumber_TestData() yield return new object[] { 0x31C0000000000003UL, 0x7C00000000000000UL, 0x31C0000000000003UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0xF800000000000000UL }; yield return new object[] { 0xB1C0000000000005UL, 0x7800000000000000UL, 0x7800000000000000UL }; + yield return new object[] { 0x7E00000000001234UL, 0x31C0000000000005UL, 0x31C0000000000005UL }; // non-canonical NaN dropped in favor of the number + yield return new object[] { 0x7E00000000001234UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000001234UL }; // both NaN -> first operand canonicalized } [Theory] @@ -2124,6 +2130,8 @@ public static IEnumerable MinMagnitudeNumber_TestData() yield return new object[] { 0x31C0000000000003UL, 0x7C00000000000000UL, 0x31C0000000000003UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0xF800000000000000UL }; yield return new object[] { 0xB1C0000000000005UL, 0x7800000000000000UL, 0xB1C0000000000005UL }; + yield return new object[] { 0x7E00000000001234UL, 0x31C0000000000005UL, 0x31C0000000000005UL }; // non-canonical NaN dropped in favor of the number + yield return new object[] { 0x7E00000000001234UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000001234UL }; // both NaN -> first operand canonicalized } [Theory] @@ -2178,6 +2186,8 @@ public static IEnumerable Max_TestData() yield return new object[] { 0xF800000000000000UL, 0x7800000000000000UL, 0x7800000000000000UL }; yield return new object[] { 0x7800000000000000UL, 0x7C00000000000000UL, 0x7C00000000000000UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0x7C00000000000000UL }; + yield return new object[] { 0x7E00000000001234UL, 0x31C0000000000005UL, 0x7C00000000001234UL }; // non-canonical NaN operand is canonicalized + yield return new object[] { 0x31C0000000000005UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000000000UL }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2213,6 +2223,8 @@ public static IEnumerable Min_TestData() yield return new object[] { 0xF800000000000000UL, 0x7800000000000000UL, 0xF800000000000000UL }; yield return new object[] { 0x7800000000000000UL, 0x7C00000000000000UL, 0x7C00000000000000UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0x7C00000000000000UL }; + yield return new object[] { 0x7E00000000001234UL, 0x31C0000000000005UL, 0x7C00000000001234UL }; // non-canonical NaN operand is canonicalized + yield return new object[] { 0x31C0000000000005UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000000000UL }; // non-canonical NaN operand is canonicalized } [Theory] @@ -2248,6 +2260,8 @@ public static IEnumerable MaxNative_TestData() yield return new object[] { 0xF800000000000000UL, 0x7800000000000000UL, 0x7800000000000000UL }; yield return new object[] { 0x7800000000000000UL, 0x7C00000000000000UL, 0x7C00000000000000UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0xF800000000000000UL }; + yield return new object[] { 0x31C0000000000005UL, 0x7E00000000001234UL, 0x7C00000000001234UL }; // NaN operand wins and is canonicalized + yield return new object[] { 0x7E00000000001234UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000000000UL }; // both NaN -> second operand canonicalized } [Theory] @@ -2283,6 +2297,8 @@ public static IEnumerable MinNative_TestData() yield return new object[] { 0xF800000000000000UL, 0x7800000000000000UL, 0xF800000000000000UL }; yield return new object[] { 0x7800000000000000UL, 0x7C00000000000000UL, 0x7C00000000000000UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0xF800000000000000UL }; + yield return new object[] { 0x31C0000000000005UL, 0x7E00000000001234UL, 0x7C00000000001234UL }; // NaN operand wins and is canonicalized + yield return new object[] { 0x7E00000000001234UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000000000UL }; // both NaN -> second operand canonicalized } [Theory] @@ -2318,6 +2334,8 @@ public static IEnumerable MaxNumber_TestData() yield return new object[] { 0xF800000000000000UL, 0x7800000000000000UL, 0x7800000000000000UL }; yield return new object[] { 0x7800000000000000UL, 0x7C00000000000000UL, 0x7800000000000000UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0xF800000000000000UL }; + yield return new object[] { 0x7E00000000001234UL, 0x31C0000000000005UL, 0x31C0000000000005UL }; // non-canonical NaN dropped in favor of the number + yield return new object[] { 0x7E00000000001234UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000001234UL }; // both NaN -> first operand canonicalized } [Theory] @@ -2353,6 +2371,8 @@ public static IEnumerable MinNumber_TestData() yield return new object[] { 0xF800000000000000UL, 0x7800000000000000UL, 0xF800000000000000UL }; yield return new object[] { 0x7800000000000000UL, 0x7C00000000000000UL, 0x7800000000000000UL }; yield return new object[] { 0x7C00000000000000UL, 0xF800000000000000UL, 0xF800000000000000UL }; + yield return new object[] { 0x7E00000000001234UL, 0x31C0000000000005UL, 0x31C0000000000005UL }; // non-canonical NaN dropped in favor of the number + yield return new object[] { 0x7E00000000001234UL, 0x7C03FFFFFFFFFFFFUL, 0x7C00000000001234UL }; // both NaN -> first operand canonicalized } [Theory]