Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
74 commits
Select commit Hold shift + click to select a range
ac5e447
add constructor info
RaymondHuy Mar 11, 2024
1af3337
Add Decimal32
RaymondHuy Mar 30, 2024
cee05c8
add test cases
RaymondHuy Apr 5, 2024
da72dcd
Add Decimal128
RaymondHuy Apr 6, 2024
6f3827c
add tests
RaymondHuy Apr 6, 2024
7e9344b
Merge commit '83b0d939bedadf7d782b0b26307c2d8c1d5b76f4' into issue-81376
RaymondHuy Apr 7, 2024
f882b96
Fix digits pointer
RaymondHuy Apr 7, 2024
762f56d
Implement ToString() method.
RaymondHuy Apr 9, 2024
66a1127
resolve comments
RaymondHuy Apr 9, 2024
83937d2
resolve comments
RaymondHuy Apr 9, 2024
19755ba
Refactor shift operators to mask
RaymondHuy Apr 10, 2024
ede6ca3
Extract common mask to property.
RaymondHuy Apr 10, 2024
b4e5d34
add more tests
RaymondHuy Apr 10, 2024
504f042
add comments
RaymondHuy Apr 11, 2024
31ffd5c
Add overflow message.
RaymondHuy Apr 11, 2024
6212873
Add overflow message
RaymondHuy Apr 11, 2024
cb1d8b1
split uint128 to ulong.
RaymondHuy Apr 11, 2024
0a4620e
Change private to internal
RaymondHuy Apr 11, 2024
8f3d4ff
validate overflow decimal case.
RaymondHuy Apr 12, 2024
db834cb
add more tests
RaymondHuy Apr 18, 2024
2ed126b
add more tests
RaymondHuy May 29, 2024
d08b7b5
add more tests
RaymondHuy May 29, 2024
bcfdca6
Merge branch 'master' into issue-81376
RaymondHuy Nov 13, 2024
472a2fe
Merge branch 'master' into issue-81376
RaymondHuy Nov 22, 2024
49f6676
Correct naming
RaymondHuy Dec 17, 2024
f82d9a6
fix naming convention
RaymondHuy Dec 17, 2024
567906c
Merge branch 'main' into issue-81376
tannergooding Jan 30, 2025
70cc09d
Merge branch 'main' into issue-81376
RaymondHuy Mar 5, 2025
715677f
Merge branch 'main' into issue-81376
RaymondHuy Mar 14, 2025
a66d209
Merge branch 'main' into issue-81376
RaymondHuy Mar 20, 2025
2405a15
Merge branch 'main' into issue-81376
RaymondHuy Mar 24, 2025
926db20
Merge branch 'main' into issue-81376
RaymondHuy Mar 30, 2025
d41c56e
Merge branch 'main' into issue-81376
RaymondHuy Apr 3, 2025
ac8f7a6
resolve simple comments
RaymondHuy Apr 4, 2025
9d0fa8e
combine interface
RaymondHuy Apr 4, 2025
4478f54
add XML documentation
RaymondHuy Apr 4, 2025
7b1db43
add XML documentation
RaymondHuy Apr 4, 2025
79bea65
resolve comments
RaymondHuy Apr 4, 2025
e9a5cf7
Revert "resolve comments"
RaymondHuy Apr 4, 2025
0a57a1a
resolve comments
RaymondHuy Apr 4, 2025
ab7c5fb
resolve comments.
RaymondHuy Apr 5, 2025
6a1c2c3
remove unused fields.
RaymondHuy Apr 5, 2025
e584a92
use ArrayPool
RaymondHuy Apr 8, 2025
84ef494
add comments
RaymondHuy Apr 8, 2025
61eaba8
remove constructor
RaymondHuy Apr 15, 2025
2a1fc70
remove constructor
RaymondHuy Apr 15, 2025
afc15e4
add rounding method
RaymondHuy Apr 20, 2025
002d9dd
Merge branch 'main' into issue-81376
RaymondHuy Apr 20, 2025
0d598a2
add digit count condition check
RaymondHuy Apr 24, 2025
313c90d
fix wrong rounding
RaymondHuy Apr 24, 2025
7b6a3c0
correct last significand digit.
RaymondHuy Apr 24, 2025
d93ac2b
add comments
RaymondHuy Apr 24, 2025
c44003e
add more tests
RaymondHuy Apr 25, 2025
9533b39
remove unused test cases
RaymondHuy Apr 25, 2025
f2e73e2
Merge branch 'main' into issue-81376
RaymondHuy May 15, 2025
e2b497a
Merge branch 'main' into issue-81376
RaymondHuy Jun 7, 2025
a1dee7b
Merge branch 'main' into issue-81376
RaymondHuy Jul 18, 2025
0ce3024
Merge branch 'main' into issue-81376
jeffhandley Sep 1, 2025
a136ca1
Merge branch 'main' into issue-81376
RaymondHuy Sep 9, 2025
335089f
fix failed build
RaymondHuy Sep 9, 2025
e34d256
resolve some comments
RaymondHuy Nov 18, 2025
6947fe1
Merge branch 'master' into issue-81376
RaymondHuy Nov 18, 2025
d17a6d3
fix failed test cases
RaymondHuy Nov 18, 2025
b4c97a5
resolve some comments
RaymondHuy Nov 18, 2025
eb07b42
update
RaymondHuy Nov 18, 2025
5a1a8fe
fix negative zero failed tests
RaymondHuy Nov 19, 2025
516a200
add comments to clarify method parameters
RaymondHuy Nov 19, 2025
bdf86f5
replace readonly by property
RaymondHuy Nov 19, 2025
1921014
fix wrong decimal 64 mask
RaymondHuy Nov 19, 2025
ee2acad
handle NaN value
RaymondHuy Nov 26, 2025
a9362e3
add test cases for NaN number
RaymondHuy Dec 4, 2025
8668878
Handle Ulp value
RaymondHuy Dec 9, 2025
3f7be57
Add test case for max value rounding
RaymondHuy Dec 10, 2025
cef4924
update
RaymondHuy Dec 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/libraries/Common/src/System/Number.NumberBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ internal static partial class Number
internal const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295
internal const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615
internal const int UInt128NumberBufferLength = 39 + 1; // 39 for the longest input: 340,282,366,920,938,463,463,374,607,431,768,211,455
internal const int Decimal32NumberBufferLength = 97 + 1 + 1; // 97 for the longest input + 1 for rounding
internal const int Decimal64NumberBufferLength = 385 + 1 + 1; // 385 for the longest input + 1 for rounding
internal const int Decimal128NumberBufferLength = 6145 + 1 + 1; // 6145 for the longest input + 1 for rounding

