Skip to content

Add Decimal32, Decimal64, Decimal128#100729

Open
RaymondHuy wants to merge 76 commits intodotnet:mainfrom
RaymondHuy:issue-81376
Open

Add Decimal32, Decimal64, Decimal128#100729
RaymondHuy wants to merge 76 commits intodotnet:mainfrom
RaymondHuy:issue-81376

Conversation

@RaymondHuy
Copy link
Contributor

Resolve #81376

@ghost
Copy link

ghost commented Apr 6, 2024

Note regarding the new-api-needs-documentation label:

This serves as a reminder for when your PR is modifying a ref *.cs file and adding/modifying public APIs, please make sure the API implementation in the src *.cs file is documented with triple slash comments, so the PR reviewers can sign off that change.

@RaymondHuy RaymondHuy marked this pull request as draft April 6, 2024 17:29
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Apr 6, 2024
static int IDecimalIeee754UnpackInfo<Decimal128, Int128, UInt128>.NumberDigitsPrecision => NumberDigitsPrecision;


private static Int128[] Int128Powers10 =>
Copy link
Member

Choose a reason for hiding this comment

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

If Int128 is not optimized, this can be optimally stored as a ReadOnlySpan of Int32/Int64 like Number.BigInteger.Pow10BigNumTable

Comment on lines 104 to 107
public override int GetHashCode()
{
return _value.GetHashCode();
}
Copy link
Member

Choose a reason for hiding this comment

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

+0 and -0 should have the same hash code. The easiest way can be stripping the sign bit.

I also remember that there can be same values with different representations.

else
{
// Equivalant values are compared by their exponent parts,
// and the value with smaller exponent is considered closer to zero.
// This only applies to IEEE 754 decimals. Consider to add support if decimals are added into .NET.
return 0;
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@huoyaoyuan Thanks for your review. However this PR targets only the first phase as following: #81376 (comment) so I haven't added NegativeZero and PositiveZero. I will update GetHashCode method in the next phase.

@tannergooding
Copy link
Member

This is on my radar to take a look at; just noting it might be a bit delayed due to other priorities for the .NET 9 release.

CC. @jeffhandley

@RaymondHuy RaymondHuy marked this pull request as ready for review April 9, 2024 17:21
Comment on lines 33 to 36
private static readonly UInt128 PositiveInfinityValue = new UInt128(upper: 0x7800_0000_0000_0000, lower: 0);
private static readonly UInt128 NegativeInfinityValue = new UInt128(upper: 0xf800_0000_0000_0000, lower: 0);
private static readonly UInt128 ZeroValue = new UInt128(0, 0);
private static readonly UInt128 NegativeZeroValue = new UInt128(0x8000_0000_0000_0000, 0);
Copy link
Member

Choose a reason for hiding this comment

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

Can this be a problem for codegen since UInt128 is not const?

Copy link
Member

Choose a reason for hiding this comment

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

They should probably be properties instead

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks, I have updated it.

return Number.FormatDecimalIeee754<Decimal128, UInt128>(this, format, NumberFormatInfo.GetInstance(provider));
}

private static UInt128[] UInt128Powers10 =>
Copy link
Member

Choose a reason for hiding this comment

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

This can be a problem for constant embedding. long array can be encoded into executable and read by pairs.

@znakeeye
Copy link

Are there any preliminary benchmarks of these new types? I.e. How do decimal128 and decimal64 compare to the existing decimal type?

Also, can we expect these new types to arrive in .NET 11?

@huoyaoyuan
Copy link
Member

Are there any preliminary benchmarks of these new types? I.e. How do decimal128 and decimal64 compare to the existing decimal type?

We are focusing on the different semantic first. Both types are having different existing standard to follow.

Also, can we expect these new types to arrive in .NET 11?

It's optimistic, but not a guarantee.

@RaymondHuy
Copy link
Contributor Author

Hi @tannergooding, thanks for your review, I have resolved almost your comments (handle NaN, TDecimal.MaxValue + (Delta / 2) vs TDecimal.MaxValue + ((Delta / 2) - 1) rounding, remove unsafe, update parameter names, added comments). Can you help me review it again ?

@RaymondHuy
Copy link
Contributor Author

