Skip to content

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

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

Implement Decimal32/64/128 arithmetic and comparison operators#130508
tannergooding wants to merge 17 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

Comment on lines +1019 to +1022
int bits = TValue.Zero.GetByteCount() * 8;
int half = bits / 2;
TValue lowMask = (TValue.One << half) - TValue.One;
TValue baseValue = TValue.One << half;
{
roundDigit = 0;

for (int i = 0; i < dropCount; i++)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Without the full reciprocal tables, you could still do a simple optimization for Decimal32 and Decimal64 by widening them to C# types. If TValue is UInt64 then create a UInt128 and divide by 10^dropCount. That should already be in the power of 10 table and if not it can be calculated by multiplying large enough powers of 10.

/// direct integer division for simplicity; adopting the reciprocal-multiply tables is a possible future
/// performance optimization.
/// </remarks>
private static int WideDivideByTen<TValue>(ref TValue high, ref TValue low)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Same comment here about widening to C# types for when TValue is uint and ulong.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The Intel implementation has a lot of tests as well. It would probably be worth adding those if not already added. They have different rounding modes, so filtering to just round to even and the specific operation we want to test would give us a good subset of tests.

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