Skip to content

Implement Decimal32/64/128 arithmetic and comparison operators#130508

Open
tannergooding wants to merge 20 commits into
dotnet:mainfrom
tannergooding:tannergooding-decimal-ieee754-operators
Open

Implement Decimal32/64/128 arithmetic and comparison operators#130508
tannergooding wants to merge 20 commits into
dotnet:mainfrom
tannergooding:tannergooding-decimal-ieee754-operators

Conversation

@tannergooding

Copy link
Copy Markdown
Member

Implements the operator surface for Decimal32/Decimal64/Decimal128 in System.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

  • Unary + / -
  • Addition / subtraction
  • Equality / inequality (== / !=)
  • Relational comparisons (<, <=, >, >=)
  • Multiplication
  • Division
  • Increment / decrement (++ / --)

Implementation notes

  • The arithmetic operates on word-level integer significands (not per-digit/string), and produces canonical BID-encoded results with the IEEE 754 preferred exponent for finite results.
  • The non-trivial arithmetic (add/mul/div) follows the algorithms in Intel's Decimal Floating-Point Math Library reference implementation (bidNN_add/bidNN_mul/bidNN_div). Each such method carries an attribution header pointing at THIRD-PARTY-NOTICES.TXT.
  • One documented deviation: the divide-by-ten rounding helper (WideDivideByTen) uses direct division rather than Intel's precomputed reciprocal tables. This is noted in-source as a potential future optimization.

Licensing

  • Added the Intel Decimal Floating-Point Math Library BSD 3-Clause notice to the root THIRD-PARTY-NOTICES.TXT.

Testing

  • Added operator test data to Decimal{32,64,128}Tests.cs.
  • Validated locally against a large batch of fuzz vectors produced by an independent oracle (Python's decimal module, ROUND_HALF_EVEN, clamp=1) with 0 failures; the committed tests exercise the smaller in-repo data sets.

Ref assembly

  • System.Runtime ref regenerated via GenAPI per docs/coding-guidelines/updating-ref-source.md (scoped to the Decimal types).

Note

This PR description was drafted with GitHub Copilot.

tannergooding and others added 11 commits July 9, 2026 17:38
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>
@dotnet-policy-service

Copy link
Copy Markdown
Contributor

Tagging subscribers to this area: @dotnet/area-system-numerics
See info in area-owners.md if you want to be subscribed.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.DecimalIeee754 helpers for equality and relational comparisons, plus add/mul/div integer-significand arithmetic.
  • Updated System.Runtime ref 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

Comment thread src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs Outdated
…-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
Copilot AI review requested due to automatic review settings July 10, 2026 18:14

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 8/9 changed files
  • Comments generated: 2

Comment thread src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs Outdated
Comment thread src/libraries/System.Runtime/ref/System.Runtime.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>
Copilot AI review requested due to automatic review settings July 10, 2026 18:47

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 8/9 changed files
  • Comments generated: 1

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>
Copilot AI review requested due to automatic review settings July 10, 2026 19:22

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 8/9 changed files
  • Comments generated: 2

Comment thread src/libraries/System.Runtime/ref/System.Runtime.cs
Comment thread src/libraries/System.Private.CoreLib/src/System/Number.DecimalIeee754.cs Outdated
…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>
Copilot AI review requested due to automatic review settings July 10, 2026 19:54

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 8/9 changed files
  • Comments generated: 3

Comment thread src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs Outdated
Comment thread src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs Outdated
Comment thread src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs Outdated
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>
Copilot AI review requested due to automatic review settings July 10, 2026 23:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 8/9 changed files
  • Comments generated: 4

Comment thread src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal32Tests.cs Outdated
Comment thread src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal64Tests.cs Outdated
Comment thread src/libraries/System.Runtime/tests/System.Runtime.Tests/System/Decimal128Tests.cs Outdated
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>
Copilot AI review requested due to automatic review settings July 10, 2026 23:25

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 8/9 changed files
  • Comments generated: 1

tannergooding and others added 3 commits July 11, 2026 08:22
…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>
Copilot AI review requested due to automatic review settings July 11, 2026 17:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 10/11 changed files
  • Comments generated: 0 new

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants