summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTanner Gooding <tagoo@outlook.com>2018-11-07 20:23:49 -0800
committerGitHub <noreply@github.com>2018-11-07 20:23:49 -0800
commitb30280d98e6c8e1980173a256609a0bde7f9c83d (patch)
tree43562a77917da61f08d08f893421ab24b129fe84
parent002603e22fe881fc501b344910740a529bed0d9c (diff)
downloadcoreclr-b30280d98e6c8e1980173a256609a0bde7f9c83d.tar.gz
coreclr-b30280d98e6c8e1980173a256609a0bde7f9c83d.tar.bz2
coreclr-b30280d98e6c8e1980173a256609a0bde7f9c83d.zip
Fixing up the Double/Single parsing code to be correct (#20707)
* Don't normalize -0.0 to 0.0 when parsing * Updating NumberBuffer to validate the constructor inputs * Updating NumberToDouble to just get the precision * Don't check for non-significant zero's in NumberToDouble * Updating Number.BigInteger to carry additional space for the worst-case scenario * Removing some dead code from double.TryParse * Updating NumberToDouble to use the RealParser implementation from Roslyn * Fixing TryNumberToDouble and TryNumberToSingle to apply the appropriate sign. * Adding a fast path for double/single parsing when we have <= 15 digits and an absolute scale <= 22 * Update NumberBuffer to also track whether any input digits past maxDigCount were non-zero * Renaming NumberToFloatingPointBitsRoslyn to NumberToFloatingPointBits * Updating TryNumberToDouble and TryNumberToSingle to support Overflow to Infinity * Fixing a Debug.Assert in TryParseNumber * Fixing `DecimalNumberBufferLength` to 30 * Renaming NumberToFloatingPointBitsRoslyn to NumberToFloatingPointBits * Clarifying the NumberBufferLength comments * Fixing TryNumberToDecimal to check the last digit in the buffer, if it exists * Disable some CoreFX tests due to the single/double/decimal parsing fixes * Fix TryNumberToDecimal to not modify the value of p in the assert. Co-Authored-By: tannergooding <tagoo@outlook.com> * Updating NumberToFloatingPointBits to use single-precision arithmetic and extended-precision multiplication where possible * Splitting the NumberToFloatingPointBits code into a fast and slow-path method * Ensure Roslyn is properly attributed. * Removing the 80-bit extended precision fast path for NumberToFloatingPointBits, due to a bug * Fixing the double and single parser to ignore case for Infinity and NaN * Add a clarifying comment to Number.NumberToFloatingPointBits that the code has been modified from the original source. * Removing the remaining code that was used by the 80-bit extended precision fast-path in NumberToFloatingPointBits * Adding a missing comma to the CoreFX.issues.json * Remove licensing "glue" and re-release the Roslyn RealParser code under the MIT license. * Some minor cleanup to the NumberToFloatingPointBits code.
-rw-r--r--src/System.Private.CoreLib/Resources/Strings.resx6
-rw-r--r--src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems2
-rw-r--r--src/System.Private.CoreLib/shared/System/Double.cs23
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.BigInteger.cs617
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.Dragon4.cs8
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.Formatting.cs12
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs21
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs449
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs621
-rw-r--r--src/System.Private.CoreLib/shared/System/Number.Parsing.cs133
-rw-r--r--src/System.Private.CoreLib/shared/System/Single.cs2
-rw-r--r--tests/CoreFX/CoreFX.issues.json90
12 files changed, 1407 insertions, 577 deletions
diff --git a/src/System.Private.CoreLib/Resources/Strings.resx b/src/System.Private.CoreLib/Resources/Strings.resx
index 0349819347..b1f3b01786 100644
--- a/src/System.Private.CoreLib/Resources/Strings.resx
+++ b/src/System.Private.CoreLib/Resources/Strings.resx
@@ -3100,9 +3100,6 @@
<data name="Overflow_Decimal" xml:space="preserve">
<value>Value was either too large or too small for a Decimal.</value>
</data>
- <data name="Overflow_Double" xml:space="preserve">
- <value>Value was either too large or too small for a Double.</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>
@@ -3124,9 +3121,6 @@
<data name="Overflow_SByte" xml:space="preserve">
<value>Value was either too large or too small for a signed byte.</value>
</data>
- <data name="Overflow_Single" xml:space="preserve">
- <value>Value was either too large or too small for a Single.</value>
- </data>
<data name="Overflow_TimeSpanElementTooLarge" xml:space="preserve">
<value>The TimeSpan string '{0}' could not be parsed because at least one of the numeric components is out of range or contains too many digits.</value>
</data>
diff --git a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
index c3ef9a3211..050591fd91 100644
--- a/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
+++ b/src/System.Private.CoreLib/shared/System.Private.CoreLib.Shared.projitems
@@ -294,7 +294,7 @@
<Compile Include="$(MSBuildThisFileDirectory)System\Number.Formatting.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Number.Grisu3.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Number.NumberBuffer.cs" />
- <Compile Include="$(MSBuildThisFileDirectory)System\Number.NumberToDouble.cs" />
+ <Compile Include="$(MSBuildThisFileDirectory)System\Number.NumberToFloatingPointBits.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\Number.Parsing.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\NullReferenceException.cs" />
<Compile Include="$(MSBuildThisFileDirectory)System\ObjectDisposedException.cs" />
diff --git a/src/System.Private.CoreLib/shared/System/Double.cs b/src/System.Private.CoreLib/shared/System/Double.cs
index 5a94551410..885f005a21 100644
--- a/src/System.Private.CoreLib/shared/System/Double.cs
+++ b/src/System.Private.CoreLib/shared/System/Double.cs
@@ -341,28 +341,7 @@ namespace System
private static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out double result)
{
- bool success = Number.TryParseDouble(s, style, info, out result, out _);
- if (!success)
- {
- ReadOnlySpan<char> sTrim = s.Trim();
- if (sTrim.EqualsOrdinal(info.PositiveInfinitySymbol))
- {
- result = PositiveInfinity;
- }
- else if (sTrim.EqualsOrdinal(info.NegativeInfinitySymbol))
- {
- result = NegativeInfinity;
- }
- else if (sTrim.EqualsOrdinal(info.NaNSymbol))
- {
- result = NaN;
- }
- else
- {
- return false; // We really failed
- }
- }
- return true;
+ return Number.TryParseDouble(s, style, info, out result);
}
//
diff --git a/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs b/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs
index 2599823c44..1231b55bd6 100644
--- a/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs
+++ b/src/System.Private.CoreLib/shared/System/Number.BigInteger.cs
@@ -13,7 +13,25 @@ namespace System
[StructLayout(LayoutKind.Sequential, Pack = 1)]
internal unsafe ref struct BigInteger
{
- private const int MaxBlockCount = 35;
+ // The longest binary mantissa requires: explicit mantissa bits + abs(min exponent)
+ // * Half: 10 + 14 = 24
+ // * Single: 23 + 126 = 149
+ // * Double: 52 + 1022 = 1074
+ // * Quad: 112 + 16382 = 16494
+ private const int BitsForLongestBinaryMantissa = 1074;
+
+ // The longest digit sequence requires: ceil(log2(pow(10, max significant digits + 1 rounding digit)))
+ // * Half: ceil(log2(pow(10, 21 + 1))) = 74
+ // * Single: ceil(log2(pow(10, 112 + 1))) = 376
+ // * Double: ceil(log2(pow(10, 767 + 1))) = 2552
+ // * Quad: ceil(log2(pow(10, 11563 + 1))) = 38415
+ private const int BitsForLongestDigitSequence = 2552;
+
+ // We require BitsPerBlock additional bits for shift space used during the pre-division preparation
+ private const int MaxBits = BitsForLongestBinaryMantissa + BitsForLongestDigitSequence + BitsPerBlock;
+
+ private const int BitsPerBlock = sizeof(int) * 8;
+ private const int MaxBlockCount = (MaxBits + (BitsPerBlock - 1)) / BitsPerBlock;
private static readonly uint[] s_Pow10UInt32Table = new uint[]
{
@@ -27,7 +45,7 @@ namespace System
10000000, // 10^7
};
- private static readonly int[] s_s_Pow10BigNumTableIndices = new int[]
+ private static readonly int[] s_Pow10BigNumTableIndices = new int[]
{
0, // 10^8
2, // 10^16
@@ -35,6 +53,8 @@ namespace System
10, // 10^64
18, // 10^128
33, // 10^256
+ 61, // 10^512
+ 116, // 10^1024
};
private static readonly uint[] s_Pow10BigNumTable = new uint[]
@@ -112,7 +132,175 @@ namespace System
0x5FDCEFCE,
0x000553F7,
- // Trailing blocks to ensure MaxBlockCount
+ // 10^512
+ 54, // _length
+ 0x00000000, // _blocks
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0xFC6CF801,
+ 0x77F27267,
+ 0x8F9546DC,
+ 0x5D96976F,
+ 0xB83A8A97,
+ 0xC31E1AD9,
+ 0x46C40513,
+ 0x94E65747,
+ 0xC88976C1,
+ 0x4475B579,
+ 0x28F8733B,
+ 0xAA1DA1BF,
+ 0x703ED321,
+ 0x1E25CFEA,
+ 0xB21A2F22,
+ 0xBC51FB2E,
+ 0x96E14F5D,
+ 0xBFA3EDAC,
+ 0x329C57AE,
+ 0xE7FC7153,
+ 0xC3FC0695,
+ 0x85A91924,
+ 0xF95F635E,
+ 0xB2908EE0,
+ 0x93ABADE4,
+ 0x1366732A,
+ 0x9449775C,
+ 0x69BE5B0E,
+ 0x7343AFAC,
+ 0xB099BC81,
+ 0x45A71D46,
+ 0xA2699748,
+ 0x8CB07303,
+ 0x8A0B1F13,
+ 0x8CAB8A97,
+ 0xC1D238D9,
+ 0x633415D4,
+ 0x0000001C,
+
+ // 10^1024
+ 107, // _length
+ 0x00000000, // _blocks
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x00000000,
+ 0x2919F001,
+ 0xF55B2B72,
+ 0x6E7C215B,
+ 0x1EC29F86,
+ 0x991C4E87,
+ 0x15C51A88,
+ 0x140AC535,
+ 0x4C7D1E1A,
+ 0xCC2CD819,
+ 0x0ED1440E,
+ 0x896634EE,
+ 0x7DE16CFB,
+ 0x1E43F61F,
+ 0x9FCE837D,
+ 0x231D2B9C,
+ 0x233E55C7,
+ 0x65DC60D7,
+ 0xF451218B,
+ 0x1C5CD134,
+ 0xC9635986,
+ 0x922BBB9F,
+ 0xA7E89431,
+ 0x9F9F2A07,
+ 0x62BE695A,
+ 0x8E1042C4,
+ 0x045B7A74,
+ 0x1ABE1DE3,
+ 0x8AD822A5,
+ 0xBA34C411,
+ 0xD814B505,
+ 0xBF3FDEB3,
+ 0x8FC51A16,
+ 0xB1B896BC,
+ 0xF56DEEEC,
+ 0x31FB6BFD,
+ 0xB6F4654B,
+ 0x101A3616,
+ 0x6B7595FB,
+ 0xDC1A47FE,
+ 0x80D98089,
+ 0x80BDA5A5,
+ 0x9A202882,
+ 0x31EB0F66,
+ 0xFC8F1F90,
+ 0x976A3310,
+ 0xE26A7B7E,
+ 0xDF68368A,
+ 0x3CE3A0B8,
+ 0x8E4262CE,
+ 0x75A351A2,
+ 0x6CB0B6C9,
+ 0x44597583,
+ 0x31B5653F,
+ 0xC356E38A,
+ 0x35FAABA6,
+ 0x0190FBA0,
+ 0x9FC4ED52,
+ 0x88BC491B,
+ 0x1640114A,
+ 0x005B8041,
+ 0xF4F3235E,
+ 0x1E8D4649,
+ 0x36A8DE06,
+ 0x73C55349,
+ 0xA7E6BD2A,
+ 0xC1A6970C,
+ 0x47187094,
+ 0xD2DB49EF,
+ 0x926C3F5B,
+ 0xAE6209D4,
+ 0x2D433949,
+ 0x34F4A3C6,
+ 0xD4305D94,
+ 0xD9D61A05,
+ 0x00000325,
+
+ // 9 Trailing blocks to ensure MaxBlockCount
+ 0x00000000,
0x00000000,
0x00000000,
0x00000000,
@@ -149,19 +337,39 @@ namespace System
_length = (upper == 0) ? 1 : 2;
}
- public static uint BitScanReverse(uint mask)
+ public static void Add(ref BigInteger lhs, uint value, ref BigInteger result)
{
- // This comes from the Stanford Bit Widdling Hacks by Sean Eron Anderson:
- // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn
+ if (lhs.IsZero())
+ {
+ result.SetUInt32(value);
+ return;
+ }
+
+ if (value == 0)
+ {
+ result.SetValue(ref lhs);
+ return;
+ }
- mask |= (mask >> 1); // first round down to one less than a power of 2
- mask |= (mask >> 2);
- mask |= (mask >> 4);
- mask |= (mask >> 8);
- mask |= (mask >> 16);
+ int lhsLength = lhs._length;
+ int index = 0;
+ uint carry = value;
- uint index = (mask * 0x07C4ACDD) >> 27;
- return s_MultiplyDeBruijnBitPosition[(int)(index)];
+ while (index < lhsLength)
+ {
+ ulong sum = (ulong)(lhs._blocks[index]) + carry;
+ lhs._blocks[index] = (uint)(sum);
+ carry = (uint)(sum >> 32);
+
+ index++;
+ }
+
+ if (carry != 0)
+ {
+ Debug.Assert(unchecked((uint)(lhsLength)) + 1 <= MaxBlockCount);
+ result._blocks[index] = carry;
+ result._length = (lhsLength + 1);
+ }
}
public static int Compare(ref BigInteger lhs, ref BigInteger rhs)
@@ -198,6 +406,217 @@ namespace System
return 0;
}
+ public static uint CountSignificantBits(uint value)
+ {
+ return (value != 0) ? (1 + LogBase2(value)) : 0;
+ }
+
+ public static uint CountSignificantBits(ulong value)
+ {
+ uint upper = (uint)(value >> 32);
+
+ if (upper != 0)
+ {
+ return 32 + CountSignificantBits(upper);
+ }
+
+ return CountSignificantBits((uint)(value));
+ }
+
+ public static uint CountSignificantBits(ref BigInteger value)
+ {
+ if (value.IsZero())
+ {
+ return 0;
+ }
+
+ // We don't track any unused blocks, so we only need to do a BSR on the
+ // last index and add that to the number of bits we skipped.
+
+ uint lastIndex = (uint)(value._length - 1);
+ return (lastIndex * BitsPerBlock) + CountSignificantBits(value._blocks[lastIndex]);
+ }
+
+ public static void DivRem(ref BigInteger lhs, ref BigInteger rhs, out BigInteger quo, out BigInteger rem)
+ {
+ // This is modified from the CoreFX BigIntegerCalculator.DivRem.cs implementation:
+ // https://github.com/dotnet/corefx/blob/0bb106232745aedfc0d0c5a84ff2b244bf190317/src/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs
+
+ Debug.Assert(!rhs.IsZero());
+
+ quo = new BigInteger(0);
+ rem = new BigInteger(0);
+
+ if (lhs.IsZero())
+ {
+ return;
+ }
+
+ int lhsLength = lhs._length;
+ int rhsLength = rhs._length;
+
+ if ((lhsLength == 1) && (rhsLength == 1))
+ {
+ uint quotient = Math.DivRem(lhs._blocks[0], rhs._blocks[0], out uint remainder);
+ quo = new BigInteger(quotient);
+ rem = new BigInteger(remainder);
+ return;
+ }
+
+ if (rhsLength == 1)
+ {
+ // We can make the computation much simpler if the rhs is only one block
+
+ int quoLength = lhsLength;
+
+ ulong rhsValue = rhs._blocks[0];
+ ulong carry = 0;
+
+ for (int i = quoLength - 1; i >= 0; i--)
+ {
+ ulong value = (carry << 32) | lhs._blocks[i];
+ ulong digit = Math.DivRem(value, rhsValue, out carry);
+
+ if ((digit == 0) && (i == (quoLength - 1)))
+ {
+ quoLength--;
+ }
+ else
+ {
+ quo._blocks[i] = (uint)(digit);
+ }
+ }
+
+ quo._length = quoLength;
+ rem.SetUInt32((uint)(carry));
+
+ return;
+ }
+ else if (rhsLength > lhsLength)
+ {
+ // Handle the case where we have no quotient
+ rem.SetValue(ref lhs);
+ return;
+ }
+ else
+ {
+ int quoLength = lhsLength - rhsLength + 1;
+ rem.SetValue(ref lhs);
+ int remLength = lhsLength;
+
+ // Executes the "grammar-school" algorithm for computing q = a / b.
+ // Before calculating q_i, we get more bits into the highest bit
+ // block of the divisor. Thus, guessing digits of the quotient
+ // will be more precise. Additionally we'll get r = a % b.
+
+ uint divHi = rhs._blocks[rhsLength - 1];
+ uint divLo = rhs._blocks[rhsLength - 2];
+
+ // We measure the leading zeros of the divisor
+ int shiftLeft = (int)(LeadingZeroCount(divHi));
+ int shiftRight = 32 - shiftLeft;
+
+ // And, we make sure the most significant bit is set
+ if (shiftLeft > 0)
+ {
+ divHi = (divHi << shiftLeft) | (divLo >> shiftRight);
+ divLo = (divLo << shiftLeft);
+
+ if (rhsLength > 2)
+ {
+ divLo |= (rhs._blocks[rhsLength - 3] >> shiftRight);
+ }
+ }
+
+ // Then, we divide all of the bits as we would do it using
+ // pen and paper: guessing the next digit, subtracting, ...
+ for (int i = lhsLength; i >= rhsLength; i--)
+ {
+ int n = i - rhsLength;
+ uint t = i < lhsLength ? rem._blocks[i] : 0;
+
+ ulong valHi = ((ulong)(t) << 32) | rem._blocks[i - 1];
+ uint valLo = i > 1 ? rem._blocks[i - 2] : 0;
+
+ // We shifted the divisor, we shift the dividend too
+ if (shiftLeft > 0)
+ {
+ valHi = (valHi << shiftLeft) | (valLo >> shiftRight);
+ valLo = (valLo << shiftLeft);
+
+ if (i > 2)
+ {
+ valLo |= (rem._blocks[i - 3] >> shiftRight);
+ }
+ }
+
+ // First guess for the current digit of the quotient,
+ // which naturally must have only 32 bits...
+ ulong digit = valHi / divHi;
+
+ if (digit > uint.MaxValue)
+ {
+ digit = uint.MaxValue;
+ }
+
+ // Our first guess may be a little bit to big
+ while (DivideGuessTooBig(digit, valHi, valLo, divHi, divLo))
+ {
+ digit--;
+ }
+
+ if (digit > 0)
+ {
+ // Now it's time to subtract our current quotient
+ uint carry = SubtractDivisor(ref rem, n, ref rhs, digit);
+
+ if (carry != t)
+ {
+ Debug.Assert(carry == t + 1);
+
+ // Our guess was still exactly one too high
+ carry = AddDivisor(ref rem, n, ref rhs);
+ digit--;
+
+ Debug.Assert(carry == 1);
+ }
+ }
+
+ // We have the digit!
+ if (quoLength != 0)
+ {
+ if ((digit == 0) && (n == (quoLength - 1)))
+ {
+ quoLength--;
+ }
+ else
+ {
+ quo._blocks[n] = (uint)(digit);
+ }
+ }
+
+ if (i < remLength)
+ {
+ remLength--;
+ }
+ }
+
+ quo._length = quoLength;
+
+ // We need to check for the case where remainder is zero
+
+ for (int i = remLength - 1; i >= 0; i--)
+ {
+ if (rem._blocks[i] == 0)
+ {
+ remLength--;
+ }
+ }
+
+ rem._length = remLength;
+ }
+ }
+
public static uint HeuristicDivide(ref BigInteger dividend, ref BigInteger divisor)
{
int divisorLength = divisor._length;
@@ -278,10 +697,31 @@ namespace System
return quotient;
}
+ public static uint LeadingZeroCount(uint value)
+ {
+ return 32 - CountSignificantBits(value);
+ }
+
+ public static uint LeadingZeroCount(ulong value)
+ {
+ return 64 - CountSignificantBits(value);
+ }
+
public static uint LogBase2(uint value)
{
Debug.Assert(value != 0);
- return BitScanReverse(value);
+
+ // This comes from the Stanford Bit Widdling Hacks by Sean Eron Anderson:
+ // http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogDeBruijn
+
+ value |= (value >> 1); // first round down to one less than a power of 2
+ value |= (value >> 2);
+ value |= (value >> 4);
+ value |= (value >> 8);
+ value |= (value >> 16);
+
+ uint index = (value * 0x07C4ACDD) >> 27;
+ return s_MultiplyDeBruijnBitPosition[(int)(index)];
}
public static uint LogBase2(ulong value)
@@ -314,14 +754,13 @@ namespace System
int lhsLength = lhs._length;
int index = 0;
-
- ulong carry = 0;
+ uint carry = 0;
while (index < lhsLength)
{
ulong product = ((ulong)(lhs._blocks[index]) * value) + carry;
- carry = product >> 32;
result._blocks[index] = (uint)(product);
+ carry = (uint)(product >> 32);
index++;
}
@@ -329,8 +768,8 @@ namespace System
if (carry != 0)
{
Debug.Assert(unchecked((uint)(lhsLength)) + 1 <= MaxBlockCount);
- result._blocks[index] = (uint)(carry);
- result._length += (lhsLength + 1);
+ result._blocks[index] = carry;
+ result._length = (lhsLength + 1);
}
}
@@ -410,12 +849,12 @@ namespace System
}
}
- public static void Pow10(uint exponent, ref BigInteger result)
+ public static void Pow10(uint exponent, out BigInteger result)
{
// We leverage two arrays - s_Pow10UInt32Table and s_Pow10BigNumTable to speed up the Pow10 calculation.
//
// s_Pow10UInt32Table stores the results of 10^0 to 10^7.
- // s_Pow10BigNumTable stores the results of 10^8, 10^16, 10^32, 10^64, 10^128 and 10^256.
+ // s_Pow10BigNumTable stores the results of 10^8, 10^16, 10^32, 10^64, 10^128, 10^256, and 10^512
//
// For example, let's say exp = 0b111111. We can split the exp to two parts, one is small exp,
// which 10^smallExp can be represented as uint, another part is 10^bigExp, which must be represented as BigNum.
@@ -438,6 +877,10 @@ namespace System
//
// More details of this implementation can be found at: https://github.com/dotnet/coreclr/pull/12894#discussion_r128890596
+ // Validate that `s_Pow10BigNumTable` has exactly enough trailing elements to fill a BigInteger (which contains MaxBlockCount + 1 elements)
+ // We validate here, since this is the only current consumer of the array
+ Debug.Assert((s_Pow10BigNumTableIndices[s_Pow10BigNumTableIndices.Length - 1] + MaxBlockCount + 2) == s_Pow10BigNumTable.Length);
+
BigInteger temp1 = new BigInteger(s_Pow10UInt32Table[exponent & 0x7]);
ref BigInteger lhs = ref temp1;
@@ -453,7 +896,7 @@ namespace System
if ((exponent & 1) != 0)
{
// Multiply into the next temporary
- ref BigInteger rhs = ref *(BigInteger*)(Unsafe.AsPointer(ref s_Pow10BigNumTable[s_s_Pow10BigNumTableIndices[index]]));
+ ref BigInteger rhs = ref *(BigInteger*)(Unsafe.AsPointer(ref s_Pow10BigNumTable[s_Pow10BigNumTableIndices[index]]));
Multiply(ref lhs, ref rhs, ref product);
// Swap to the next temporary
@@ -467,6 +910,7 @@ namespace System
exponent >>= 1;
}
+ result = new BigInteger(0);
result.SetValue(ref lhs);
}
@@ -542,6 +986,100 @@ namespace System
}
}
+ private static unsafe uint AddDivisor(ref BigInteger lhs, int lhsStartIndex, ref BigInteger rhs)
+ {
+ int lhsLength = lhs._length;
+ int rhsLength = rhs._length;
+
+ Debug.Assert(lhsLength >= 0);
+ Debug.Assert(rhsLength >= 0);
+ Debug.Assert(lhsLength >= rhsLength);
+
+ // Repairs the dividend, if the last subtract was too much
+
+ ulong carry = 0UL;
+
+ for (int i = 0; i < rhsLength; i++)
+ {
+ ref uint lhsValue = ref lhs._blocks[lhsStartIndex + i];
+
+ ulong digit = (lhsValue + carry) + rhs._blocks[i];
+ lhsValue = unchecked((uint)digit);
+ carry = digit >> 32;
+ }
+
+ return (uint)(carry);
+ }
+
+ private static bool DivideGuessTooBig(ulong q, ulong valHi, uint valLo, uint divHi, uint divLo)
+ {
+ Debug.Assert(q <= 0xFFFFFFFF);
+
+ // We multiply the two most significant limbs of the divisor
+ // with the current guess for the quotient. If those are bigger
+ // than the three most significant limbs of the current dividend
+ // we return true, which means the current guess is still too big.
+
+ ulong chkHi = divHi * q;
+ ulong chkLo = divLo * q;
+
+ chkHi = chkHi + (chkLo >> 32);
+ chkLo = chkLo & uint.MaxValue;
+
+ if (chkHi < valHi)
+ return false;
+
+ if (chkHi > valHi)
+ return true;
+
+ if (chkLo < valLo)
+ return false;
+
+ if (chkLo > valLo)
+ return true;
+
+ return false;
+ }
+
+ private static unsafe uint SubtractDivisor(ref BigInteger lhs, int lhsStartIndex, ref BigInteger rhs, ulong q)
+ {
+ int lhsLength = lhs._length - lhsStartIndex;
+ int rhsLength = rhs._length;
+
+ Debug.Assert((lhsLength) >= 0);
+ Debug.Assert(rhsLength >= 0);
+ Debug.Assert(lhsLength >= rhsLength);
+ Debug.Assert(q <= uint.MaxValue);
+
+ // Combines a subtract and a multiply operation, which is naturally
+ // more efficient than multiplying and then subtracting...
+
+ ulong carry = 0;
+
+ for (int i = 0; i < rhsLength; i++)
+ {
+ carry += rhs._blocks[i] * q;
+ uint digit = unchecked((uint)carry);
+ carry = carry >> 32;
+
+ ref uint lhsValue = ref lhs._blocks[lhsStartIndex + i];
+
+ if (lhsValue < digit)
+ {
+ carry++;
+ }
+
+ lhsValue = unchecked(lhsValue - digit);
+ }
+
+ return (uint)(carry);
+ }
+
+ public void Add(uint value)
+ {
+ Add(ref this, value, ref this);
+ }
+
public void ExtendBlock(uint blockValue)
{
_blocks[_length] = blockValue;
@@ -564,6 +1102,12 @@ namespace System
_blocks[_length - 1] = blockValue;
}
+ public uint GetBlock(uint index)
+ {
+ Debug.Assert(index < _length);
+ return _blocks[index];
+ }
+
public bool IsOne()
{
return (_length == 1)
@@ -618,6 +1162,20 @@ namespace System
}
}
+ public void MultiplyPow10(uint exponent)
+ {
+ Pow10(exponent, out BigInteger poweredValue);
+
+ if (poweredValue._length == 1)
+ {
+ Multiply(poweredValue._blocks[0]);
+ }
+ else
+ {
+ Multiply(ref poweredValue);
+ }
+ }
+
public void SetUInt32(uint value)
{
_blocks[0] = value;
@@ -721,6 +1279,21 @@ namespace System
}
}
+ public ulong ToUInt64()
+ {
+ if (_length > 1)
+ {
+ return ((ulong)(_blocks[1]) << 32) + _blocks[0];
+ }
+
+ if (_length > 0)
+ {
+ return _blocks[0];
+ }
+
+ return 0;
+ }
+
private uint* GetBlocksPointer()
{
// This is safe to do since we are a ref struct
diff --git a/src/System.Private.CoreLib/shared/System/Number.Dragon4.cs b/src/System.Private.CoreLib/shared/System/Number.Dragon4.cs
index 7c1d480610..ec51104488 100644
--- a/src/System.Private.CoreLib/shared/System/Number.Dragon4.cs
+++ b/src/System.Private.CoreLib/shared/System/Number.Dragon4.cs
@@ -129,15 +129,11 @@ namespace System
if (k > 0)
{
- BigInteger poweredValue = new BigInteger(0);
- BigInteger.Pow10((uint)(k), ref poweredValue);
- s.Multiply(ref poweredValue);
+ s.MultiplyPow10((uint)(k));
}
else if (k < 0)
{
- BigInteger poweredValue = new BigInteger(0);
- BigInteger.Pow10((uint)(-k), ref poweredValue);
- r.Multiply(ref poweredValue);
+ r.MultiplyPow10((uint)(-k));
}
if (BigInteger.Compare(ref r, ref s) >= 0)
diff --git a/src/System.Private.CoreLib/shared/System/Number.Formatting.cs b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs
index 76f0469b4b..1c56546ed3 100644
--- a/src/System.Private.CoreLib/shared/System/Number.Formatting.cs
+++ b/src/System.Private.CoreLib/shared/System/Number.Formatting.cs
@@ -393,7 +393,7 @@ namespace System
int precision = DoublePrecision;
char* pDigits = stackalloc char[DoubleNumberBufferLength];
- NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, DoubleNumberBufferLength);
+ NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength);
switch (fmt)
{
@@ -413,7 +413,9 @@ namespace System
return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
}
- if (NumberToDouble(ref number) == value)
+ double roundTrip = NumberToDouble(ref number);
+
+ if (roundTrip == value)
{
NumberToString(ref sb, ref number, 'G', DoublePrecision, info);
}
@@ -495,7 +497,7 @@ namespace System
int precision = SinglePrecision;
char* pDigits = stackalloc char[SingleNumberBufferLength];
- NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, SingleNumberBufferLength);
+ NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength);
switch (fmt)
{
@@ -515,7 +517,9 @@ namespace System
return number.Sign ? info.NegativeInfinitySymbol : info.PositiveInfinitySymbol;
}
- if ((float)NumberToDouble(ref number) == value)
+ float roundTrip = NumberToSingle(ref number);
+
+ if (roundTrip == value)
{
NumberToString(ref sb, ref number, 'G', SinglePrecision, info);
}
diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs
index f96687e43e..83af513458 100644
--- a/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs
+++ b/src/System.Private.CoreLib/shared/System/Number.NumberBuffer.cs
@@ -2,7 +2,7 @@
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
-using System.Runtime.InteropServices;
+using System.Diagnostics;
using Internal.Runtime.CompilerServices;
namespace System
@@ -10,11 +10,11 @@ namespace System
internal static partial class Number
{
// We need 1 additional byte, per length, for the terminating null
- private const int DecimalNumberBufferLength = 50 + 1;
- private const int DoubleNumberBufferLength = 768 + 1; // 767 for the longest input + 1 for rounding: 4.9406564584124654E-324
+ private const int DecimalNumberBufferLength = 29 + 1 + 1; // 29 for the longest input + 1 for rounding
+ private const int DoubleNumberBufferLength = 767 + 1 + 1; // 767 for the longest input + 1 for rounding: 4.9406564584124654E-324
private const int Int32NumberBufferLength = 10 + 1; // 10 for the longest input: 2,147,483,647
private const int Int64NumberBufferLength = 19 + 1; // 19 for the longest input: 9,223,372,036,854,775,807
- private const int SingleNumberBufferLength = 113 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45
+ private const int SingleNumberBufferLength = 112 + 1 + 1; // 112 for the longest input + 1 for rounding: 1.40129846E-45
private const int UInt32NumberBufferLength = 10 + 1; // 10 for the longest input: 4,294,967,295
private const int UInt64NumberBufferLength = 20 + 1; // 20 for the longest input: 18,446,744,073,709,551,615
@@ -23,16 +23,23 @@ namespace System
public int Precision;
public int Scale;
public bool Sign;
+ public bool HasNonZeroTail;
public NumberBufferKind Kind;
public Span<char> Digits;
- public NumberBuffer(NumberBufferKind kind, char* pDigits, int digitsLength)
+ public NumberBuffer(NumberBufferKind kind, char* digits, int digitsLength)
{
+ Debug.Assert(Enum.IsDefined(typeof(NumberBufferKind), kind));
+ Debug.Assert(kind != NumberBufferKind.Unknown);
+ Debug.Assert(digits != null);
+ Debug.Assert(digitsLength > 0);
+
Precision = 0;
Scale = 0;
Sign = false;
+ HasNonZeroTail = false;
Kind = kind;
- Digits = new Span<char>(pDigits, digitsLength);
+ Digits = new Span<char>(digits, digitsLength);
}
public char* GetDigitsPointer()
@@ -47,7 +54,7 @@ namespace System
Unknown = 0,
Integer = 1,
Decimal = 2,
- Double = 3,
+ FloatingPoint = 3,
}
}
}
diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs
deleted file mode 100644
index 974786d3de..0000000000
--- a/src/System.Private.CoreLib/shared/System/Number.NumberToDouble.cs
+++ /dev/null
@@ -1,449 +0,0 @@
-// Licensed to the .NET Foundation under one or more agreements.
-// The .NET Foundation licenses this file to you under the MIT license.
-// See the LICENSE file in the project root for more information.
-
-using System.Diagnostics;
-
-namespace System
-{
- internal unsafe partial class Number
- {
- // precomputed tables with powers of 10. These allows us to do at most
- // two Mul64 during the conversion. This is important not only
- // for speed, but also for precision because of Mul64 computes with 1 bit error.
-
- private static readonly ulong[] s_Pow10MantissaTable = new ulong[]
- {
- // powers of 10
- 0XA0000000_00000000, // 1
- 0XC8000000_00000000, // 2
- 0XFA000000_00000000, // 3
- 0X9C400000_00000000, // 4
- 0XC3500000_00000000, // 5
- 0XF4240000_00000000, // 6
- 0X98968000_00000000, // 7
- 0XBEBC2000_00000000, // 8
- 0XEE6B2800_00000000, // 9
- 0X9502F900_00000000, // 10
- 0XBA43B740_00000000, // 11
- 0XE8D4A510_00000000, // 12
- 0X9184E72A_00000000, // 13
- 0XB5E620F4_80000000, // 14
- 0XE35FA931_A0000000, // 15
-
- // powers of 0.1
- 0xCCCCCCCC_CCCCCCCD, // 1
- 0xA3D70A3D_70A3D70B, // 2
- 0x83126E97_8D4FDF3C, // 3
- 0xD1B71758_E219652E, // 4
- 0xA7C5AC47_1B478425, // 5
- 0x8637BD05_AF6C69B7, // 6
- 0xD6BF94D5_E57A42BE, // 7
- 0xABCC7711_8461CEFF, // 8
- 0x89705F41_36B4A599, // 9
- 0xDBE6FECE_BDEDD5C2, // 10
- 0xAFEBFF0B_CB24AB02, // 11
- 0x8CBCCC09_6F5088CF, // 12
- 0xE12E1342_4BB40E18, // 13
- 0xB424DC35_095CD813, // 14
- 0x901D7CF7_3AB0ACDC, // 15
- };
-
- private static readonly short[] s_Pow10ExponentTable = new short[]
- {
- // exponents for both powers of 10 and 0.1
- 4, // 1
- 7, // 2
- 10, // 3
- 14, // 4
- 17, // 5
- 20, // 6
- 24, // 7
- 27, // 8
- 30, // 9
- 34, // 10
- 37, // 11
- 40, // 12
- 44, // 13
- 47, // 14
- 50, // 15
- };
-
- private static readonly ulong[] s_Pow10By16MantissaTable = new ulong[]
- {
- // powers of 10^16
- 0x8E1BC9BF_04000000, // 1
- 0x9DC5ADA8_2B70B59E, // 2
- 0xAF298D05_0E4395D6, // 3
- 0xC2781F49_FFCFA6D4, // 4
- 0xD7E77A8F_87DAF7FA, // 5
- 0xEFB3AB16_C59B14A0, // 6
- 0x850FADC0_9923329C, // 7
- 0x93BA47C9_80E98CDE, // 8
- 0xA402B9C5_A8D3A6E6, // 9
- 0xB616A12B_7FE617A8, // 10
- 0xCA28A291_859BBF90, // 11
- 0xE070F78D_39275566, // 12
- 0xF92E0C35_37826140, // 13
- 0x8A5296FF_E33CC92C, // 14
- 0x9991A6F3_D6BF1762, // 15
- 0xAA7EEBFB_9DF9DE8A, // 16
- 0xBD49D14A_A79DBC7E, // 17
- 0xD226FC19_5C6A2F88, // 18
- 0xE950DF20_247C83F8, // 19
- 0x81842F29_F2CCE373, // 20
- 0x8FCAC257_558EE4E2, // 21
-
- // powers of 0.1^16
- 0xE69594BE_C44DE160, // 1
- 0xCFB11EAD_453994C3, // 2
- 0xBB127C53_B17EC165, // 3
- 0xA87FEA27_A539E9B3, // 4
- 0x97C560BA_6B0919B5, // 5
- 0x88B402F7_FD7553AB, // 6
- 0xF64335BC_F065D3A0, // 7
- 0xDDD0467C_64BCE4C4, // 8
- 0xC7CABA6E_7C5382ED, // 9
- 0xB3F4E093_DB73A0B7, // 10
- 0xA21727DB_38CB0053, // 11
- 0x91FF8377_5423CC29, // 12
- 0x8380DEA9_3DA4BC82, // 13
- 0xECE53CEC_4A314F00, // 14
- 0xD5605FCD_CF32E217, // 15
- 0xC0314325_637A1978, // 16
- 0xAD1C8EAB_5EE43BA2, // 17
- 0x9BECCE62_836AC5B0, // 18
- 0x8C71DCD9_BA0B495C, // 19
- 0xFD00B897_47823938, // 20
- 0xE3E27A44_4D8D991A, // 21
- };
-
- private static readonly short[] s_Pow10By16ExponentTable = new short[]
- {
- // exponents for both powers of 10^16 and 0.1^16
- 54, // 1
- 107, // 2
- 160, // 3
- 213, // 4
- 266, // 5
- 319, // 6
- 373, // 7
- 426, // 8
- 479, // 9
- 532, // 10
- 585, // 11
- 638, // 12
- 691, // 13
- 745, // 14
- 798, // 15
- 851, // 16
- 904, // 17
- 957, // 18
- 1010, // 19
- 1064, // 20
- 1117, // 21
- };
-
-#if DEBUG
- private static bool s_CheckedTables = false;
-
- private static void CheckTables()
- {
- ulong val;
- int exp;
-
- val = 0xA0000000_00000000;
- exp = 4; // 10
-
- CheckPow10MantissaTable(val, exp, new Span<ulong>(s_Pow10MantissaTable, 0, 15), nameof(s_Pow10MantissaTable));
- CheckPow10ExponentTable(val, exp, new Span<short>(s_Pow10ExponentTable, 0, 15), nameof(s_Pow10ExponentTable));
-
- val = 0x8E1BC9BF_04000000;
- exp = 54; //10^16
-
- CheckPow10MantissaTable(val, exp, new Span<ulong>(s_Pow10By16MantissaTable, 0, 21), nameof(s_Pow10By16MantissaTable));
- CheckPow10ExponentTable(val, exp, new Span<short>(s_Pow10By16ExponentTable, 0, 21), nameof(s_Pow10By16ExponentTable));
-
- val = 0xCCCCCCCC_CCCCCCCD;
- exp = -3; // 0.1
-
- CheckPow10MantissaTable(val, exp, new Span<ulong>(s_Pow10MantissaTable, 15, 15), nameof(s_Pow10MantissaTable) + " - inv");
-
- val = 0xE69594BE_C44DE160;
- exp = -53; // 0.1^16
-
- CheckPow10MantissaTable(val, exp, new Span<ulong>(s_Pow10By16MantissaTable, 21, 21), nameof(s_Pow10By16MantissaTable) + " - inv");
- }
-
- // debug-only verification of the precomputed tables
- private static void CheckPow10MantissaTable(ulong val, int exp, Span<ulong> table, string name)
- {
- ulong multval = val;
- int mulexp = exp;
- bool isBad = false;
-
- for (int i = 0; i < table.Length; i++)
- {
- if (table[i] != val)
- {
- if (!isBad)
- {
- Debug.WriteLine(name);
- isBad = true;
- }
- Debug.WriteLine($"0x{val:X16}, // {i + 1}");
- }
-
- exp += mulexp;
- val = Mul64Precise(val, multval, ref exp);
- }
-
- Debug.Assert(!isBad, "NumberToDouble table not correct. Correct version dumped to debug output.");
- }
-
- // debug-only verification of the precomputed tables
- private static void CheckPow10ExponentTable(ulong val, int exp, Span<short> table, string name)
- {
- ulong multval = val;
- int mulexp = exp;
- bool isBad = false;
-
- for (int i = 0; i < table.Length; i++)
- {
- if (table[i] != exp)
- {
- if (!isBad)
- {
- Debug.WriteLine(name);
- isBad = true;
- }
- Debug.WriteLine($"{val}, // {i + 1}");
- }
-
- exp += mulexp;
- val = Mul64Precise(val, multval, ref exp);
- }
-
- Debug.Assert(!isBad, "NumberToDouble table not correct. Correct version dumped to debug output.");
- }
-
- // slower high precision version of Mul64 for computation of the tables
- private static ulong Mul64Precise(ulong a, ulong b, ref int exp)
- {
- ulong hilo = ((Mul32x32To64((uint)(a >> 32), (uint)(b)) >> 1)
- + (Mul32x32To64((uint)(a), (uint)(b >> 32)) >> 1)
- + (Mul32x32To64((uint)(a), (uint)(b)) >> 33)) >> 30;
-
- ulong val = Mul32x32To64((uint)(a >> 32), (uint)(b >> 32))
- + (hilo >> 1)
- + (hilo & 1);
-
- // normalize
- if ((val & 0x80000000_00000000) == 0)
- {
- val <<= 1;
- exp--;
- }
-
- return val;
- }
-#endif
-
- // get 32-bit integer from at most 9 digits
- private static uint DigitsToInt(char* p, int count)
- {
- Debug.Assert((1 <= count) && (count <= 9));
-
- char* end = (p + count);
- uint res = (uint)(p[0] - '0');
-
- for (p++; p < end; p++)
- {
- res = (10 * res) + p[0] - '0';
- }
-
- return res;
- }
-
- private static int GetLength(char* src)
- {
- int length = 0;
-
- while (src[length] != '\0')
- {
- length++;
- }
-
- return length;
- }
-
- // helper function to multiply two 32-bit uints
- private static ulong Mul32x32To64(uint a, uint b)
- {
- return (ulong)(a) * b;
- }
-
- // multiply two numbers in the internal integer representation
- private static ulong Mul64Lossy(ulong a, ulong b, ref int exp)
- {
- // it's ok to lose some precision here - Mul64 will be called
- // at most twice during the conversion, so the error won't propagate
- // to any of the 53 significant bits of the result
- ulong val = Mul32x32To64((uint)(a >> 32), (uint)(b >> 32))
- + (Mul32x32To64((uint)(a >> 32), (uint)(b)) >> 32)
- + (Mul32x32To64((uint)(a), (uint)(b >> 32)) >> 32);
-
- // normalize
- if ((val & 0x80000000_00000000) == 0)
- {
- val <<= 1;
- exp--;
- }
-
- return val;
- }
-
- private static double NumberToDouble(ref NumberBuffer number)
- {
-#if DEBUG
-
- if (!s_CheckedTables)
- {
- CheckTables();
- s_CheckedTables = true;
- }
-#endif
-
- char* src = number.GetDigitsPointer();
- int total = GetLength(src);
- int remaining = total;
-
- // skip the leading zeros
- while (src[0] == '0')
- {
- remaining--;
- src++;
- }
-
- if (remaining == 0)
- {
- return number.Sign ? -0.0 : 0.0;
- }
-
- int count = Math.Min(remaining, 9);
- remaining -= count;
- ulong val = DigitsToInt(src, count);
-
- if (remaining > 0)
- {
- count = Math.Min(remaining, 9);
- remaining -= count;
-
- // get the denormalized power of 10
- uint mult = (uint)(s_Pow10MantissaTable[count - 1] >> (64 - s_Pow10ExponentTable[count - 1]));
- val = Mul32x32To64((uint)(val), mult) + DigitsToInt(src + 9, count);
- }
-
- int scale = number.Scale - (total - remaining);
- int absscale = Math.Abs(scale);
- if (absscale >= 22 * 16)
- {
- // overflow / underflow
- if (scale > 0)
- {
- return number.Sign ? double.NegativeInfinity : double.PositiveInfinity;
- }
- else
- {
- return number.Sign ? -0.0 : 0.0;
- }
- }
-
- int exp = 64;
-
- // normalize the mantissa
- if ((val & 0xFFFFFFFF_00000000) == 0) { val <<= 32; exp -= 32; }
- if ((val & 0xFFFF0000_00000000) == 0) { val <<= 16; exp -= 16; }
- if ((val & 0xFF000000_00000000) == 0) { val <<= 8; exp -= 8; }
- if ((val & 0xF0000000_00000000) == 0) { val <<= 4; exp -= 4; }
- if ((val & 0xC0000000_00000000) == 0) { val <<= 2; exp -= 2; }
- if ((val & 0x80000000_00000000) == 0) { val <<= 1; exp -= 1; }
-
- int index = absscale & 15;
- if (index != 0)
- {
- int multexp = s_Pow10ExponentTable[index - 1];
- // the exponents are shared between the inverted and regular table
- exp += (scale < 0) ? (-multexp + 1) : multexp;
-
- ulong multval = s_Pow10MantissaTable[index + ((scale < 0) ? 15 : 0) - 1];
- val = Mul64Lossy(val, multval, ref exp);
- }
-
- index = absscale >> 4;
- if (index != 0)
- {
- int multexp = s_Pow10By16ExponentTable[index - 1];
- // the exponents are shared between the inverted and regular table
- exp += (scale < 0) ? (-multexp + 1) : multexp;
-
- ulong multval = s_Pow10By16MantissaTable[index + ((scale < 0) ? 21 : 0) - 1];
- val = Mul64Lossy(val, multval, ref exp);
- }
-
- // round & scale down
- if (((uint)(val) & (1 << 10)) != 0)
- {
- // IEEE round to even
- ulong tmp = val + ((1 << 10) - 1) + (((uint)(val) >> 11) & 1);
- if (tmp < val)
- {
- // overflow
- tmp = (tmp >> 1) | 0x8000000000000000;
- exp += 1;
- }
- val = tmp;
- }
-
- // return the exponent to a biased state
- exp += 0x3FE;
-
- // handle overflow, underflow, "Epsilon - 1/2 Epsilon", denormalized, and the normal case
- if (exp <= 0)
- {
- if (exp == -52 && (val >= 0x8000000000000058))
- {
- // round X where {Epsilon > X >= 2.470328229206232730000000E-324} up to Epsilon (instead of down to zero)
- val = 0x0000000000000001;
- }
- else if (exp <= -52)
- {
- // underflow
- val = 0;
- }
- else
- {
- // denormalized
- val >>= (-exp + 11 + 1);
- }
- }
- else if (exp >= 0x7FF)
- {
- // overflow
- val = 0x7FF0000000000000;
- }
- else
- {
- // normal postive exponent case
- val = ((ulong)(exp) << 52) + ((val >> 11) & 0x000FFFFFFFFFFFFF);
- }
-
- if (number.Sign)
- {
- val |= 0x8000000000000000;
- }
-
- return BitConverter.Int64BitsToDouble((long)(val));
- }
- }
-}
diff --git a/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs
new file mode 100644
index 0000000000..16626bb7cb
--- /dev/null
+++ b/src/System.Private.CoreLib/shared/System/Number.NumberToFloatingPointBits.cs
@@ -0,0 +1,621 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Diagnostics;
+
+namespace System
+{
+ internal unsafe partial class Number
+ {
+ public readonly struct FloatingPointInfo
+ {
+ public static readonly FloatingPointInfo Double = new FloatingPointInfo(
+ denormalMantissaBits: 52,
+ exponentBits: 11,
+ maxBinaryExponent: 1023,
+ exponentBias: 1023,
+ infinityBits: 0x7FF00000_00000000
+ );
+
+ public static readonly FloatingPointInfo Single = new FloatingPointInfo(
+ denormalMantissaBits: 23,
+ exponentBits: 8,
+ maxBinaryExponent: 127,
+ exponentBias: 127,
+ infinityBits: 0x7F800000
+ );
+
+ public ulong ZeroBits { get; }
+ public ulong InfinityBits { get; }
+
+ public ulong NormalMantissaMask { get; }
+ public ulong DenormalMantissaMask { get; }
+
+ public int MinBinaryExponent { get; }
+ public int MaxBinaryExponent { get; }
+
+ public int ExponentBias { get; }
+ public int OverflowDecimalExponent { get; }
+
+ public ushort NormalMantissaBits { get; }
+ public ushort DenormalMantissaBits { get; }
+
+ public ushort ExponentBits { get; }
+
+ public FloatingPointInfo(ushort denormalMantissaBits, ushort exponentBits, int maxBinaryExponent, int exponentBias, ulong infinityBits)
+ {
+ ExponentBits = exponentBits;
+
+ DenormalMantissaBits = denormalMantissaBits;
+ NormalMantissaBits = (ushort)(denormalMantissaBits + 1); // we get an extra (hidden) bit for normal mantissas
+
+ OverflowDecimalExponent = (maxBinaryExponent + 2 * NormalMantissaBits) / 3;
+ ExponentBias = exponentBias;
+
+ MaxBinaryExponent = maxBinaryExponent;
+ MinBinaryExponent = 1 - maxBinaryExponent;
+
+ DenormalMantissaMask = (1UL << denormalMantissaBits) - 1;
+ NormalMantissaMask = (1UL << NormalMantissaBits) - 1;
+
+ InfinityBits = infinityBits;
+ ZeroBits = 0;
+ }
+ }
+
+ private static float[] s_Pow10SingleTable = new float[]
+ {
+ 1e0f, // 10^0
+ 1e1f, // 10^1
+ 1e2f, // 10^2
+ 1e3f, // 10^3
+ 1e4f, // 10^4
+ 1e5f, // 10^5
+ 1e6f, // 10^6
+ 1e7f, // 10^7
+ 1e8f, // 10^8
+ 1e9f, // 10^9
+ 1e10f, // 10^10
+ };
+
+ private static double[] s_Pow10DoubleTable = new double[]
+ {
+ 1e0, // 10^0
+ 1e1, // 10^1
+ 1e2, // 10^2
+ 1e3, // 10^3
+ 1e4, // 10^4
+ 1e5, // 10^5
+ 1e6, // 10^6
+ 1e7, // 10^7
+ 1e8, // 10^8
+ 1e9, // 10^9
+ 1e10, // 10^10
+ 1e11, // 10^11
+ 1e12, // 10^12
+ 1e13, // 10^13
+ 1e14, // 10^14
+ 1e15, // 10^15
+ 1e16, // 10^16
+ 1e17, // 10^17
+ 1e18, // 10^18
+ 1e19, // 10^19
+ 1e20, // 10^20
+ 1e21, // 10^21
+ 1e22, // 10^22
+ };
+
+ private static void AccumulateDecimalDigitsIntoBigInteger(ref NumberBuffer number, uint firstIndex, uint lastIndex, out BigInteger result)
+ {
+ result = new BigInteger(0);
+
+ char* src = number.GetDigitsPointer() + firstIndex;
+ uint remaining = lastIndex - firstIndex;
+
+ while (remaining != 0)
+ {
+ uint count = Math.Min(remaining, 9);
+ uint value = DigitsToUInt32(src, (int)(count));
+
+ result.MultiplyPow10(count);
+ result.Add(value);
+
+ src += count;
+ remaining -= count;
+ }
+ }
+
+ private static ulong AssembleFloatingPointBits(in FloatingPointInfo info, ulong initialMantissa, int initialExponent, bool hasZeroTail)
+ {
+ // number of bits by which we must adjust the mantissa to shift it into the
+ // correct position, and compute the resulting base two exponent for the
+ // normalized mantissa:
+ uint initialMantissaBits = BigInteger.CountSignificantBits(initialMantissa);
+ int normalMantissaShift = info.NormalMantissaBits - (int)(initialMantissaBits);
+ int normalExponent = initialExponent - normalMantissaShift;
+
+ ulong mantissa = initialMantissa;
+ int exponent = normalExponent;
+
+ if (normalExponent > info.MaxBinaryExponent)
+ {
+ // The exponent is too large to be represented by the floating point
+ // type; report the overflow condition:
+ return info.InfinityBits;
+ }
+ else if (normalExponent < info.MinBinaryExponent)
+ {
+ // The exponent is too small to be represented by the floating point
+ // type as a normal value, but it may be representable as a denormal
+ // value. Compute the number of bits by which we need to shift the
+ // mantissa in order to form a denormal number. (The subtraction of
+ // an extra 1 is to account for the hidden bit of the mantissa that
+ // is not available for use when representing a denormal.)
+ int denormalMantissaShift = normalMantissaShift + normalExponent + info.ExponentBias - 1;
+
+ // Denormal values have an exponent of zero, so the debiased exponent is
+ // the negation of the exponent bias:
+ exponent = -info.ExponentBias;
+
+ if (denormalMantissaShift < 0)
+ {
+ // Use two steps for right shifts: for a shift of N bits, we first
+ // shift by N-1 bits, then shift the last bit and use its value to
+ // round the mantissa.
+ mantissa = RightShiftWithRounding(mantissa, -denormalMantissaShift, hasZeroTail);
+
+ // If the mantissa is now zero, we have underflowed:
+ if (mantissa == 0)
+ {
+ return info.ZeroBits;
+ }
+
+ // When we round the mantissa, the result may be so large that the
+ // number becomes a normal value. For example, consider the single
+ // precision case where the mantissa is 0x01ffffff and a right shift
+ // of 2 is required to shift the value into position. We perform the
+ // shift in two steps: we shift by one bit, then we shift again and
+ // round using the dropped bit. The initial shift yields 0x00ffffff.
+ // The rounding shift then yields 0x007fffff and because the least
+ // significant bit was 1, we add 1 to this number to round it. The
+ // final result is 0x00800000.
+ //
+ // 0x00800000 is 24 bits, which is more than the 23 bits available
+ // in the mantissa. Thus, we have rounded our denormal number into
+ // a normal number.
+ //
+ // We detect this case here and re-adjust the mantissa and exponent
+ // appropriately, to form a normal number:
+ if (mantissa > info.DenormalMantissaMask)
+ {
+ // We add one to the denormal_mantissa_shift to account for the
+ // hidden mantissa bit (we subtracted one to account for this bit
+ // when we computed the denormal_mantissa_shift above).
+ exponent = initialExponent - (denormalMantissaShift + 1) - normalMantissaShift;
+ }
+ }
+ else
+ {
+ mantissa <<= denormalMantissaShift;
+ }
+ }
+ else
+ {
+ if (normalMantissaShift < 0)
+ {
+ // Use two steps for right shifts: for a shift of N bits, we first
+ // shift by N-1 bits, then shift the last bit and use its value to
+ // round the mantissa.
+ mantissa = RightShiftWithRounding(mantissa, -normalMantissaShift, hasZeroTail);
+
+ // When we round the mantissa, it may produce a result that is too
+ // large. In this case, we divide the mantissa by two and increment
+ // the exponent (this does not change the value).
+ if (mantissa > info.NormalMantissaMask)
+ {
+ mantissa >>= 1;
+ exponent++;
+
+ // The increment of the exponent may have generated a value too
+ // large to be represented. In this case, report the overflow:
+ if (exponent > info.MaxBinaryExponent)
+ {
+ return info.InfinityBits;
+ }
+ }
+ }
+ else if (normalMantissaShift > 0)
+ {
+ mantissa <<= normalMantissaShift;
+ }
+ }
+
+ // Unset the hidden bit in the mantissa and assemble the floating point value
+ // from the computed components:
+ mantissa &= info.DenormalMantissaMask;
+
+ Debug.Assert((info.DenormalMantissaMask & (1UL << info.DenormalMantissaBits)) == 0);
+ ulong shiftedExponent = ((ulong)(exponent + info.ExponentBias)) << info.DenormalMantissaBits;
+ Debug.Assert((shiftedExponent & info.DenormalMantissaMask) == 0);
+ Debug.Assert((mantissa & ~info.DenormalMantissaMask) == 0);
+ Debug.Assert((shiftedExponent & ~(((1UL << info.ExponentBits) - 1) << info.DenormalMantissaBits)) == 0); // exponent fits in its place
+
+ return (shiftedExponent | mantissa);
+ }
+
+ private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger value, in FloatingPointInfo info, uint integerBitsOfPrecision, bool hasNonZeroFractionalPart)
+ {
+ int baseExponent = info.DenormalMantissaBits;
+
+ // When we have 64-bits or less of precision, we can just get the mantissa directly
+ if (integerBitsOfPrecision <= 64)
+ {
+ return AssembleFloatingPointBits(in info, value.ToUInt64(), baseExponent, !hasNonZeroFractionalPart);
+ }
+
+ uint topBlockIndex = Math.DivRem(integerBitsOfPrecision, 32, out uint topBlockBits);
+ uint middleBlockIndex = topBlockIndex - 1;
+ uint bottomBlockIndex = middleBlockIndex - 1;
+
+ ulong mantissa = 0;
+ int exponent = baseExponent + ((int)(bottomBlockIndex) * 32);
+ bool hasZeroTail = !hasNonZeroFractionalPart;
+
+ // When the top 64-bits perfectly span two blocks, we can get those blocks directly
+ if (topBlockBits == 0)
+ {
+ mantissa = ((ulong)(value.GetBlock(middleBlockIndex)) << 32) + value.GetBlock(bottomBlockIndex);
+ }
+ else
+ {
+ // Otherwise, we need to read three blocks and combine them into a 64-bit mantissa
+
+ int bottomBlockShift = (int)(topBlockBits);
+ int topBlockShift = 64 - bottomBlockShift;
+ int middleBlockShift = topBlockShift - 32;
+
+ exponent += (int)(topBlockBits);
+
+ uint bottomBlock = value.GetBlock(bottomBlockIndex);
+ uint bottomBits = bottomBlock >> bottomBlockShift;
+
+ ulong middleBits = (ulong)(value.GetBlock(middleBlockIndex)) << middleBlockShift;
+ ulong topBits = (ulong)(value.GetBlock(topBlockIndex)) << topBlockShift;
+
+ mantissa = topBits + middleBits + bottomBits;
+
+ uint unusedBottomBlockBitsMask = (1u << (int)(topBlockBits)) - 1;
+ hasZeroTail &= (bottomBlock & unusedBottomBlockBitsMask) == 0;
+ }
+
+ for (uint i = 0; i != bottomBlockIndex; i++)
+ {
+ hasZeroTail &= (value.GetBlock(i) == 0);
+ }
+
+ return AssembleFloatingPointBits(in info, mantissa, exponent, hasZeroTail);
+ }
+
+ // get 32-bit integer from at most 9 digits
+ private static uint DigitsToUInt32(char* p, int count)
+ {
+ Debug.Assert((1 <= count) && (count <= 9));
+
+ char* end = (p + count);
+ uint res = (uint)(p[0] - '0');
+
+ for (p++; p < end; p++)
+ {
+ res = (10 * res) + p[0] - '0';
+ }
+
+ return res;
+ }
+
+ // get 64-bit integer from at most 19 digits
+ private static ulong DigitsToUInt64(char* p, int count)
+ {
+ Debug.Assert((1 <= count) && (count <= 19));
+
+ char* end = (p + count);
+ ulong res = (ulong)(p[0] - '0');
+
+ for (p++; p < end; p++)
+ {
+ res = (10 * res) + p[0] - '0';
+ }
+
+ return res;
+ }
+
+ private static ulong NumberToFloatingPointBits(ref NumberBuffer number, in FloatingPointInfo info)
+ {
+ Debug.Assert(number.GetDigitsPointer()[0] != '0');
+
+ // The input is of the form 0.Mantissa x 10^Exponent, where 'Mantissa' are
+ // the decimal digits of the mantissa and 'Exponent' is the decimal exponent.
+ // We decompose the mantissa into two parts: an integer part and a fractional
+ // part. If the exponent is positive, then the integer part consists of the
+ // first 'exponent' digits, or all present digits if there are fewer digits.
+ // If the exponent is zero or negative, then the integer part is empty. In
+ // either case, the remaining digits form the fractional part of the mantissa.
+
+ uint totalDigits = (uint)(number.Precision);
+ uint positiveExponent = (uint)(Math.Max(0, number.Scale));
+
+ uint integerDigitsPresent = Math.Min(positiveExponent, totalDigits);
+ uint fractionalDigitsPresent = totalDigits - integerDigitsPresent;
+
+ uint fastExponent = (uint)(Math.Abs(number.Scale - integerDigitsPresent - fractionalDigitsPresent));
+
+ // When the number of significant digits is less than or equal to 15 and the
+ // scale is less than or equal to 22, we can take some shortcuts and just rely
+ // on floating-point arithmetic to compute the correct result. This is
+ // because each floating-point precision values allows us to exactly represent
+ // different whole integers and certain powers of 10, depending on the underlying
+ // formats exact range. Additionally, IEEE operations dictate that the result is
+ // computed to the infinitely precise result and then rounded, which means that
+ // we can rely on it to produce the correct result when both inputs are exact.
+
+ char* src = number.GetDigitsPointer();
+
+ if (totalDigits == 0)
+ {
+ return info.ZeroBits;
+ }
+
+ if ((info.DenormalMantissaBits == 23) && (totalDigits <= 7) && (fastExponent <= 10))
+ {
+ // It is only valid to do this optimization for single-precision floating-point
+ // values since we can lose some of the mantissa bits and would return the
+ // wrong value when upcasting to double.
+
+ float result = DigitsToUInt32(src, (int)(totalDigits));
+ float scale = s_Pow10SingleTable[fastExponent];
+
+ if (fractionalDigitsPresent != 0)
+ {
+ result /= scale;
+ }
+ else
+ {
+ result *= scale;
+ }
+
+ return (uint)(BitConverter.SingleToInt32Bits(result));
+ }
+
+ if ((totalDigits <= 15) && (fastExponent <= 22))
+ {
+ double result = DigitsToUInt64(src, (int)(totalDigits));
+ double scale = s_Pow10DoubleTable[fastExponent];
+
+ if (fractionalDigitsPresent != 0)
+ {
+ result /= scale;
+ }
+ else
+ {
+ result *= scale;
+ }
+
+ if (info.DenormalMantissaBits == 52)
+ {
+ return (ulong)(BitConverter.DoubleToInt64Bits(result));
+ }
+ else
+ {
+ Debug.Assert(info.DenormalMantissaBits == 23);
+ return (uint)(BitConverter.SingleToInt32Bits((float)(result)));
+ }
+ }
+
+ return NumberToFloatingPointBitsSlow(ref number, in info, positiveExponent, integerDigitsPresent, fractionalDigitsPresent);
+ }
+
+ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer number, in FloatingPointInfo info, uint positiveExponent, uint integerDigitsPresent, uint fractionalDigitsPresent)
+ {
+ // To generate an N bit mantissa we require N + 1 bits of precision. The
+ // extra bit is used to correctly round the mantissa (if there are fewer bits
+ // than this available, then that's totally okay; in that case we use what we
+ // have and we don't need to round).
+ uint requiredBitsOfPrecision = (uint)(info.NormalMantissaBits + 1);
+
+ uint totalDigits = (uint)(number.Precision);
+ uint integerDigitsMissing = positiveExponent - integerDigitsPresent;
+
+ uint integerFirstIndex = 0;
+ uint integerLastIndex = integerDigitsPresent;
+
+ uint fractionalFirstIndex = integerLastIndex;
+ uint fractionalLastIndex = totalDigits;
+
+ // First, we accumulate the integer part of the mantissa into a big_integer:
+ AccumulateDecimalDigitsIntoBigInteger(ref number, integerFirstIndex, integerLastIndex, out BigInteger integerValue);
+
+ if (integerDigitsMissing > 0)
+ {
+ if (integerDigitsMissing > info.OverflowDecimalExponent)
+ {
+ return info.InfinityBits;
+ }
+
+ integerValue.MultiplyPow10(integerDigitsMissing);
+ }
+
+ // At this point, the integer_value contains the value of the integer part
+ // of the mantissa. If either [1] this number has more than the required
+ // number of bits of precision or [2] the mantissa has no fractional part,
+ // then we can assemble the result immediately:
+ uint integerBitsOfPrecision = BigInteger.CountSignificantBits(ref integerValue);
+
+ if ((integerBitsOfPrecision >= requiredBitsOfPrecision) || (fractionalDigitsPresent == 0))
+ {
+ return ConvertBigIntegerToFloatingPointBits(
+ ref integerValue,
+ in info,
+ integerBitsOfPrecision,
+ fractionalDigitsPresent != 0
+ );
+ }
+
+ // Otherwise, we did not get enough bits of precision from the integer part,
+ // and the mantissa has a fractional part. We parse the fractional part of
+ // the mantissa to obtain more bits of precision. To do this, we convert
+ // the fractional part into an actual fraction N/M, where the numerator N is
+ // computed from the digits of the fractional part, and the denominator M is
+ // computed as the power of 10 such that N/M is equal to the value of the
+ // fractional part of the mantissa.
+
+ uint fractionalDenominatorExponent = fractionalDigitsPresent;
+
+ if (number.Scale < 0)
+ {
+ fractionalDenominatorExponent += (uint)(-number.Scale);
+ }
+
+ if ((integerBitsOfPrecision == 0) && (fractionalDenominatorExponent - (int)(totalDigits)) > info.OverflowDecimalExponent)
+ {
+ // If there were any digits in the integer part, it is impossible to
+ // underflow (because the exponent cannot possibly be small enough),
+ // so if we underflow here it is a true underflow and we return zero.
+ return info.ZeroBits;
+ }
+
+ AccumulateDecimalDigitsIntoBigInteger(ref number, fractionalFirstIndex, fractionalLastIndex, out BigInteger fractionalNumerator);
+ Debug.Assert(!fractionalNumerator.IsZero());
+
+ BigInteger.Pow10(fractionalDenominatorExponent, out BigInteger fractionalDenominator);
+
+ // Because we are using only the fractional part of the mantissa here, the
+ // numerator is guaranteed to be smaller than the denominator. We normalize
+ // the fraction such that the most significant bit of the numerator is in
+ // the same position as the most significant bit in the denominator. This
+ // ensures that when we later shift the numerator N bits to the left, we
+ // will produce N bits of precision.
+ uint fractionalNumeratorBits = BigInteger.CountSignificantBits(ref fractionalNumerator);
+ uint fractionalDenominatorBits = BigInteger.CountSignificantBits(ref fractionalDenominator);
+
+ uint fractionalShift = 0;
+
+ if (fractionalDenominatorBits > fractionalNumeratorBits)
+ {
+ fractionalShift = fractionalDenominatorBits - fractionalNumeratorBits;
+ }
+
+ if (fractionalShift > 0)
+ {
+ fractionalNumerator.ShiftLeft(fractionalShift);
+ }
+
+ uint requiredFractionalBitsOfPrecision = requiredBitsOfPrecision - integerBitsOfPrecision;
+ uint remainingBitsOfPrecisionRequired = requiredFractionalBitsOfPrecision;
+
+ if (integerBitsOfPrecision > 0)
+ {
+ // If the fractional part of the mantissa provides no bits of precision
+ // and cannot affect rounding, we can just take whatever bits we got from
+ // the integer part of the mantissa. This is the case for numbers like
+ // 5.0000000000000000000001, where the significant digits of the fractional
+ // part start so far to the right that they do not affect the floating
+ // point representation.
+ //
+ // If the fractional shift is exactly equal to the number of bits of
+ // precision that we require, then no fractional bits will be part of the
+ // result, but the result may affect rounding. This is e.g. the case for
+ // large, odd integers with a fractional part greater than or equal to .5.
+ // Thus, we need to do the division to correctly round the result.
+ if (fractionalShift > remainingBitsOfPrecisionRequired)
+ {
+ return ConvertBigIntegerToFloatingPointBits(
+ ref integerValue,
+ in info,
+ integerBitsOfPrecision,
+ fractionalDigitsPresent != 0
+ );
+ }
+
+ remainingBitsOfPrecisionRequired -= fractionalShift;
+ }
+
+ // If there was no integer part of the mantissa, we will need to compute the
+ // exponent from the fractional part. The fractional exponent is the power
+ // of two by which we must multiply the fractional part to move it into the
+ // range [1.0, 2.0). This will either be the same as the shift we computed
+ // earlier, or one greater than that shift:
+ uint fractionalExponent = fractionalShift;
+
+ if (BigInteger.Compare(ref fractionalNumerator, ref fractionalDenominator) < 0)
+ {
+ fractionalExponent++;
+ }
+
+ fractionalNumerator.ShiftLeft(remainingBitsOfPrecisionRequired);
+
+ BigInteger.DivRem(ref fractionalNumerator, ref fractionalDenominator, out BigInteger bigFractionalMantissa, out BigInteger fractionalRemainder);
+ ulong fractionalMantissa = bigFractionalMantissa.ToUInt64();
+ bool hasZeroTail = !number.HasNonZeroTail && fractionalRemainder.IsZero();
+
+ // We may have produced more bits of precision than were required. Check,
+ // and remove any "extra" bits:
+ uint fractionalMantissaBits = BigInteger.CountSignificantBits(fractionalMantissa);
+
+ if (fractionalMantissaBits > requiredFractionalBitsOfPrecision)
+ {
+ int shift = (int)(fractionalMantissaBits - requiredFractionalBitsOfPrecision);
+ hasZeroTail = hasZeroTail && (fractionalMantissa & ((1UL << shift) - 1)) == 0;
+ fractionalMantissa >>= shift;
+ }
+
+ // Compose the mantissa from the integer and fractional parts:
+ ulong integerMantissa = integerValue.ToUInt64();
+ ulong completeMantissa = (integerMantissa << (int)(requiredFractionalBitsOfPrecision)) + fractionalMantissa;
+
+ // Compute the final exponent:
+ // * If the mantissa had an integer part, then the exponent is one less than
+ // the number of bits we obtained from the integer part. (It's one less
+ // because we are converting to the form 1.11111, with one 1 to the left
+ // of the decimal point.)
+ // * If the mantissa had no integer part, then the exponent is the fractional
+ // exponent that we computed.
+ // Then, in both cases, we subtract an additional one from the exponent, to
+ // account for the fact that we've generated an extra bit of precision, for
+ // use in rounding.
+ int finalExponent = (integerBitsOfPrecision > 0) ? (int)(integerBitsOfPrecision) - 2 : -(int)(fractionalExponent) - 1;
+
+ return AssembleFloatingPointBits(in info, completeMantissa, finalExponent, hasZeroTail);
+ }
+
+ private static ulong RightShiftWithRounding(ulong value, int shift, bool hasZeroTail)
+ {
+ // If we'd need to shift further than it is possible to shift, the answer
+ // is always zero:
+ if (shift >= 64)
+ {
+ return 0;
+ }
+
+ ulong extraBitsMask = (1UL << (shift - 1)) - 1;
+ ulong roundBitMask = (1UL << (shift - 1));
+ ulong lsbBitMask = 1UL << shift;
+
+ bool lsbBit = (value & lsbBitMask) != 0;
+ bool roundBit = (value & roundBitMask) != 0;
+ bool hasTailBits = !hasZeroTail || (value & extraBitsMask) != 0;
+
+ return (value >> shift) + (ShouldRoundUp(lsbBit, roundBit, hasTailBits) ? 1UL : 0);
+ }
+
+ private static bool ShouldRoundUp(bool lsbBit, bool roundBit, bool hasTailBits)
+ {
+ // If there are insignificant set bits, we need to round to the
+ // nearest; there are two cases:
+ // we round up if either [1] the value is slightly greater than the midpoint
+ // between two exactly representable values or [2] the value is exactly the
+ // midpoint between two exactly representable values and the greater of the
+ // two is even (this is "round-to-even").
+ return roundBit && (hasTailBits || lsbBit);
+ }
+ }
+}
diff --git a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs
index 802f157451..60185b44b5 100644
--- a/src/System.Private.CoreLib/shared/System/Number.Parsing.cs
+++ b/src/System.Private.CoreLib/shared/System/Number.Parsing.cs
@@ -252,8 +252,12 @@ namespace System
const int StateDecimal = 0x0010;
const int StateCurrency = 0x0020;
- number.Scale = 0;
- number.Sign = false;
+ Debug.Assert(number.Precision == 0);
+ Debug.Assert(number.Scale == 0);
+ Debug.Assert(number.Sign == false);
+ Debug.Assert(number.HasNonZeroTail == false);
+ Debug.Assert(number.Kind != NumberBufferKind.Unknown);
+
string decSep; // decimal separator from NumberFormatInfo.
string groupSep; // group separator from NumberFormatInfo.
string currSymbol = null; // currency symbol from NumberFormatInfo.
@@ -327,11 +331,23 @@ namespace System
if (digCount < maxDigCount)
{
number.Digits[digCount++] = ch;
- if (ch != '0' || number.Kind == NumberBufferKind.Decimal)
+ if ((ch != '0') || (number.Kind != NumberBufferKind.Integer))
{
digEnd = digCount;
}
}
+ else if (ch != '0')
+ {
+ // For decimal and binary floating-point numbers, we only
+ // need to store digits up to maxDigCount. However, we still
+ // need to keep track of whether any additional digits past
+ // maxDigCount were non-zero, as that can impact rounding
+ // for an input that falls evenly between two representable
+ // results.
+
+ number.HasNonZeroTail = true;
+ }
+
if ((state & StateDecimal) == 0)
{
number.Scale++;
@@ -1576,18 +1592,20 @@ namespace System
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)
+ if ((c == '5') && ((low64 & 1) == 0))
{
c = *++p;
- int count = 20; // Look at the next 20 digits to check to round
- while (c == '0' && count != 0)
+
+ // At this point we should either be at the end of the buffer, or just
+ // have a single rounding digit left, and the next should be the end
+ Debug.Assert((c == 0) || (p[1] == 0));
+
+ if (((c == 0) || c == '0') && !number.HasNonZeroTail)
{
- c = *++p;
- count--;
+ // When the next digit is 5, the number is even, and all following digits are zero
+ // we don't need to round.
+ goto NoRounding;
}
- if (c == 0 || count == 0)
- goto NoRounding;// Do nothing
}
if (++low64 == 0 && ++high == 0)
@@ -1617,9 +1635,9 @@ namespace System
internal static double ParseDouble(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
{
- if (!TryParseDouble(value, styles, info, out double result, out bool failureIsOverflow))
+ if (!TryParseDouble(value, styles, info, out double result))
{
- ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Double));
+ ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null);
}
return result;
@@ -1627,9 +1645,9 @@ namespace System
internal static float ParseSingle(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info)
{
- if (!TryParseSingle(value, styles, info, out float result, out bool failureIsOverflow))
+ if (!TryParseSingle(value, styles, info, out float result))
{
- ThrowOverflowOrFormatException(failureIsOverflow, nameof(SR.Overflow_Single));
+ ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null);
}
return result;
@@ -1657,65 +1675,73 @@ namespace System
return true;
}
- internal static unsafe bool TryParseDouble(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out double result, out bool failureIsOverflow)
+ internal static unsafe bool TryParseDouble(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out double result)
{
char* pDigits = stackalloc char[DoubleNumberBufferLength];
- NumberBuffer number = new NumberBuffer(NumberBufferKind.Double, pDigits, DoubleNumberBufferLength);
-
- result = 0;
- failureIsOverflow = false;
+ NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, DoubleNumberBufferLength);
if (!TryStringToNumber(value, styles, ref number, info))
{
ReadOnlySpan<char> valueTrim = value.Trim();
- if (valueTrim.EqualsOrdinal(info.PositiveInfinitySymbol))
+ if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol))
{
result = double.PositiveInfinity;
}
- else if (valueTrim.EqualsOrdinal(info.NegativeInfinitySymbol))
+ else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol))
{
result = double.NegativeInfinity;
}
- else if (valueTrim.EqualsOrdinal(info.NaNSymbol))
+ else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol))
{
result = double.NaN;
}
else
{
+ result = 0;
return false; // We really failed
}
-
- return true;
}
-
- if (!TryNumberToDouble(ref number, ref result))
+ else
{
- failureIsOverflow = true;
- return false;
+ result = NumberToDouble(ref number);
}
return true;
}
- internal static bool TryParseSingle(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out float result, out bool failureIsOverflow)
+ internal static unsafe bool TryParseSingle(ReadOnlySpan<char> value, NumberStyles styles, NumberFormatInfo info, out float result)
{
- result = 0;
+ char* pDigits = stackalloc char[SingleNumberBufferLength];
+ NumberBuffer number = new NumberBuffer(NumberBufferKind.FloatingPoint, pDigits, SingleNumberBufferLength);
- if (!TryParseDouble(value, styles, info, out double doubleResult, out failureIsOverflow))
+ if (!TryStringToNumber(value, styles, ref number, info))
{
- return false;
- }
-
- float singleResult = (float)(doubleResult);
+ ReadOnlySpan<char> valueTrim = value.Trim();
- if (float.IsInfinity(singleResult) && double.IsFinite(doubleResult))
+ if (valueTrim.EqualsOrdinalIgnoreCase(info.PositiveInfinitySymbol))
+ {
+ result = float.PositiveInfinity;
+ }
+ else if (valueTrim.EqualsOrdinalIgnoreCase(info.NegativeInfinitySymbol))
+ {
+ result = float.NegativeInfinity;
+ }
+ else if (valueTrim.EqualsOrdinalIgnoreCase(info.NaNSymbol))
+ {
+ result = float.NaN;
+ }
+ else
+ {
+ result = 0;
+ return false; // We really failed
+ }
+ }
+ else
{
- failureIsOverflow = true;
- return false;
+ result = NumberToSingle(ref number);
}
- result = singleResult;
return true;
}
@@ -1723,7 +1749,7 @@ namespace System
{
if (!TryStringToNumber(value, styles, ref number, info))
{
- ThrowOverflowOrFormatException(overflow: false, null);
+ ThrowOverflowOrFormatException(overflow: false, overflowResourceKey: null);
}
}
@@ -1797,23 +1823,18 @@ namespace System
(Exception)new FormatException(SR.Format_InvalidString);
}
- private static bool TryNumberToDouble(ref NumberBuffer number, ref double value)
+ private static double NumberToDouble(ref NumberBuffer number)
{
- double d = NumberToDouble(ref number);
- if (!double.IsFinite(d))
- {
- value = default;
- return false;
- }
-
- if (d == 0.0)
- {
- // normalize -0.0 to 0.0
- d = 0.0;
- }
+ ulong bits = NumberToFloatingPointBits(ref number, in FloatingPointInfo.Double);
+ double result = BitConverter.Int64BitsToDouble((long)(bits));
+ return number.Sign ? -result : result;
+ }
- value = d;
- return true;
+ private static float NumberToSingle(ref NumberBuffer number)
+ {
+ uint bits = (uint)(NumberToFloatingPointBits(ref number, in FloatingPointInfo.Single));
+ float result = BitConverter.Int32BitsToSingle((int)(bits));
+ return number.Sign ? -result : result;
}
}
}
diff --git a/src/System.Private.CoreLib/shared/System/Single.cs b/src/System.Private.CoreLib/shared/System/Single.cs
index dfe1af3f8c..4a0a06f139 100644
--- a/src/System.Private.CoreLib/shared/System/Single.cs
+++ b/src/System.Private.CoreLib/shared/System/Single.cs
@@ -330,7 +330,7 @@ namespace System
private static bool TryParse(ReadOnlySpan<char> s, NumberStyles style, NumberFormatInfo info, out float result)
{
- return Number.TryParseSingle(s, style, info, out result, out _);
+ return Number.TryParseSingle(s, style, info, out result);
}
//
diff --git a/tests/CoreFX/CoreFX.issues.json b/tests/CoreFX/CoreFX.issues.json
index 2f15282a7f..b784fbd635 100644
--- a/tests/CoreFX/CoreFX.issues.json
+++ b/tests/CoreFX/CoreFX.issues.json
@@ -494,7 +494,15 @@
{
"name": "System.SpanTests.MemoryMarshalTests.CreateFromPinnedArrayIntReadOnlyMemorySliceRemainsPinned",
"reason": "https://github.com/dotnet/corefx/pull/32994"
- }
+ },
+ {
+ "name": "System.Buffers.Text.Tests.ParserTests.TestParserSingle",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
+ {
+ "name": "System.Buffers.Text.Tests.ParserTests.TestParserDouble",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
]
}
},
@@ -508,7 +516,15 @@
{
"name": "System.Tests.ConvertToStringTests.FromBoxedObject",
"reason": "https://github.com/dotnet/coreclr/pull/19775"
- }
+ },
+ {
+ "name": "System.Tests.ConvertToDoubleTests.FromString",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
+ {
+ "name": "System.Tests.ConvertToSingleTests.FromString",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
]
}
},
@@ -684,5 +700,73 @@
}
]
}
- }
+ },
+ {
+ "name": "System.Xml.Linq.SDMSample.Tests",
+ "enabled": true,
+ "exclusions": {
+ "namespaces": null,
+ "classes": null,
+ "methods": [
+ {
+ "name": "XDocumentTests.SDMSample.SDM_Attribute.AttributeExplicitToDouble",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
+ {
+ "name": "XDocumentTests.SDMSample.SDM_Attribute.AttributeExplicitToFloat",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
+ {
+ "name": "XDocumentTests.SDMSample.SDM_Element.ElementExplicitToFloat",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
+ {
+ "name": "XDocumentTests.SDMSample.SDM_Element.ElementExplicitToDouble",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
+ ]
+ }
+ },
+ {
+ "name": "System.Json.Tests",
+ "enabled": true,
+ "exclusions": {
+ "namespaces": null,
+ "classes": null,
+ "methods": [
+ {
+ "name": "System.Json.Tests.JsonValueTests.Parse_DoubleTooLarge_ThrowsOverflowException",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
+ ]
+ }
+ },
+ {
+ "name": "System.Xml.RW.XmlWriterApi.Tests",
+ "enabled": true,
+ "exclusions": {
+ "namespaces": null,
+ "classes": null,
+ "methods": [
+ {
+ "name": "System.Xml.Tests.TCFullEndElement+TCWriteValue.writeValue_27",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
+ ]
+ }
+ },
+ {
+ "name": "System.ComponentModel.Annotations.Tests",
+ "enabled": true,
+ "exclusions": {
+ "namespaces": null,
+ "classes": null,
+ "methods": [
+ {
+ "name": "System.ComponentModel.DataAnnotations.Tests.RangeAttributeTests.Validate_DoubleConversionOverflows_ThrowsOverflowException",
+ "reason": "https://github.com/dotnet/coreclr/pull/20707"
+ },
+ ]
+ }
+ },
]