-
Notifications
You must be signed in to change notification settings - Fork 4.7k
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
Double to Decimal Conversion Refactor (WIP) #70602
Changes from all 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 |
---|---|---|
|
@@ -93,7 +93,6 @@ private ulong Low64 | |
private const int DEC_SCALE_MAX = 28; | ||
|
||
private const uint TenToPowerNine = 1000000000; | ||
private const ulong TenToPowerEighteen = 1000000000000000000; | ||
|
||
// The maximum power of 10 that a 32 bit integer can store | ||
private const int MaxInt32Scale = 9; | ||
|
@@ -1520,340 +1519,63 @@ internal static unsafe void VarDecMul(ref DecCalc d1, ref DecCalc d2) | |
/// <summary> | ||
/// Convert float to Decimal | ||
/// </summary> | ||
internal static void VarDecFromR4(float input, out DecCalc result) | ||
internal static void VarDecFromR4(float input, out decimal 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 | ||
// barely reach 0.5, but smaller exponents will always round to zero. | ||
// | ||
const uint SNGBIAS = 126; | ||
int exp = (int)(GetExponent(input) - SNGBIAS); | ||
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 7-digit integer. The R4 format has | ||
// only 7 digits of precision, and we want to keep garbage digits | ||
// out of the Decimal were making. | ||
// | ||
// 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 = 6 - ((exp * 19728) >> 16); | ||
// power is between -22 and 35 | ||
|
||
if (power >= 0) | ||
{ | ||
// We have less than 7 digits, scale input up. | ||
// | ||
if (power > DEC_SCALE_MAX) | ||
power = DEC_SCALE_MAX; | ||
|
||
dbl *= s_doublePowers10[power]; | ||
} | ||
else | ||
{ | ||
if (power != -1 || dbl >= 1E7) | ||
dbl /= s_doublePowers10[-power]; | ||
else | ||
power = 0; // didn't scale it | ||
} | ||
VarDecFromR8(input, out result); | ||
} | ||
|
||
Debug.Assert(dbl < 1E7); | ||
if (dbl < 1E6 && power < DEC_SCALE_MAX) | ||
{ | ||
dbl *= 10; | ||
power++; | ||
Debug.Assert(dbl >= 1E6); | ||
} | ||
/// <summary> | ||
/// Convert double to Decimal | ||
/// </summary> | ||
|
||
// Round to integer | ||
internal static void VarDecFromR8(double input, out decimal result) | ||
{ | ||
// 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. | ||
// | ||
uint mant; | ||
// with SSE4.1 support ROUNDSD can be used | ||
if (X86.Sse41.IsSupported) | ||
mant = (uint)(int)Math.Round(dbl); | ||
else | ||
if (input.Exponent < -94) | ||
{ | ||
mant = (uint)(int)dbl; | ||
dbl -= (int)mant; // difference between input & integer | ||
if (dbl > 0.5 || dbl == 0.5 && (mant & 1) != 0) | ||
mant++; | ||
} | ||
|
||
if (mant == 0) | ||
return; // result should be zeroed out | ||
|
||
if (power < 0) | ||
{ | ||
// Add -power factors of 10, -power <= (29 - 7) = 22. | ||
// | ||
power = -power; | ||
if (power < 10) | ||
// If the input is actually zero, we return the smallest precision zero | ||
if (input == double.Zero) | ||
{ | ||
result.Low64 = UInt32x32To64(mant, s_powers10[power]); | ||
result = double.IsPositive(input) ? decimal.Zero : new decimal(0, 0, 0, unchecked((int)SignMask)); | ||
} | ||
else | ||
{ | ||
// Have a big power of 10. | ||
// | ||
if (power > 18) | ||
{ | ||
ulong low64 = UInt32x32To64(mant, s_powers10[power - 18]); | ||
UInt64x64To128(low64, TenToPowerEighteen, ref result); | ||
} | ||
else | ||
{ | ||
ulong low64 = UInt32x32To64(mant, s_powers10[power - 9]); | ||
ulong hi64 = UInt32x32To64(TenToPowerNine, (uint)(low64 >> 32)); | ||
low64 = UInt32x32To64(TenToPowerNine, (uint)low64); | ||
result.Low = (uint)low64; | ||
hi64 += low64 >> 32; | ||
result.Mid = (uint)hi64; | ||
hi64 >>= 32; | ||
result.High = (uint)hi64; | ||
} | ||
} | ||
} | ||
else | ||
{ | ||
// Factor out powers of 10 to reduce the scale, if possible. | ||
// The maximum number we could factor out would be 6. This | ||
// comes from the fact we have a 7-digit number, and the | ||
// MSD must be non-zero -- but the lower 6 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 > 6) | ||
lmax = 6; | ||
|
||
if ((mant & 0xF) == 0 && lmax >= 4) | ||
{ | ||
const uint den = 10000; | ||
uint div = mant / den; | ||
if (mant == div * den) | ||
// Otherwise, we return the maximum precision version of zero or negative zero | ||
uint zeroFlags = 0; | ||
if (double.IsNegative(input)) | ||
{ | ||
mant = div; | ||
power -= 4; | ||
lmax -= 4; | ||
zeroFlags = SignMask; | ||
} | ||
zeroFlags |= 28 << ScaleShift; | ||
result = new decimal(0, 0, 0, (int)zeroFlags); | ||
} | ||
|
||
if ((mant & 3) == 0 && lmax >= 2) | ||
{ | ||
const uint den = 100; | ||
uint div = mant / den; | ||
if (mant == div * den) | ||
{ | ||
mant = div; | ||
power -= 2; | ||
lmax -= 2; | ||
} | ||
} | ||
|
||
if ((mant & 1) == 0 && lmax >= 1) | ||
{ | ||
const uint den = 10; | ||
uint div = mant / den; | ||
if (mant == div * den) | ||
{ | ||
mant = div; | ||
power--; | ||
} | ||
} | ||
|
||
flags |= (uint)power << ScaleShift; | ||
result.Low = mant; | ||
return; | ||
} | ||
|
||
result.uflags = flags; | ||
} | ||
|
||
/// <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 | ||
// barely reach 0.5, but smaller exponents will always round to zero. | ||
// The smallest double with an exponent of 96 is just over decimal.MaxValue. This | ||
// means that an exponent of 96 and above should overflow. | ||
// | ||
const uint DBLBIAS = 1022; | ||
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 conceptually incorrect, as possibly even logically incorrect |
||
int exp = (int)(GetExponent(input) - DBLBIAS); | ||
if (exp < -94) | ||
return; // result should be zeroed out | ||
|
||
if (exp > 96) | ||
if (input.Exponent >= 96) | ||
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. |
||
{ | ||
Number.ThrowOverflowException(TypeCode.Decimal); | ||
} | ||
|
||
uint flags = 0; | ||
if (input < 0) | ||
if (double.IsNegative(input)) | ||
{ | ||
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
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) | ||
{ | ||
// 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); | ||
} | ||
|
||
// Round to int64 | ||
// | ||
ulong mant; | ||
// with SSE4.1 support ROUNDSD can be used | ||
if (X86.Sse41.IsSupported) | ||
mant = (ulong)(long)Math.Round(dbl); | ||
else | ||
{ | ||
mant = (ulong)(long)dbl; | ||
dbl -= (long)mant; // difference between input & integer | ||
if (dbl > 0.5 || dbl == 0.5 && (mant & 1) != 0) | ||
mant++; | ||
} | ||
|
||
if (mant == 0) | ||
return; // result should be zeroed out | ||
|
||
if (power < 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); | ||
} | ||
} | ||
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; | ||
} | ||
} | ||
(uint low, uint mid, uint high, uint scale) = Number.Dragon4DoubleToDecimal(input, 29, true); | ||
|
||
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; | ||
} | ||
flags |= scale << ScaleShift; | ||
|
||
result.uflags = flags; | ||
// Construct the decimal and canonicalize it, removing extra trailing zeros with a division | ||
result = new decimal((int)low, (int)mid, (int)high, (int)flags) / 1.0000000000000000000000000000m; | ||
} | ||
|
||
/// <summary> | ||
|
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.
I'm using the
Exponent
exposed ondouble
now. I think this is more correct than the original comparison.