Implement Decimal32/64/128 arithmetic and comparison operators#130508
Open
tannergooding wants to merge 20 commits into
Open
Implement Decimal32/64/128 arithmetic and comparison operators#130508tannergooding wants to merge 20 commits into
tannergooding wants to merge 20 commits into
Conversation
Adds the IUnaryPlusOperators and IUnaryNegationOperators operators to Decimal32, Decimal64, and Decimal128 as part of dotnet#81376. Unary plus returns the value unchanged; unary negation toggles the sign bit, matching the IEEE 754 negate operation for finite values, infinities, and NaN. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add binary operator + and operator - for the IEEE 754 decimal types via a shared generic Number.AddDecimalIeee754 helper. The helper aligns operands to the smaller (preferred) exponent using exact base-10 digit arithmetic, folds truly-dropped low-order digits into a sticky flag, and feeds the exact sum into the existing NumberToDecimalIeee754Bits round-nearest-ties-even pipeline. Subtraction negates the right operand's sign bit and adds. Infinity results are canonicalized. Adds exact-bit test vectors validated against an independent oracle. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add IEEE 754 == and != operators to Decimal32, Decimal64, and Decimal128. A new shared Number.EqualsDecimalIeee754<TDecimal,TValue> helper returns false when either operand is NaN (NaN is unordered and never equal, even to itself) and otherwise defers to the existing CompareDecimalIeee754 for value-based equality, so the two zeros compare equal and different members of a cohort compare equal. operator != is defined as !(left == right). The ref assembly also declares the Equals(object)/GetHashCode overrides that the concrete types already provide (required once == / != are present). Tests use bit-exact MemberData vectors produced by an independent Python decimal oracle, covering NaN, signed zero, cohort equality, and non-canonical infinity inputs, plus random equal-cohort and unequal pairs. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds IEEE 754 relational operators (<, >, <=, >=) to Decimal32, Decimal64, and Decimal128. NaN is unordered, so any comparison involving NaN returns false; non-NaN operands use the existing CompareDecimalIeee754 value ordering (+0 == -0, cohort members equal, -Inf < finite < +Inf). Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds `operator *` for the IEEE 754 decimal types. Coefficients are multiplied exactly in base 10 (a product can span up to twice the format precision, exceeding UInt128 for Decimal128, so digit spans are used rather than the underlying integer width). The product exponent is the sum of the operand exponents, which is also the IEEE 754 preferred exponent since the product's trailing zeros are retained; the exact digit string is then rounded through the shared NumberToDecimalIeee754Bits pipeline. Also fixes two latent issues in the shared encoding pipeline that only a multiply product can reach: * NumberToDecimalIeee754Bits underflow branch assumed at most Precision significant digits. A wide product whose quantum falls just below the minimum adjusted exponent can carry more, so the digit count is now capped to Precision (a single correct rounding of the full product; a no-op for <=Precision inputs from parse/add/sub). * DecimalIeee754FiniteNumberBinaryEncoding clamped a zero coefficient's exponent to MaxExponent instead of MaxAdjustedExponent. A zero result with a preferred exponent above the maximum quantum (e.g. zero times a large-exponent value) overflowed the biased exponent field. Add/sub never reach this because their zero-path exponents are bounded by the operand quanta. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Adds the op_Increment (++) and op_Decrement (--) operators for the IEEE 754 decimal types. These are implemented as value + 1E0 / value - 1E0 by reusing the existing Number.AddDecimalIeee754 helper, matching the binary +/- operators. A OneValue constant (canonical 1E0) is added to each type. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The internal Add/Multiply/Divide cores for the IEEE 754 decimal types previously serialized significands into per-digit ASCII byte spans and performed base-10 "string" arithmetic, which made the operators far too slow to be useful. Rewrite the arithmetic to operate directly on the native limb type (uint/ulong/UInt128): - Multiply computes the full double-width product via WideMultiply and rounds through the shared word-level encoder. - Add aligns operands using single-limb scaling into a (Hi,Lo) pair, then performs a wide add (same sign) or compare-and-subtract (opposite sign) with correct sticky-bit propagation. - Divide uses single-limb decimal long division, producing the quotient one digit at a time with an exact-result trailing-zero strip toward the preferred exponent. All three feed NumberToDecimalIeee754BitsFromWide, which mirrors the existing NumberToDecimalIeee754Bits rounding (round-half-even, subnormal capping to avoid double rounding, overflow-to-infinity, exact preferred exponent). The now-dead Big* digit-span helper cluster is removed. Validated against the Intel BID reference oracle with large randomized batches (39235 vectors across all three ops and formats) plus the existing committed test suite, all passing. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The Decimal32/64/128 operator declarations were previously hand-added to the reference assembly in a non-conformant style (non-partial, unqualified names, hand-ordered members, missing the implicit IParsable interface). Per docs/coding-guidelines/updating-ref-source.md, regenerate these three types with the GenAPI tool so the reference source matches tool output (fully-qualified names, partial structs, canonical member ordering), while filtering out the unrelated GenAPI churn to the rest of the file. No public API surface changes; this only normalizes the reference source. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…ByTen The Intel reference implementation divides by powers of ten using precomputed reciprocals; this helper uses direct division for now. Add a remark documenting the potential future optimization. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…OTICES The Decimal32/64/128 arithmetic is based on Intel's BSD 3-Clause licensed reference implementation. Reproduce the copyright and license text as required by the license. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Contributor
|
Tagging subscribers to this area: @dotnet/area-system-numerics |
Contributor
There was a problem hiding this comment.
Pull request overview
Adds IEEE 754 decimal operator support for System.Numerics.Decimal32/Decimal64/Decimal128, implemented via shared BID-style arithmetic helpers in System.Number, with corresponding ref-surface updates and tests.
Changes:
- Added arithmetic/comparison operator implementations to the three decimal IEEE 754 types.
- Introduced/extended shared
Number.DecimalIeee754helpers for equality and relational comparisons, plus add/mul/div integer-significand arithmetic. - Updated
System.Runtimeref surface and added test vectors for the new operators; added the Intel Decimal FP Math Library notice.
Show a summary per file
| File | Description |
|---|---|
| THIRD-PARTY-NOTICES.TXT | Adds BSD 3-Clause notice for Intel Decimal Floating-Point Math Library. |
| src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs | Adds operator test vectors (unary, add/sub, comparisons, mul/div, ++/--). |
| src/libraries/System.Runtime/ref/System.Runtime.cs | Updates public ref surface for Decimal32/64/128 to include new operators. |
| src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs | Adds operator implementations forwarding to Number.DecimalIeee754 helpers. |
| src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs | Adds operator implementations forwarding to Number.DecimalIeee754 helpers. |
| src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs | Adds operator implementations forwarding to Number.DecimalIeee754 helpers. |
| src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs | Adds IEEE 754 equality/relational helpers and BID-style add/mul/div implementations plus wide rounding support. |
Copilot's findings
- Files reviewed: 8/9 changed files
- Comments generated: 6
…-ieee754-operators # Conflicts: # src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal128.cs # src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal32.cs # src/libraries/System.Private.CoreLib/src/System/Numerics/Decimal64.cs # src/libraries/System.Runtime/ref/System.Runtime.cs # src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs # src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs # src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs
IEEE 754 BID specifies that a finite encoding whose significand exceeds the maximum representable coefficient (10^p - 1) is non-canonical and represents zero. The generic UnpackDecimalIeee754 helper decoded the raw significand without applying this rule, so arithmetic and comparisons on such bit patterns could misbehave (e.g. finite/nc not treated as division by zero). Add a single clamp inside UnpackDecimalIeee754, matching unpack_BID32/64/128 in the Intel Decimal Floating-Point Math Library, so every downstream consumer (Add/Subtract/Multiply/Divide, Compare, GetHashCode) handles all finite bit patterns correctly. Also fix copy/pasted addition comments in op_Subtraction_TestData across all three test files so they describe subtraction operands and outcomes. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
In NumberToDecimalIeee754BitsFromWide the expensive WideDigitCount was computed before the exponent > MaxExponent early return, so the cost was paid even when immediately returning +/-Infinity. Move the overflow check ahead of the digit count. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…e754 The addition alignment scale factor 10^effectiveDifference was always computed via a multiply-by-ten loop. Reuse the format's existing Power10 lookup table: exponents in range (0..Precision-1) index it directly, and the larger alignment exponents (Precision..Precision+2, which the table does not cover) reduce by the largest table entry each iteration and finish with a single lookup for the remainder. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The op_Increment_TestData and op_Decrement_TestData comments were copy/pasted, so several described the wrong operator (e.g. decrement cases annotated with '++ -> ...'). Rewrite each dataset's comments to describe only its own operator's semantics. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
The op_Equality_TestData 'all-nines' case described the result with '!=' even though the table asserts '=='; correct it to match the operator under test. Also replace the ignored 'expected' parameter in Decimal128.op_UnaryPlus (which reuses UnaryNegation_TestData because UInt128 cannot be an InlineData constant) with a discard parameter, removing the redundant assignment. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This was referenced Jul 11, 2026
…it and 64-bit formats The double-width helpers used by the decimal arithmetic operators (WideMultiply, DropDigits, WideDigitCount) carried the coefficient as a pair of TValue limbs and operated on it with half-limb decomposition and a per-digit long-division-by-ten loop. For the 32-bit and 64-bit formats the pair fits in a single wider C# integer (ulong and UInt128 respectively), so the product now comes from one native multiply and digit dropping/counting from a single native divide by 10^dropCount instead of looping. The 128-bit format has no wider native type and keeps the generic limb path. Validated bit-for-bit against the Intel Decimal Floating-Point Math Library readtest.in vectors (round-to-nearest, 2454 hex-operand cases) with no change in results, and all existing Decimal32/64/128 tests continue to pass. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
…NaN sign Arithmetic on the decimal types now propagates a NaN operand's sign and payload per IEEE 754-2019 6.2.3 (first NaN operand wins, signaling NaNs are quieted, non-canonical payloads read as zero) via a shared PropagateNaN helper, and fresh invalid operations (Inf +/- Inf, Inf * 0, Inf / Inf, 0 / 0) now return the canonical positive quiet NaN emitted by the Intel reference rather than the negative NaN constant. Subtraction is routed through a helper that leaves a NaN operand's sign untouched so it propagates identically to addition. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Add an opt-in test data source that streams the operator vectors out of the readtest.in file shipped with the Intel(R) Decimal Floating-Point Math Library and validates the ported Decimal32/64/128 arithmetic and comparison operators bit-for-bit against them. The file is large and not redistributed, so the six theories are gated on readtest.in being present next to the test assembly (AppContext.BaseDirectory) and skip otherwise. An interested developer copies the file into the output directory to opt in, keeping the exhaustive validation available for local and outer-loop runs without redistributing the file or adding cost to every test pass. Gating on a deliberately dropped-in file rather than an environment variable naming an arbitrary path avoids reading from a location an unrelated process could influence and is no worse than shipping the file. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the operator surface for
Decimal32/Decimal64/Decimal128inSystem.Numerics, continuing the work in #81376. The types previously provided only layout plus parsing/formatting; this adds the arithmetic and comparison operators, built up incrementally (one commit per operator group).Operators added
+/-==/!=)<,<=,>,>=)++/--)Implementation notes
bidNN_add/bidNN_mul/bidNN_div). Each such method carries an attribution header pointing atTHIRD-PARTY-NOTICES.TXT.WideDivideByTen) uses direct division rather than Intel's precomputed reciprocal tables. This is noted in-source as a potential future optimization.Licensing
THIRD-PARTY-NOTICES.TXT.Testing
Decimal{32,64,128}Tests.cs.decimalmodule,ROUND_HALF_EVEN,clamp=1) with 0 failures; the committed tests exercise the smaller in-repo data sets.Ref assembly
System.Runtimeref regenerated via GenAPI perdocs/coding-guidelines/updating-ref-source.md(scoped to the Decimal types).Note
This PR description was drafted with GitHub Copilot.