Hi @tannergooding can you help me take a look at this PR when you have time 😉

@tannergooding
Copy link
Member

Yes, this is still on my backlog, just had some other work items that have been higher priority. Sorry for the delay

@jeffhandley
Copy link
Member

@RaymondHuy Just reassuring you this is still on our radar. We are committed to getting this into .NET 11 as early in the release as we can.

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 16 out of 16 changed files in this pull request and generated 13 comments.

private static UInt128 NegativeZeroValue => new UInt128(0x8000_0000_0000_0000, 0);
private static UInt128 QuietNaNValue => new UInt128(0x7C00_0000_0000_0000, 0);
private static UInt128 MaxInternalValue = new UInt128(upper: 0x5FFF_ED09_BEAD_87C0, lower: 0x378D_8E63_FFFF_FFFF);
private static UInt128 MinInternalValue = new UInt128(upper: 0xDFFF_ED09B_EAD_87C0, lower: 0x378D_8E63_FFFF_FFFF);
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

There appears to be a typo in the hexadecimal constant. The value 0xDFFF_ED09B_EAD_87C0 contains an extra 'B' after '09'. It should likely be 0xDFFF_ED09_BEAD_87C0 to match the pattern in MaxInternalValue on line 39.

Suggested change
private static UInt128 MinInternalValue = new UInt128(upper: 0xDFFF_ED09B_EAD_87C0, lower: 0x378D_8E63_FFFF_FFFF);
private static UInt128 MinInternalValue = new UInt128(upper: 0xDFFF_ED09_BEAD_87C0, lower: 0x378D_8E63_FFFF_FFFF);

Copilot uses AI. Check for mistakes.
Comment on lines 1359 to 1369
return new UInt128(((ulong)(_blocks[3]) << 96) + ((ulong)_blocks[2] << 64), ((ulong)(_blocks[1]) << 32) + _blocks[0]);
}

if (_length > 2)
{
return new UInt128(((ulong)_blocks[2] << 64), ((ulong)(_blocks[1]) << 32) + _blocks[0]);
}

if (_length > 1)
{
return ((ulong)(_blocks[1]) << 32) + _blocks[0];
Copy link

Copilot AI Feb 16, 2026

Choose a reason for hiding this comment

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

The bit shift operation ((ulong)(_blocks[3]) << 96) is incorrect. A ulong is 64 bits, so shifting it by 96 bits will produce 0 or undefined behavior. For a UInt128 with blocks stored as 32-bit values, the upper bits should be constructed differently. The correct approach should be: new UInt128(((ulong)_blocks[3] << 32) + _blocks[2], ((ulong)_blocks[1] << 32) + _blocks[0]) since UInt128 takes two ulong parameters (upper and lower 64 bits).

Suggested change
return new UInt128(((ulong)(_blocks[3]) << 96) + ((ulong)_blocks[2] << 64), ((ulong)(_blocks[1]) << 32) + _blocks[0]);
}
if (_length > 2)
{
return new UInt128(((ulong)_blocks[2] << 64), ((ulong)(_blocks[1]) << 32) + _blocks[0]);
}
if (_length > 1)
{
return ((ulong)(_blocks[1]) << 32) + _blocks[0];
return new UInt128(((ulong)_blocks[3] << 32) + _blocks[2], ((ulong)_blocks[1] << 32) + _blocks[0]);
}
if (_length > 2)
{
return new UInt128(_blocks[2], ((ulong)_blocks[1] << 32) + _blocks[0]);
}
if (_length > 1)
{
return ((ulong)_blocks[1] << 32) + _blocks[0];

Copilot uses AI. Check for mistakes.
@jeffhandley
Copy link
Member

@RaymondHuy Copilot's review produced some good feedback comments. The using statements and unused variables look straightforward enough, but the bit-shifting comment warrants your analysis and consideration, so I've left all those comments unaddressed.

@RaymondHuy
Copy link
Contributor Author

Hi @jeffhandley , thanks for your review, I have resolved all of them and also added test case to bit-shifting comment.

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

Labels

area-System.Numerics community-contribution Indicates that the PR has been added by a community member new-api-needs-documentation

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[API Proposal]: Add Decimal32, Decimal64, and Decimal128 from the IEEE 754-2019 standard.

7 participants