internal unsafe ref struct NumberBuffer
{
Expand Down
18 changes: 18 additions & 0 deletions src/libraries/System.Private.CoreLib/src/Resources/Strings.resx
Original file line number Diff line number Diff line change
Expand Up @@ -531,6 +531,15 @@
<data name="Arg_MustBeDecimal" xml:space="preserve">
<value>Object must be of type Decimal.</value>
</data>
<data name="Arg_MustBeDecimal32" xml:space="preserve">
<value>Object must be of type Decimal32.</value>
</data>
<data name="Arg_MustBeDecimal64" xml:space="preserve">
<value>Object must be of type Decimal64.</value>
</data>
<data name="Arg_MustBeDecimal128" xml:space="preserve">
<value>Object must be of type Decimal128.</value>
</data>
<data name="Arg_MustBeDelegate" xml:space="preserve">
<value>Type must derive from Delegate.</value>
</data>
Expand Down Expand Up @@ -3197,6 +3206,15 @@
<data name="Overflow_Decimal" xml:space="preserve">
<value>Value was either too large or too small for a Decimal.</value>
</data>
<data name="Overflow_Decimal32" xml:space="preserve">
<value>Value was either too large or too small for a Decimal32.</value>
</data>
<data name="Overflow_Decimal64" xml:space="preserve">
<value>Value was either too large or too small for a Decimal64.</value>
</data>
<data name="Overflow_Decimal128" xml:space="preserve">
<value>Value was either too large or too small for a Decimal128.</value>
</data>
<data name="Overflow_Duration" xml:space="preserve">
<value>The duration cannot be returned for TimeSpan.MinValue because the absolute value of TimeSpan.MinValue exceeds the value of TimeSpan.MaxValue.</value>
</data>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,10 @@
<Compile Include="$(MSBuildThisFileDirectory)System\IFormatProvider.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\IFormattable.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Index.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Number.DecimalIeee754.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Numerics\Decimal128.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Numerics\Decimal32.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Numerics\Decimal64.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any1CharPackedSearchValues.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any1CharPackedIgnoreCaseSearchValues.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\SearchValues\Any2CharPackedIgnoreCaseSearchValues.cs" />
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,252 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Numerics;

namespace System
{
internal interface IDecimalIeee754ConstructorInfo<TSelf, TSignificand, TValue>
where TSelf : unmanaged, IDecimalIeee754ConstructorInfo<TSelf, TSignificand, TValue>
where TSignificand : IBinaryInteger<TSignificand>
where TValue : IBinaryInteger<TValue>
{
static abstract TSignificand MaxSignificand { get; }
static abstract int MaxDecimalExponent { get; }
static abstract int MinDecimalExponent { get; }
static abstract int NumberDigitsPrecision { get; }
static abstract int Bias { get; }
static abstract int CountDigits(TSignificand number);
static abstract TSignificand Power10(int exponent);
static abstract int MostSignificantBitNumberOfSignificand { get; }
static abstract int NumberBitsEncoding { get; }
static abstract int NumberBitsCombinationField { get; }
static abstract int NumberBitsExponent { get; }
static abstract TValue PositiveInfinityBits { get; }
static abstract TValue NegativeInfinityBits { get; }
static abstract TValue Zero { get; }
}

internal interface IDecimalIeee754UnpackInfo<TSelf, TSignificand, TValue>
where TSelf : unmanaged, IDecimalIeee754UnpackInfo<TSelf, TSignificand, TValue>
where TSignificand : IBinaryInteger<TSignificand>
where TValue : IBinaryInteger<TValue>
{
static abstract TValue SignMask { get; }
static abstract int NumberBitsEncoding { get; }
static abstract int NumberBitsExponent { get; }
static abstract int NumberDigitsPrecision { get; }
static abstract int Bias { get; }
static abstract TSignificand TwoPowerMostSignificantBitNumberOfSignificand { get; }
static abstract int ConvertToExponent(TValue value);
static abstract TSignificand ConvertToSignificand(TValue value);
static abstract TSignificand Power10(int exponent);
}

internal static partial class Number
{
internal static TValue CalDecimalIeee754<TDecimal, TSignificand, TValue>(TSignificand significand, int exponent)
where TDecimal : unmanaged, IDecimalIeee754ConstructorInfo<TDecimal, TSignificand, TValue>
where TSignificand : IBinaryInteger<TSignificand>
where TValue : IBinaryInteger<TValue>
{
if (significand == TSignificand.Zero)
{
return TValue.Zero;
}

TSignificand unsignedSignificand = significand > TSignificand.Zero ? significand : -significand;

if (unsignedSignificand > TDecimal.MaxSignificand && exponent > TDecimal.MaxDecimalExponent)
{
return significand > TSignificand.Zero ? TDecimal.PositiveInfinityBits : TDecimal.NegativeInfinityBits;
}

TSignificand ten = TSignificand.CreateTruncating(10);
if (exponent < TDecimal.MinDecimalExponent)
Copy link
Member

Choose a reason for hiding this comment

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

A lot of this logic is complex and doing things that aren't "immediately obvious" if you aren't familiar with the algorithm or format.

We need some comments inserted into the code to help explain, overall, what the goal of various blocks of code are. This helps lay out any invariants, any base math that is being done, and lets readers better assert that the code is doing what is intended.

This does not need to be "obvious" comments or repeat exactly what the code says. But should instead explain the overarching theme such as is done for https://source.dot.net/#System.Private.CoreLib/src/libraries/Common/src/System/Number.Parsing.Common.cs,116 or https://source.dot.net/#System.Private.CoreLib/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs,86

Something like the while loop below should similarly cover why it's reducing the significand to bring exponent >= MinDecimalExponent. Some of this is "more obvious" but should still be covered to help explain that's what the loop is doing. That is significand: 100000, exponent: Min - 2 is the same as significand: 1000, exponent: Min, or as another example 10e2 and 1e3 are the same number.

Other cases like why converting 100050e2 into 1000e4 is "safe" are not obvious and initially look to be potential bugs because of how that could theoretically impact rounding to the nearest representable.

{
while (unsignedSignificand >= ten)
{
unsignedSignificand /= ten;
++exponent;
}
if (exponent < TDecimal.MinDecimalExponent)
{
throw new OverflowException(SR.Overflow_Decimal);
}
}

if (unsignedSignificand > TDecimal.MaxSignificand)
{
int numberDigitsRemoving = TDecimal.CountDigits(unsignedSignificand) - TDecimal.NumberDigitsPrecision;

if (exponent + numberDigitsRemoving > TDecimal.MaxDecimalExponent)
{
throw new OverflowException(SR.Overflow_Decimal);
}

exponent += numberDigitsRemoving;
TSignificand two = TSignificand.CreateTruncating(2);
TSignificand divisor = TDecimal.Power10(numberDigitsRemoving);
TSignificand quotient = unsignedSignificand / divisor;
TSignificand remainder = unsignedSignificand % divisor;
TSignificand midPoint = divisor / two;
bool needRouding = remainder > midPoint || (remainder == midPoint && quotient % two == TSignificand.One);

if (needRouding && quotient == TDecimal.MaxSignificand && exponent < TDecimal.MaxDecimalExponent)
{
unsignedSignificand = TDecimal.Power10(TDecimal.NumberDigitsPrecision - 1);
exponent++;
}
else if (needRouding && quotient < TDecimal.MaxSignificand)
{
unsignedSignificand = quotient + TSignificand.One;
}
else
{
unsignedSignificand = quotient;
}
}
else if (exponent > TDecimal.MaxDecimalExponent)
{
int numberZeroDigits = exponent - TDecimal.MaxDecimalExponent;
int numberSignificandDigits = TDecimal.CountDigits(unsignedSignificand);

if (numberSignificandDigits + numberZeroDigits > TDecimal.NumberDigitsPrecision)
{
throw new OverflowException(SR.Overflow_Decimal);
}
unsignedSignificand *= TDecimal.Power10(numberZeroDigits);
exponent -= numberZeroDigits;
}

exponent += TDecimal.Bias;
bool msbSignificand = (unsignedSignificand & TSignificand.One << TDecimal.MostSignificantBitNumberOfSignificand) != TSignificand.Zero;

TValue value = TValue.Zero;
TValue exponentVal = TValue.CreateTruncating(exponent);
TValue significandVal = TValue.CreateTruncating(unsignedSignificand);

if (significand < TSignificand.Zero)
{
value = TValue.One << TDecimal.NumberBitsEncoding - 1;
}

if (msbSignificand)
{
value ^= TValue.One << TDecimal.NumberBitsEncoding - 2;
value ^= TValue.One << TDecimal.NumberBitsEncoding - 3;
exponentVal <<= TDecimal.NumberBitsEncoding - 4;
value ^= exponentVal;
significandVal <<= TDecimal.NumberBitsEncoding - TDecimal.MostSignificantBitNumberOfSignificand;
significandVal >>= TDecimal.NumberBitsCombinationField;
value ^= significandVal;
}
else
{
exponentVal <<= TDecimal.NumberBitsEncoding - TDecimal.NumberBitsExponent - 1;
value ^= exponentVal;
value ^= significandVal;
}

return value;
}

internal struct DecimalIeee754<TSignificand>
where TSignificand : IBinaryInteger<TSignificand>
{
public bool Signed { get; }
public int Exponent { get; }
public TSignificand Significand { get; }
Comment on lines 131 to 140
Copy link
Member

Choose a reason for hiding this comment

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

This and other places could likely be a bit more explicit, such as specifying whether this is the biased or unbiased exponent and which part of the significand it is (such as if its already undergone the BID encoding transforms)

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 the struct name and its property names to make it more readable


public DecimalIeee754(bool signed, int exponent, TSignificand significand)
{
Signed = signed;
Exponent = exponent;
Significand = significand;
}
}

internal static DecimalIeee754<TSignificand> UnpackDecimalIeee754<TDecimal, TSignificand, TValue>(TValue value)
where TDecimal : unmanaged, IDecimalIeee754UnpackInfo<TDecimal, TSignificand, TValue>
where TSignificand : IBinaryInteger<TSignificand>
where TValue : IBinaryInteger<TValue>
{
bool signed = (value & TDecimal.SignMask) != TValue.Zero;
Copy link
Member

Choose a reason for hiding this comment

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

Looks like value is rather decimalBits, which would make it clearer it's not some arbitrary integer

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

TValue g0g1Bits = (value << 1) >> TDecimal.NumberBitsEncoding - 2;
TSignificand significand;
int exponent;

if (g0g1Bits == TValue.CreateTruncating(3))
{
exponent = TDecimal.ConvertToExponent((value << 3) >> TDecimal.NumberBitsEncoding - TDecimal.NumberBitsExponent);
significand = TDecimal.ConvertToSignificand((value << TDecimal.NumberBitsEncoding + 3) >> TDecimal.NumberBitsEncoding + 3);
significand += TDecimal.TwoPowerMostSignificantBitNumberOfSignificand;
}
else
{
exponent = TDecimal.ConvertToExponent((value << 1) >> TDecimal.NumberBitsEncoding - TDecimal.NumberBitsExponent);
significand = TDecimal.ConvertToSignificand((value << TDecimal.NumberBitsExponent + 1) >> TDecimal.NumberBitsExponent + 1);
}

return new DecimalIeee754<TSignificand>(signed, exponent - TDecimal.Bias, significand);
}

internal static int CompareDecimalIeee754<TDecimal, TSignificand, TValue>(TValue currentValue, TValue otherValue)
where TDecimal : unmanaged, IDecimalIeee754UnpackInfo<TDecimal, TSignificand, TValue>
where TSignificand : IBinaryInteger<TSignificand>
where TValue : IBinaryInteger<TValue>
{
if (currentValue == otherValue)
{
return 0;
}
DecimalIeee754<TSignificand> current = UnpackDecimalIeee754<TDecimal, TSignificand, TValue>(currentValue);
DecimalIeee754<TSignificand> other = UnpackDecimalIeee754<TDecimal, TSignificand, TValue>(otherValue);

if (current.Signed && !other.Signed) return -1;

if (!current.Signed && other.Signed) return 1;

if (current.Exponent > other.Exponent)
{
return current.Signed ? -InternalUnsignedCompare(current, other) : InternalUnsignedCompare(current, other);
}

if (current.Exponent < other.Exponent)
{
return current.Signed ? InternalUnsignedCompare(other, current) : -InternalUnsignedCompare(current, other);
}

if (current.Significand == other.Significand) return 0;

if (current.Significand > other.Significand)
{
return current.Signed ? -1 : 1;
}
else
{
return current.Signed ? 1 : -1;
}

static int InternalUnsignedCompare(DecimalIeee754<TSignificand> biggerExp, DecimalIeee754<TSignificand> smallerExp)
{
if (biggerExp.Significand >= smallerExp.Significand) return 1;

int diffExponent = biggerExp.Exponent - smallerExp.Exponent;
if (diffExponent < TDecimal.NumberDigitsPrecision)
{
TSignificand factor = TDecimal.Power10(diffExponent);
TSignificand quotient = smallerExp.Significand / biggerExp.Significand;
TSignificand remainder = smallerExp.Significand % biggerExp.Significand;

if (quotient < factor) return 1;
if (quotient > factor) return -1;
if (remainder > TSignificand.Zero) return -1;
return 0;
}

return 1;
}
}
}
}
Loading