-
Notifications
You must be signed in to change notification settings - Fork 5k
Add Decimal32, Decimal64, Decimal128 #100729
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 7 commits
ac5e447
1af3337
cee05c8
da72dcd
6f3827c
7e9344b
f882b96
762f56d
66a1127
83937d2
19755ba
ede6ca3
b4e5d34
504f042
31ffd5c
6212873
cb1d8b1
0a4620e
8f3d4ff
db834cb
2ed126b
d08b7b5
bcfdca6
472a2fe
49f6676
f82d9a6
567906c
70cc09d
715677f
a66d209
2405a15
926db20
d41c56e
ac8f7a6
9d0fa8e
4478f54
7b1db43
79bea65
e9a5cf7
0a57a1a
ab7c5fb
6a1c2c3
e584a92
84ef494
61eaba8
2a1fc70
afc15e4
002d9dd
0d598a2
313c90d
7b6a3c0
d93ac2b
c44003e
9533b39
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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; } | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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> | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
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) | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
where TDecimal : unmanaged, IDecimalIeee754ConstructorInfo<TDecimal, TSignificand, TValue> | ||
where TSignificand : IBinaryInteger<TSignificand> | ||
where TValue : IBinaryInteger<TValue> | ||
{ | ||
if (significand == TSignificand.Zero) | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
return TValue.Zero; | ||
} | ||
|
||
TSignificand unsignedSignificand = significand > TSignificand.Zero ? significand : -significand; | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (unsignedSignificand > TDecimal.MaxSignificand && exponent > TDecimal.MaxDecimalExponent) | ||
{ | ||
return significand > TSignificand.Zero ? TDecimal.PositiveInfinityBits : TDecimal.NegativeInfinityBits; | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
TSignificand ten = TSignificand.CreateTruncating(10); | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (exponent < TDecimal.MinDecimalExponent) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 Other cases like why converting |
||
{ | ||
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) | ||
stephentoub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
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; | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
TSignificand midPoint = divisor / two; | ||
bool needRouding = remainder > midPoint || (remainder == midPoint && quotient % two == TSignificand.One); | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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) | ||
stephentoub marked this conversation as resolved.
Show resolved
Hide resolved
|
||
{ | ||
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; } | ||
|
||
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; | ||
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; | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (current.Exponent > other.Exponent) | ||
{ | ||
return current.Signed ? -InternalUnsignedCompare(current, other) : InternalUnsignedCompare(current, other); | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
if (current.Exponent < other.Exponent) | ||
{ | ||
return current.Signed ? InternalUnsignedCompare(other, current) : -InternalUnsignedCompare(current, other); | ||
} | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
if (current.Significand == other.Significand) return 0; | ||
|
||
if (current.Significand > other.Significand) | ||
{ | ||
return current.Signed ? -1 : 1; | ||
} | ||
else | ||
{ | ||
return current.Signed ? 1 : -1; | ||
} | ||
RaymondHuy marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
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; | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment explaining why these numbers are correct would be beneficial.
There's many resources out there that break down the algorithm for how you get to
767
digits for the longest double (or11563
forbinary128
), as it is:But the algorithm for
decimal
isn't as well known (but should overall be similar).There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi @tannergooding , I have tried your formula but I can't get the
767
result.MaxUnbiasedExponent = 1023, SignificandBits = 52
result is
1023 + (−309) + 1= 715
.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, I did it from memory initially. It should be
(MaxUnbiasedExponent + TrailingSignificandBits - 1)
So for
double
it is1074 + ⌊log10((2^53 – 1) / 2^1074)⌋ + 1
. This is because the largest subnormal expands to a value that is 1074 digits in length (which is why in many comments and other places you'll see 1074 and 1075 being used)