-
Notifications
You must be signed in to change notification settings - Fork 5.2k
Double to Decimal Conversion Refactor (WIP) #70602
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
Changes from 14 commits
391f805
a6dc156
edc3f67
cccae84
5a19f23
b0721b8
d213837
728efa1
31820e3
1df3d17
246483c
40fcdd5
7e6cd93
e77161f
29bac4b
94b874b
6525508
895b413
5d9edfa
e6dbd62
2d191a1
47ac650
6db7625
1d38279
1c3556d
ceccdb5
2030e68
f1d0451
8a28c84
6a7266c
a62767d
23b71de
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 |
|---|---|---|
| @@ -1,6 +1,7 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using Internal; | ||
| using System.Diagnostics; | ||
| using System.Numerics; | ||
| using System.Runtime.CompilerServices; | ||
|
|
@@ -1687,173 +1688,41 @@ internal static void VarDecFromR4(float input, out DecCalc result) | |
| /// <summary> | ||
| /// Convert double to Decimal | ||
| /// </summary> | ||
|
|
||
| internal static void VarDecFromR8(double input, out DecCalc result) | ||
| { | ||
| result = default; | ||
|
|
||
| // The most we can scale by is 10^28, which is just slightly more | ||
| // than 2^93. So a float with an exponent of -94 could just | ||
| // The smallest non-zero decimal we can represent is 10^-28, which is just slightly more | ||
| // than 2^-93. So a float with an exponent of -94 could just | ||
| // barely reach 0.5, but smaller exponents will always round to zero. | ||
| // This is a shortcut to catch doubles that are too small efficiently. | ||
| // | ||
| const uint DBLBIAS = 1022; | ||
| int exp = (int)(GetExponent(input) - DBLBIAS); | ||
| if (exp < -94) | ||
| return; // result should be zeroed out | ||
|
|
||
| if (exp > 96) | ||
| Number.ThrowOverflowException(TypeCode.Decimal); | ||
|
|
||
| uint flags = 0; | ||
| if (input < 0) | ||
| { | ||
| input = -input; | ||
| flags = SignMask; | ||
| } | ||
|
|
||
| // Round the input to a 15-digit integer. The R8 format has | ||
| // only 15 digits of precision, and we want to keep garbage digits | ||
| // out of the Decimal were making. | ||
|
Comment on lines
-1713
to
-1715
Contributor
Author
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. As mentioned, this is incorrect, and the cause of the original bug |
||
| // | ||
| // Calculate max power of 10 input value could have by multiplying | ||
| // the exponent by log10(2). Using scaled integer multiplcation, | ||
| // log10(2) * 2 ^ 16 = .30103 * 65536 = 19728.3. | ||
| // | ||
| double dbl = input; | ||
| int power = 14 - ((exp * 19728) >> 16); | ||
| // power is between -14 and 43 | ||
|
|
||
| if (power >= 0) | ||
| if (input.Exponent < -94) | ||
|
Contributor
Author
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. I'm using the |
||
| { | ||
| // We have less than 15 digits, scale input up. | ||
| // | ||
| if (power > DEC_SCALE_MAX) | ||
| power = DEC_SCALE_MAX; | ||
|
|
||
| dbl *= s_doublePowers10[power]; | ||
| } | ||
| else | ||
| { | ||
| if (power != -1 || dbl >= 1E15) | ||
| dbl /= s_doublePowers10[-power]; | ||
| else | ||
| power = 0; // didn't scale it | ||
| } | ||
|
|
||
| Debug.Assert(dbl < 1E15); | ||
| if (dbl < 1E14 && power < DEC_SCALE_MAX) | ||
| { | ||
| dbl *= 10; | ||
| power++; | ||
| Debug.Assert(dbl >= 1E14); | ||
| return; // result should be zeroed out | ||
| } | ||
|
|
||
| // Round to int64 | ||
| // The smallest double with an exponent of 96 is just over decimal.MaxValue. This | ||
| // means that an exponent of 96 and above should overflow. | ||
| // | ||
| ulong mant; | ||
| // with SSE4.1 support ROUNDSD can be used | ||
| if (X86.Sse41.IsSupported) | ||
| mant = (ulong)(long)Math.Round(dbl); | ||
| else | ||
| if (input.Exponent >= 96) | ||
|
Contributor
Author
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. This should prevent all overflowing doubles from making it to Dragon4. |
||
| { | ||
| mant = (ulong)(long)dbl; | ||
| dbl -= (long)mant; // difference between input & integer | ||
| if (dbl > 0.5 || dbl == 0.5 && (mant & 1) != 0) | ||
| mant++; | ||
| Number.ThrowOverflowException(TypeCode.Decimal); | ||
| } | ||
|
|
||
| if (mant == 0) | ||
| return; // result should be zeroed out | ||
|
|
||
| if (power < 0) | ||
| uint flags = 0; | ||
| if (input < 0) | ||
| { | ||
| // Add -power factors of 10, -power <= (29 - 15) = 14. | ||
| // | ||
| power = -power; | ||
| if (power < 10) | ||
| { | ||
| uint pow10 = s_powers10[power]; | ||
| ulong low64 = UInt32x32To64((uint)mant, pow10); | ||
| ulong hi64 = UInt32x32To64((uint)(mant >> 32), pow10); | ||
| result.Low = (uint)low64; | ||
| hi64 += low64 >> 32; | ||
| result.Mid = (uint)hi64; | ||
| hi64 >>= 32; | ||
| result.High = (uint)hi64; | ||
| } | ||
| else | ||
| { | ||
| // Have a big power of 10. | ||
| // | ||
| Debug.Assert(power <= 14); | ||
| UInt64x64To128(mant, s_ulongPowers10[power - 1], ref result); | ||
| } | ||
| flags = SignMask; | ||
| } | ||
| else | ||
| { | ||
| // Factor out powers of 10 to reduce the scale, if possible. | ||
| // The maximum number we could factor out would be 14. This | ||
| // comes from the fact we have a 15-digit number, and the | ||
| // MSD must be non-zero -- but the lower 14 digits could be | ||
| // zero. Note also the scale factor is never negative, so | ||
| // we can't scale by any more than the power we used to | ||
| // get the integer. | ||
| // | ||
| int lmax = power; | ||
| if (lmax > 14) | ||
| lmax = 14; | ||
|
|
||
| if ((byte)mant == 0 && lmax >= 8) | ||
| { | ||
| const uint den = 100000000; | ||
| ulong div = mant / den; | ||
| if ((uint)mant == (uint)(div * den)) | ||
| { | ||
| mant = div; | ||
| power -= 8; | ||
| lmax -= 8; | ||
| } | ||
| } | ||
|
|
||
| if (((uint)mant & 0xF) == 0 && lmax >= 4) | ||
| { | ||
| const uint den = 10000; | ||
| ulong div = mant / den; | ||
| if ((uint)mant == (uint)(div * den)) | ||
| { | ||
| mant = div; | ||
| power -= 4; | ||
| lmax -= 4; | ||
| } | ||
| } | ||
|
|
||
| if (((uint)mant & 3) == 0 && lmax >= 2) | ||
| { | ||
| const uint den = 100; | ||
| ulong div = mant / den; | ||
| if ((uint)mant == (uint)(div * den)) | ||
| { | ||
| mant = div; | ||
| power -= 2; | ||
| lmax -= 2; | ||
| } | ||
| } | ||
|
|
||
| if (((uint)mant & 1) == 0 && lmax >= 1) | ||
| { | ||
| const uint den = 10; | ||
| ulong div = mant / den; | ||
| if ((uint)mant == (uint)(div * den)) | ||
| { | ||
| mant = div; | ||
| power--; | ||
| } | ||
| } | ||
|
|
||
| flags |= (uint)power << ScaleShift; | ||
| result.Low64 = mant; | ||
| } | ||
| (result.Low, result.Mid, result.High, uint scale) = Number.Dragon4DoubleToDecimal(input, 29, true); | ||
|
|
||
| flags |= scale << ScaleShift; | ||
| result.uflags = flags; | ||
|
|
||
| return; | ||
| } | ||
|
|
||
| /// <summary> | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -20,8 +20,8 @@ namespace System | |||||
| // require a large number of significant digits and no round-off errors. | ||||||
| // | ||||||
| // The finite set of values of type Decimal are of the form m | ||||||
| // / 10e, where m is an integer such that | ||||||
| // -296 <; m <; 296, and e is an integer | ||||||
| // / 10^e, where m is an integer such that | ||||||
| // -2^96 <; m <; 2^96, and e is an integer | ||||||
|
||||||
| // -2^96 <; m <; 2^96, and e is an integer | |
| // -2^96 <= m <= 2^96, and e is an integer |
Is there something I'm missing about the use of ; here?
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.
This comment got corrupted in June 2002. Originally it looked this way:
* <p>The finite set of values of type <i>Decimal</i> are of the form <i>m</i>
* / 10<sup><i>e</i></sup>, where <i>m</i> is an integer such that
* -2<sup>96</sup> < <i>m</i> < 2<sup>96</sup>, and <i>e</i> is an integer
* between 0 and 28 inclusive.
You should use <, not <=. Also please move / 10^e to the previous line.
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.
As mentioned, this is conceptually incorrect, as possibly even logically incorrect