return DecCalc.DecDivMod1E9(ref AsMutable(ref value));
}
- internal static void DecAddInt32(ref decimal value, uint i)
- {
- DecCalc.DecAddInt32(ref AsMutable(ref value), i);
- }
-
- internal static void DecMul10(ref decimal value)
- {
- DecCalc.DecMul10(ref AsMutable(ref value));
- }
-
#endregion
/// <summary>
return;
}
-#region Number Formatting helpers
-
internal static uint DecDivMod1E9(ref DecCalc value)
{
ulong high64 = ((ulong)value.uhi << 32) + value.umid;
return (uint)num - div * TenToPowerNine;
}
- internal static void DecAddInt32(ref DecCalc value, uint i)
- {
- if (D32AddCarry(ref value.ulo, i))
- {
- if (D32AddCarry(ref value.umid, 1))
- D32AddCarry(ref value.uhi, 1);
- }
- }
-
- private static bool D32AddCarry(ref uint value, uint i)
- {
- uint v = value;
- uint sum = v + i;
- value = sum;
- return (sum < v) || (sum < i);
- }
-
- internal static void DecMul10(ref DecCalc value)
- {
- DecCalc d = value;
- DecShiftLeft(ref value);
- DecShiftLeft(ref value);
- DecAdd(ref value, d);
- DecShiftLeft(ref value);
- }
-
- private static void DecShiftLeft(ref DecCalc value)
- {
- uint c0 = (value.Low & 0x80000000) != 0 ? 1u : 0u;
- uint c1 = (value.Mid & 0x80000000) != 0 ? 1u : 0u;
- value.Low = value.Low << 1;
- value.Mid = (value.Mid << 1) | c0;
- value.High = (value.High << 1) | c1;
- }
-
- private static void DecAdd(ref DecCalc value, DecCalc d)
- {
- if (D32AddCarry(ref value.ulo, d.Low))
- {
- if (D32AddCarry(ref value.umid, 1))
- D32AddCarry(ref value.uhi, 1);
- }
-
- if (D32AddCarry(ref value.umid, d.Mid))
- D32AddCarry(ref value.uhi, 1);
-
- D32AddCarry(ref value.uhi, d.High);
- }
-
-#endregion
-
struct PowerOvfl
{
public readonly uint Hi;
private static unsafe bool NumberBufferToDecimal(ref NumberBuffer number, ref decimal value)
{
- decimal d = new decimal();
-
char* p = number.digits;
int e = number.scale;
- if (*p == 0)
+ bool sign = number.sign;
+ uint c = *p;
+ if (c == 0)
{
// To avoid risking an app-compat issue with pre 4.5 (where some app was illegally using Reflection to examine the internal scale bits), we'll only force
// the scale to 0 if the scale was previously positive (previously, such cases were unparsable to a bug.)
- if (e > 0)
+ value = new decimal(0, 0, 0, sign, (byte)Math.Clamp(-e, 0, 28));
+ return true;
+ }
+
+ if (e > DecimalPrecision)
+ return false;
+
+ ulong low64 = 0;
+ while (e > -28)
+ {
+ e--;
+ low64 *= 10;
+ low64 += c - '0';
+ c = *++p;
+ if (low64 >= ulong.MaxValue / 10)
+ break;
+ if (c == 0)
{
- e = 0;
+ while (e > 0)
+ {
+ e--;
+ low64 *= 10;
+ if (low64 >= ulong.MaxValue / 10)
+ break;
+ }
+ break;
}
}
- else
+
+ uint high = 0;
+ while ((e > 0 || (c != 0 && e > -28)) &&
+ (high < uint.MaxValue / 10 || (high == uint.MaxValue / 10 && (low64 < 0x99999999_99999999 || (low64 == 0x99999999_99999999 && c <= '5')))))
{
- if (e > DecimalPrecision)
- return false;
+ // multiply by 10
+ ulong tmpLow = (uint)low64 * 10UL;
+ ulong tmp64 = (uint)(low64 >> 32) * 10UL + (tmpLow >> 32);
+ low64 = (uint)tmpLow + (tmp64 << 32);
+ high = (uint)(tmp64 >> 32) + high * 10;
- while (((e > 0) || ((*p != 0) && (e > -28))) &&
- ((d.High < 0x19999999) || ((d.High == 0x19999999) &&
- ((d.Mid < 0x99999999) || ((d.Mid == 0x99999999) &&
- ((d.Low < 0x99999999) || ((d.Low == 0x99999999) &&
- (*p <= '5'))))))))
+ if (c != 0)
{
- decimal.DecMul10(ref d);
- if (*p != 0)
- decimal.DecAddInt32(ref d, (uint)(*p++ - '0'));
- e--;
+ c -= '0';
+ low64 += c;
+ if (low64 < c)
+ high++;
+ c = *++p;
}
+ e--;
+ }
- if (*p++ >= '5')
+ if (c >= '5')
+ {
+ // If the next digit is 5, round up if the number is odd or any following digit is non-zero
+ if (c == '5' && (low64 & 1) == 0)
{
- bool round = true;
- if ((*(p - 1) == '5') && ((*(p - 2) % 2) == 0))
+ c = *++p;
+ int count = 20; // Look at the next 20 digits to check to round
+ while (c == '0' && count != 0)
{
- // Check if previous digit is even, only if the when we are unsure whether hows to do
- // Banker's rounding. For digits > 5 we will be roundinp up anyway.
- int count = 20; // Look at the next 20 digits to check to round
- while ((*p == '0') && (count != 0))
- {
- p++;
- count--;
- }
- if ((*p == '\0') || (count == 0))
- round = false;// Do nothing
+ c = *++p;
+ count--;
}
+ if (c == 0 || count == 0)
+ goto NoRounding;// Do nothing
+ }
- if (round)
- {
- decimal.DecAddInt32(ref d, 1);
- if ((d.High | d.Mid | d.Low) == 0)
- {
- d = new decimal(unchecked((int)0x9999999A), unchecked((int)0x99999999), 0x19999999, false, 0);
- e++;
- }
- }
+ if (++low64 == 0 && ++high == 0)
+ {
+ low64 = 0x99999999_9999999A;
+ high = uint.MaxValue / 10;
+ e++;
}
}
+ NoRounding:
if (e > 0)
return false;
{
// Parsing a large scale zero can give you more precision than fits in the decimal.
// This should only happen for actual zeros or very small numbers that round to zero.
- value = new decimal(0, 0, 0, number.sign, DecimalPrecision - 1);
+ value = new decimal(0, 0, 0, sign, DecimalPrecision - 1);
}
else
{
- value = new decimal((int)d.Low, (int)d.Mid, (int)d.High, number.sign, (byte)-e);
+ value = new decimal((int)low64, (int)(low64 >> 32), (int)high, sign, (byte)-e);
}
return true;
}