diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs b/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs index 733faa5320aab3..4c5fd32227de25 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.BigInteger.cs @@ -11,7 +11,7 @@ namespace System internal static partial class Number { [StructLayout(LayoutKind.Sequential, Pack = 1)] - internal unsafe ref struct BigInteger + internal ref struct BigInteger { // The longest binary mantissa requires: explicit mantissa bits + abs(min exponent) // * Half: 10 + 14 = 24 @@ -318,10 +318,12 @@ internal unsafe ref struct BigInteger ]; private int _length; - private fixed uint _blocks[MaxBlockCount]; + private BlocksBuffer _blocks; public static void Add(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out BigInteger result) { + Unsafe.SkipInit(out result); + // determine which operand has the smaller length ref BigInteger large = ref (lhs._length < rhs._length) ? ref rhs : ref lhs; ref BigInteger small = ref (lhs._length < rhs._length) ? ref lhs : ref rhs; @@ -343,7 +345,7 @@ public static void Add(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out { ulong sum = carry + large._blocks[largeIndex] + small._blocks[smallIndex]; carry = sum >> 32; - result._blocks[resultIndex] = (uint)(sum); + result._blocks[resultIndex] = (uint)sum; largeIndex++; smallIndex++; @@ -355,7 +357,7 @@ public static void Add(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out { ulong sum = carry + large._blocks[largeIndex]; carry = sum >> 32; - result._blocks[resultIndex] = (uint)(sum); + result._blocks[resultIndex] = (uint)sum; largeIndex++; resultIndex++; @@ -368,9 +370,9 @@ public static void Add(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out { Debug.Assert(carry == 1); Debug.Assert(resultIndex == resultLength); - Debug.Assert(unchecked((uint)(resultLength)) < MaxBlockCount); + Debug.Assert(unchecked((uint)resultLength) < MaxBlockCount); - if (unchecked((uint)(resultLength)) >= MaxBlockCount) + if (unchecked((uint)resultLength) >= MaxBlockCount) { // We shouldn't reach here, and the above assert will help flag this // during testing, but we'll ensure that we return a safe value of @@ -387,13 +389,13 @@ public static void Add(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out public static int Compare(scoped ref BigInteger lhs, scoped ref BigInteger rhs) { - Debug.Assert(unchecked((uint)(lhs._length)) <= MaxBlockCount); - Debug.Assert(unchecked((uint)(rhs._length)) <= MaxBlockCount); + Debug.Assert(unchecked((uint)lhs._length) <= MaxBlockCount); + Debug.Assert(unchecked((uint)rhs._length) <= MaxBlockCount); int lhsLength = lhs._length; int rhsLength = rhs._length; - int lengthDelta = (lhsLength - rhsLength); + int lengthDelta = lhsLength - rhsLength; if (lengthDelta != 0) { @@ -406,9 +408,9 @@ public static int Compare(scoped ref BigInteger lhs, scoped ref BigInteger rhs) return 0; } - for (int index = (lhsLength - 1); index >= 0; index--) + for (int index = lhsLength - 1; index >= 0; index--) { - long delta = (long)(lhs._blocks[index]) - rhs._blocks[index]; + long delta = (long)lhs._blocks[index] - rhs._blocks[index]; if (delta != 0) { @@ -419,17 +421,17 @@ public static int Compare(scoped ref BigInteger lhs, scoped ref BigInteger rhs) return 0; } - public static uint CountSignificantBits(uint value) + public static int CountSignificantBits(uint value) { - return 32 - (uint)BitOperations.LeadingZeroCount(value); + return 32 - BitOperations.LeadingZeroCount(value); } - public static uint CountSignificantBits(ulong value) + public static int CountSignificantBits(ulong value) { - return 64 - (uint)BitOperations.LeadingZeroCount(value); + return 64 - BitOperations.LeadingZeroCount(value); } - public static uint CountSignificantBits(ref BigInteger value) + public static int CountSignificantBits(ref BigInteger value) { if (value.IsZero()) { @@ -439,12 +441,14 @@ public static uint CountSignificantBits(ref BigInteger value) // 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); + int lastIndex = value._length - 1; return (lastIndex * BitsPerBlock) + CountSignificantBits(value._blocks[lastIndex]); } public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out BigInteger quo, out BigInteger rem) { + Unsafe.SkipInit(out quo); + // This is modified from the libraries BigIntegerCalculator.DivRem.cs implementation: // https://github.com/dotnet/runtime/blob/main/src/libraries/System.Runtime.Numerics/src/System/Numerics/BigIntegerCalculator.DivRem.cs @@ -489,12 +493,12 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs, } else { - quo._blocks[i] = (uint)(digit); + quo._blocks[i] = (uint)digit; } } quo._length = quoLength; - SetUInt32(out rem, (uint)(carry)); + SetUInt32(out rem, (uint)carry); return; } @@ -531,7 +535,7 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs, if (rhsLength > 2) { - divLo |= (rhs._blocks[rhsLength - 3] >> shiftRight); + divLo |= rhs._blocks[rhsLength - 3] >> shiftRight; } } @@ -542,7 +546,7 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs, int n = i - rhsLength; uint t = i < lhsLength ? rem._blocks[i] : 0; - ulong valHi = ((ulong)(t) << 32) | rem._blocks[i - 1]; + 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 @@ -553,7 +557,7 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs, if (i > 2) { - valLo |= (rem._blocks[i - 3] >> shiftRight); + valLo |= rem._blocks[i - 3] >> shiftRight; } } @@ -604,7 +608,7 @@ public static void DivRem(scoped ref BigInteger lhs, scoped ref BigInteger rhs, } else { - quo._blocks[n] = (uint)(digit); + quo._blocks[n] = (uint)digit; } } @@ -647,7 +651,7 @@ public static uint HeuristicDivide(ref BigInteger dividend, ref BigInteger divis // This is an estimated quotient. Its error should be less than 2. // Reference inequality: // a/b - floor(floor(a)/(floor(b) + 1)) < 2 - int lastIndex = (divisorLength - 1); + int lastIndex = divisorLength - 1; uint quotient = dividend._blocks[lastIndex] / (divisor._blocks[lastIndex] + 1); if (quotient != 0) @@ -661,13 +665,13 @@ public static uint HeuristicDivide(ref BigInteger dividend, ref BigInteger divis do { - ulong product = ((ulong)(divisor._blocks[index]) * quotient) + carry; + ulong product = ((ulong)divisor._blocks[index] * quotient) + carry; carry = product >> 32; - ulong difference = (ulong)(dividend._blocks[index]) - (uint)(product) - borrow; + ulong difference = (ulong)dividend._blocks[index] - (uint)product - borrow; borrow = (difference >> 32) & 1; - dividend._blocks[index] = (uint)(difference); + dividend._blocks[index] = (uint)difference; index++; } @@ -694,10 +698,10 @@ public static uint HeuristicDivide(ref BigInteger dividend, ref BigInteger divis do { - ulong difference = (ulong)(dividend._blocks[index]) - divisor._blocks[index] - borrow; + ulong difference = (ulong)dividend._blocks[index] - divisor._blocks[index] - borrow; borrow = (difference >> 32) & 1; - dividend._blocks[index] = (uint)(difference); + dividend._blocks[index] = (uint)difference; index++; } @@ -717,6 +721,8 @@ public static uint HeuristicDivide(ref BigInteger dividend, ref BigInteger divis public static void Multiply(scoped ref BigInteger lhs, uint value, out BigInteger result) { + Unsafe.SkipInit(out result); + if (lhs._length <= 1) { SetUInt64(out result, (ulong)lhs.ToUInt32() * value); @@ -742,8 +748,8 @@ public static void Multiply(scoped ref BigInteger lhs, uint value, out BigIntege while (index < lhsLength) { - ulong product = ((ulong)(lhs._blocks[index]) * value) + carry; - result._blocks[index] = (uint)(product); + ulong product = ((ulong)lhs._blocks[index] * value) + carry; + result._blocks[index] = (uint)product; carry = (uint)(product >> 32); index++; @@ -753,9 +759,9 @@ public static void Multiply(scoped ref BigInteger lhs, uint value, out BigIntege if (carry != 0) { - Debug.Assert(unchecked((uint)(resultLength)) < MaxBlockCount); + Debug.Assert(unchecked((uint)resultLength) < MaxBlockCount); - if (unchecked((uint)(resultLength)) >= MaxBlockCount) + if (unchecked((uint)resultLength) >= MaxBlockCount) { // We shouldn't reach here, and the above assert will help flag this // during testing, but we'll ensure that we return a safe value of @@ -769,11 +775,13 @@ public static void Multiply(scoped ref BigInteger lhs, uint value, out BigIntege resultLength += 1; } - result._length = resultLength; + result._length = resultLength; } public static void Multiply(scoped ref BigInteger lhs, scoped ref BigInteger rhs, out BigInteger result) { + Unsafe.SkipInit(out result); + if (lhs._length <= 1) { Multiply(ref rhs, lhs.ToUInt32(), out result); @@ -802,9 +810,9 @@ public static void Multiply(scoped ref BigInteger lhs, scoped ref BigInteger rhs } int maxResultLength = smallLength + largeLength; - Debug.Assert(unchecked((uint)(maxResultLength)) <= MaxBlockCount); + Debug.Assert(unchecked((uint)maxResultLength) <= MaxBlockCount); - if (unchecked((uint)(maxResultLength)) > MaxBlockCount) + if (unchecked((uint)maxResultLength) > MaxBlockCount) { // We shouldn't reach here, and the above assert will help flag this // during testing, but we'll ensure that we return a safe value of @@ -816,7 +824,7 @@ public static void Multiply(scoped ref BigInteger lhs, scoped ref BigInteger rhs // Zero out result internal blocks. result._length = maxResultLength; - result.Clear((uint)maxResultLength); + result.Clear(maxResultLength); int smallIndex = 0; int resultStartIndex = 0; @@ -833,16 +841,16 @@ public static void Multiply(scoped ref BigInteger lhs, scoped ref BigInteger rhs do { - ulong product = result._blocks[resultIndex] + ((ulong)(small._blocks[smallIndex]) * large._blocks[largeIndex]) + carry; + ulong product = result._blocks[resultIndex] + ((ulong)small._blocks[smallIndex] * large._blocks[largeIndex]) + carry; carry = product >> 32; - result._blocks[resultIndex] = (uint)(product); + result._blocks[resultIndex] = (uint)product; resultIndex++; largeIndex++; } while (largeIndex < largeLength); - result._blocks[resultIndex] = (uint)(carry); + result._blocks[resultIndex] = (uint)carry; } smallIndex++; @@ -855,10 +863,12 @@ public static void Multiply(scoped ref BigInteger lhs, scoped ref BigInteger rhs } } - public static void Pow2(uint exponent, out BigInteger result) + public static void Pow2(int exponent, out BigInteger result) { - uint blocksToShift = DivRem32(exponent, out uint remainingBitsToShift); - result._length = (int)blocksToShift + 1; + Unsafe.SkipInit(out result); + + int blocksToShift = DivRem32(exponent, out int remainingBitsToShift); + result._length = blocksToShift + 1; Debug.Assert(unchecked((uint)result._length) <= MaxBlockCount); @@ -876,7 +886,7 @@ public static void Pow2(uint exponent, out BigInteger result) { result.Clear(blocksToShift); } - result._blocks[blocksToShift] = 1U << (int)(remainingBitsToShift); + result._blocks[blocksToShift] = 1U << remainingBitsToShift; } public static void Pow10(uint exponent, out BigInteger result) @@ -918,7 +928,7 @@ public static void Pow10(uint exponent, out BigInteger result) ref BigInteger product = ref temp2; exponent >>= 3; - uint index = 0; + int index = 0; while (exponent != 0) { @@ -926,9 +936,16 @@ public static void Pow10(uint exponent, out BigInteger result) if ((exponent & 1) != 0) { // Multiply into the next temporary - fixed (uint* pBigNumEntry = &Pow10BigNumTable[Pow10BigNumTableIndices[(int)index]]) + unsafe { - ref BigInteger rhs = ref *(BigInteger*)(pBigNumEntry); + // It is safe to reinterpret the memory at the indexed position of Pow10BigNumTable as a BigInteger, + // because Pow10BigNumTable is laid out such that each indexed position contains a valid BigInteger + // representation with the correct structure and alignment. + + int pow10BigNumTableIndex = Pow10BigNumTableIndices[index]; + Debug.Assert((pow10BigNumTableIndex + ((sizeof(BigInteger) + sizeof(uint) - 1) / sizeof(uint))) < Pow10BigNumTable.Length); + + ref BigInteger rhs = ref Unsafe.As(ref Unsafe.AsRef(in Pow10BigNumTable[pow10BigNumTableIndex])); Multiply(ref lhs, ref rhs, out product); } @@ -968,7 +985,7 @@ private static uint AddDivisor(ref BigInteger lhs, int lhsStartIndex, ref BigInt carry = digit >> 32; } - return (uint)(carry); + return (uint)carry; } private static bool DivideGuessTooBig(ulong q, ulong valHi, uint valLo, uint divHi, uint divLo) @@ -983,7 +1000,7 @@ private static bool DivideGuessTooBig(ulong q, ulong valHi, uint valLo, uint div ulong chkHi = divHi * q; ulong chkLo = divLo * q; - chkHi += (chkLo >> 32); + chkHi += chkLo >> 32; chkLo &= uint.MaxValue; if (chkHi < valHi) @@ -1032,7 +1049,7 @@ private static uint SubtractDivisor(ref BigInteger lhs, int lhsStartIndex, ref B lhsValue = unchecked(lhsValue - digit); } - return (uint)(carry); + return (uint)carry; } public void Add(uint value) @@ -1061,9 +1078,9 @@ public void Add(uint value) } } - Debug.Assert(unchecked((uint)(length)) < MaxBlockCount); + Debug.Assert(unchecked((uint)length) < MaxBlockCount); - if (unchecked((uint)(length)) >= MaxBlockCount) + if (unchecked((uint)length) >= MaxBlockCount) { // We shouldn't reach here, and the above assert will help flag this // during testing, but we'll ensure that we return a safe value of @@ -1077,18 +1094,18 @@ public void Add(uint value) _length = length + 1; } - public uint GetBlock(uint index) + public readonly uint GetBlock(int index) { - Debug.Assert(index < _length); + Debug.Assert((uint)index < _length); return _blocks[index]; } - public int GetLength() + public readonly int GetLength() { return _length; } - public bool IsZero() + public readonly bool IsZero() { return _length == 0; } @@ -1124,19 +1141,19 @@ public void Multiply10() do { - ulong block = (ulong)(_blocks[index]); + ulong block = _blocks[index]; ulong product = (block << 3) + (block << 1) + carry; carry = product >> 32; - _blocks[index] = (uint)(product); + _blocks[index] = (uint)product; index++; } while (index < length); if (carry != 0) { - Debug.Assert(unchecked((uint)(length)) < MaxBlockCount); + Debug.Assert(unchecked((uint)length) < MaxBlockCount); - if (unchecked((uint)(length)) >= MaxBlockCount) + if (unchecked((uint)length) >= MaxBlockCount) { // We shouldn't reach here, and the above assert will help flag this // during testing, but we'll ensure that we return a safe value of @@ -1166,6 +1183,8 @@ public void MultiplyPow10(uint exponent) public static void SetUInt32(out BigInteger result, uint value) { + Unsafe.SkipInit(out result); + if (value == 0) { SetZero(out result); @@ -1179,13 +1198,15 @@ public static void SetUInt32(out BigInteger result, uint value) public static void SetUInt64(out BigInteger result, ulong value) { + Unsafe.SkipInit(out result); + if (value <= uint.MaxValue) { - SetUInt32(out result, (uint)(value)); + SetUInt32(out result, (uint)value); } else { - result._blocks[0] = (uint)(value); + result._blocks[0] = (uint)value; result._blocks[1] = (uint)(value >> 32); result._length = 2; @@ -1194,18 +1215,23 @@ public static void SetUInt64(out BigInteger result, ulong value) public static void SetValue(out BigInteger result, scoped ref BigInteger value) { + Unsafe.SkipInit(out result); int rhsLength = value._length; + result._length = rhsLength; Buffer.Memmove(ref result._blocks[0], ref value._blocks[0], (nuint)rhsLength); } public static void SetZero(out BigInteger result) { + Unsafe.SkipInit(out result); result._length = 0; } - public void ShiftLeft(uint shift) + public void ShiftLeft(int shift) { + Debug.Assert(shift >= 0); + // Process blocks high to low so that we can safely process in place int length = _length; @@ -1214,18 +1240,18 @@ public void ShiftLeft(uint shift) return; } - uint blocksToShift = DivRem32(shift, out uint remainingBitsToShift); + int blocksToShift = DivRem32(shift, out int remainingBitsToShift); // Copy blocks from high to low - int readIndex = (length - 1); - int writeIndex = readIndex + (int)(blocksToShift); + int readIndex = length - 1; + int writeIndex = readIndex + blocksToShift; // Check if the shift is block aligned if (remainingBitsToShift == 0) { - Debug.Assert(unchecked((uint)(length)) < MaxBlockCount); + Debug.Assert(unchecked((uint)length) < MaxBlockCount); - if (unchecked((uint)(length)) >= MaxBlockCount) + if (unchecked((uint)length) >= MaxBlockCount) { // We shouldn't reach here, and the above assert will help flag this // during testing, but we'll ensure that we return a safe value of @@ -1242,7 +1268,7 @@ public void ShiftLeft(uint shift) writeIndex--; } - _length += (int)(blocksToShift); + _length += blocksToShift; // Zero the remaining low blocks Clear(blocksToShift); @@ -1252,9 +1278,9 @@ public void ShiftLeft(uint shift) // We need an extra block for the partial shift writeIndex++; - Debug.Assert(unchecked((uint)(length)) < MaxBlockCount); + Debug.Assert(unchecked((uint)length) < MaxBlockCount); - if (unchecked((uint)(length)) >= MaxBlockCount) + if (unchecked((uint)length) >= MaxBlockCount) { // We shouldn't reach here, and the above assert will help flag this // during testing, but we'll ensure that we return a safe value of @@ -1268,25 +1294,27 @@ public void ShiftLeft(uint shift) _length = writeIndex + 1; // Output the initial blocks - uint lowBitsShift = (32 - remainingBitsToShift); + int lowBitsShift = 32 - remainingBitsToShift; + uint highBits = 0; uint block = _blocks[readIndex]; - uint lowBits = block >> (int)(lowBitsShift); + uint lowBits = block >> lowBitsShift; + while (readIndex > 0) { _blocks[writeIndex] = highBits | lowBits; - highBits = block << (int)(remainingBitsToShift); + highBits = block << remainingBitsToShift; --readIndex; --writeIndex; block = _blocks[readIndex]; - lowBits = block >> (int)lowBitsShift; + lowBits = block >> lowBitsShift; } // Output the final blocks _blocks[writeIndex] = highBits | lowBits; - _blocks[writeIndex - 1] = block << (int)(remainingBitsToShift); + _blocks[writeIndex - 1] = block << remainingBitsToShift; // Zero the remaining low blocks Clear(blocksToShift); @@ -1299,7 +1327,7 @@ public void ShiftLeft(uint shift) } } - public uint ToUInt32() + public readonly uint ToUInt32() { if (_length > 0) { @@ -1309,11 +1337,11 @@ public uint ToUInt32() return 0; } - public ulong ToUInt64() + public readonly ulong ToUInt64() { if (_length > 1) { - return ((ulong)(_blocks[1]) << 32) + _blocks[0]; + return ((ulong)_blocks[1] << 32) + _blocks[0]; } if (_length > 0) @@ -1324,15 +1352,19 @@ public ulong ToUInt64() return 0; } - private void Clear(uint length) => - NativeMemory.Clear( - (byte*)Unsafe.AsPointer(ref _blocks[0]), // This is safe to do since we are a ref struct - length * sizeof(uint)); + private void Clear(int length) => ((Span)_blocks).Slice(0, length).Clear(); - private static uint DivRem32(uint value, out uint remainder) + private static int DivRem32(int value, out int remainder) { + Debug.Assert(value >= 0); remainder = value & 31; - return value >> 5; + return value >>> 5; + } + + [InlineArray(MaxBlockCount)] + private struct BlocksBuffer + { + public uint e0; } } } diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs index 17030547014da1..d6d4ff416999cd 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Dragon4.cs @@ -3,6 +3,7 @@ using System.Diagnostics; using System.Numerics; +using System.Runtime.CompilerServices; namespace System { @@ -53,7 +54,7 @@ public static void Dragon4(TNumber value, int cutoffNumber, bool isSign // "Printing Floating-Point Numbers Quickly and Accurately" // Burger and Dybvig // http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.4656&rep=rep1&type=pdf - private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHighBitIdx, bool hasUnequalMargins, int cutoffNumber, bool isSignificantDigits, Span buffer, out int decimalExponent) + private static uint Dragon4(ulong mantissa, int exponent, uint mantissaHighBitIdx, bool hasUnequalMargins, int cutoffNumber, bool isSignificantDigits, Span buffer, out int decimalExponent) { int curDigit = 0; @@ -76,11 +77,12 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi // For normalized IEEE floating-point values, each time the exponent is incremented the margin also doubles. // That creates a subset of transition numbers where the high margin is twice the size of the low margin. - BigInteger* pScaledMarginHigh; BigInteger optionalMarginHigh; if (hasUnequalMargins) { + // The high and low margins are different + if (exponent > 0) // We have no fractional component { // 1) Expand the input value by multiplying out the mantissa and exponent. @@ -90,16 +92,16 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi // scaledValue = 2 * 2 * mantissa * 2^exponent BigInteger.SetUInt64(out scaledValue, 4 * mantissa); - scaledValue.ShiftLeft((uint)(exponent)); + scaledValue.ShiftLeft(exponent); // scale = 2 * 2 * 1 BigInteger.SetUInt32(out scale, 4); // scaledMarginLow = 2 * 2^(exponent - 1) - BigInteger.Pow2((uint)(exponent), out scaledMarginLow); + BigInteger.Pow2(exponent, out scaledMarginLow); // scaledMarginHigh = 2 * 2 * 2^(exponent + 1) - BigInteger.Pow2((uint)(exponent + 1), out optionalMarginHigh); + BigInteger.Pow2(exponent + 1, out optionalMarginHigh); } else // We have a fractional exponent { @@ -109,7 +111,7 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi BigInteger.SetUInt64(out scaledValue, 4 * mantissa); // scale = 2 * 2 * 2^(-exponent) - BigInteger.Pow2((uint)(-exponent + 2), out scale); + BigInteger.Pow2(-exponent + 2, out scale); // scaledMarginLow = 2 * 2^(-1) BigInteger.SetUInt32(out scaledMarginLow, 1); @@ -117,12 +119,11 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi // scaledMarginHigh = 2 * 2 * 2^(-1) BigInteger.SetUInt32(out optionalMarginHigh, 2); } - - // The high and low margins are different - pScaledMarginHigh = &optionalMarginHigh; } else { + // The high and low margins are equal + if (exponent > 0) // We have no fractional component { // 1) Expand the input value by multiplying out the mantissa and exponent. @@ -132,13 +133,13 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi // scaledValue = 2 * mantissa*2^exponent BigInteger.SetUInt64(out scaledValue, 2 * mantissa); - scaledValue.ShiftLeft((uint)(exponent)); + scaledValue.ShiftLeft(exponent); // scale = 2 * 1 BigInteger.SetUInt32(out scale, 2); // scaledMarginLow = 2 * 2^(exponent-1) - BigInteger.Pow2((uint)(exponent), out scaledMarginLow); + BigInteger.Pow2(exponent, out scaledMarginLow); } else // We have a fractional exponent { @@ -148,16 +149,19 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi BigInteger.SetUInt64(out scaledValue, 2 * mantissa); // scale = 2 * 2^(-exponent) - BigInteger.Pow2((uint)(-exponent + 1), out scale); + BigInteger.Pow2(-exponent + 1, out scale); // scaledMarginLow = 2 * 2^(-1) BigInteger.SetUInt32(out scaledMarginLow, 1); } - // The high and low margins are equal - pScaledMarginHigh = &scaledMarginLow; + // This is unused for this path, but we need it viewed as "initialized" so the + // scaledMarginHigh tracking works as expected. + Unsafe.SkipInit(out optionalMarginHigh); } + scoped ref BigInteger scaledMarginHigh = ref (hasUnequalMargins ? ref optionalMarginHigh : ref scaledMarginLow); + // Compute an estimate for digitExponent that will be correct or undershoot by one. // // This optimization is based on the paper "Printing Floating-Point Numbers Quickly and Accurately" by Burger and Dybvig http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.72.4656&rep=rep1&type=pdf @@ -191,9 +195,9 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi scaledValue.Multiply(ref pow10); scaledMarginLow.Multiply(ref pow10); - if (pScaledMarginHigh != &scaledMarginLow) + if (!Unsafe.AreSame(ref scaledMarginHigh, ref scaledMarginLow)) { - BigInteger.Multiply(ref scaledMarginLow, 2, out *pScaledMarginHigh); + BigInteger.Multiply(ref scaledMarginLow, 2, out scaledMarginHigh); } } @@ -206,7 +210,7 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi // take IEEE unbiased rounding into account so we can return // shorter strings for various edge case values like 1.23E+22 - BigInteger.Add(ref scaledValue, ref *pScaledMarginHigh, out BigInteger scaledValueHigh); + BigInteger.Add(ref scaledValue, ref scaledMarginHigh, out BigInteger scaledValueHigh); int cmpHigh = BigInteger.Compare(ref scaledValueHigh, ref scale); estimateTooLow = isEven ? (cmpHigh >= 0) : (cmpHigh > 0); } @@ -229,9 +233,9 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi scaledValue.Multiply10(); scaledMarginLow.Multiply10(); - if (pScaledMarginHigh != &scaledMarginLow) + if (!Unsafe.AreSame(ref scaledMarginHigh, ref scaledMarginLow)) { - BigInteger.Multiply(ref scaledMarginLow, 2, out *pScaledMarginHigh); + BigInteger.Multiply(ref scaledMarginLow, 2, out scaledMarginHigh); } } @@ -271,7 +275,7 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi // This requires the highest block of the denominator to be less than or equal to 429496729 which is the highest number that can be multiplied by 10 without overflowing to a new block. Debug.Assert(scale.GetLength() > 0); - uint hiBlock = scale.GetBlock((uint)(scale.GetLength() - 1)); + uint hiBlock = scale.GetBlock(scale.GetLength() - 1); if ((hiBlock < 8) || (hiBlock > 429496729)) { @@ -280,17 +284,17 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi // This is safe because (2^28 - 1) = 268435455 which is less than 429496729. // This means that all values with a highest bit at index 27 are within range. Debug.Assert(hiBlock != 0); - uint hiBlockLog2 = (uint)BitOperations.Log2(hiBlock); + int hiBlockLog2 = BitOperations.Log2(hiBlock); Debug.Assert((hiBlockLog2 < 3) || (hiBlockLog2 > 27)); - uint shift = (32 + 27 - hiBlockLog2) % 32; + int shift = (32 + 27 - hiBlockLog2) % 32; scale.ShiftLeft(shift); scaledValue.ShiftLeft(shift); scaledMarginLow.ShiftLeft(shift); - if (pScaledMarginHigh != &scaledMarginLow) + if (!Unsafe.AreSame(ref scaledMarginHigh, ref scaledMarginLow)) { - BigInteger.Multiply(ref scaledMarginLow, 2, out *pScaledMarginHigh); + BigInteger.Multiply(ref scaledMarginLow, 2, out scaledMarginHigh); } } @@ -314,7 +318,7 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi Debug.Assert(outputDigit < 10); // update the high end of the value - BigInteger.Add(ref scaledValue, ref *pScaledMarginHigh, out BigInteger scaledValueHigh); + BigInteger.Add(ref scaledValue, ref scaledMarginHigh, out BigInteger scaledValueHigh); // stop looping if we are far enough away from our neighboring values or if we have reached the cutoff digit int cmpLow = BigInteger.Compare(ref scaledValue, ref scaledMarginLow); @@ -344,9 +348,9 @@ private static unsafe uint Dragon4(ulong mantissa, int exponent, uint mantissaHi scaledValue.Multiply10(); scaledMarginLow.Multiply10(); - if (pScaledMarginHigh != &scaledMarginLow) + if (!Unsafe.AreSame(ref scaledMarginHigh, ref scaledMarginLow)) { - BigInteger.Multiply(ref scaledMarginLow, 2, out *pScaledMarginHigh); + BigInteger.Multiply(ref scaledMarginLow, 2, out scaledMarginHigh); } digitExponent--; diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs index 2062aa526cb386..9b2028fa0f48ff 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -412,19 +412,48 @@ internal static unsafe void DecimalToNumber(scoped ref decimal d, ref NumberBuff number.CheckConsistency(); } + [MethodImpl(MethodImplOptions.AggressiveInlining)] private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int precision, NumberFormatInfo info, out bool isSignificantDigits) { + // We want to fast path the common case of no format and general format + precision. + // These are commonly encountered and the full switch is otherwise large enough to show up in hot path profiles + if (fmt == 0) { isSignificantDigits = true; return precision; } - int maxDigits = precision; + // Bitwise-or with space (' ') converts any uppercase character to + // lowercase and keeps unsupported characters as something unsupported. + fmt |= ' '; - switch (fmt | 0x20) + if (fmt == 'g') { - case 'c': + // The general format uses the precision specifier to indicate the number of significant + // digits to format. This defaults to the shortest roundtrippable string. Additionally, + // given that we can't return zero significant digits, we treat 0 as returning the shortest + // roundtrippable string as well. + + isSignificantDigits = true; + + if (precision == 0) + { + precision = -1; + return 0; + } + return precision; + } + + return Slow(fmt, ref precision, info, out isSignificantDigits); + + static int Slow(char fmt, ref int precision, NumberFormatInfo info, out bool isSignificantDigits) + { + int maxDigits = precision; + + switch (fmt) + { + case 'c': { // The currency format uses the precision specifier to indicate the number of // decimal digits to format. This defaults to NumberFormatInfo.CurrencyDecimalDigits. @@ -438,7 +467,7 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci break; } - case 'e': + case 'e': { // The exponential format uses the precision specifier to indicate the number of // decimal digits to format. This defaults to 6. However, the exponential format @@ -456,8 +485,8 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci break; } - case 'f': - case 'n': + case 'f': + case 'n': { // The fixed-point and number formats use the precision specifier to indicate the number // of decimal digits to format. This defaults to NumberFormatInfo.NumberDecimalDigits. @@ -471,23 +500,7 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci break; } - case 'g': - { - // The general format uses the precision specifier to indicate the number of significant - // digits to format. This defaults to the shortest roundtrippable string. Additionally, - // given that we can't return zero significant digits, we treat 0 as returning the shortest - // roundtrippable string as well. - - if (precision == 0) - { - precision = -1; - } - isSignificantDigits = true; - - break; - } - - case 'p': + case 'p': { // The percent format uses the precision specifier to indicate the number of // decimal digits to format. This defaults to NumberFormatInfo.PercentDecimalDigits. @@ -505,7 +518,7 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci break; } - case 'r': + case 'r': { // The roundtrip format ignores the precision specifier and always returns the shortest // roundtrippable string. @@ -516,14 +529,15 @@ private static int GetFloatingPointMaxDigitsAndPrecision(char fmt, ref int preci break; } - default: + default: { ThrowHelper.ThrowFormatException_BadFormatSpecifier(); goto case 'r'; // unreachable } - } + } - return maxDigits; + return maxDigits; + } } public static string FormatFloat(TNumber value, string? format, NumberFormatInfo info) diff --git a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs index 303ae2f43c9222..92535633232725 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.NumberToFloatingPointBits.cs @@ -722,8 +722,8 @@ private static ulong AssembleFloatingPointBits(ulong initialMantissa, in // 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 = TFloat.NormalMantissaBits - (int)(initialMantissaBits); + int initialMantissaBits = BigInteger.CountSignificantBits(initialMantissa); + int normalMantissaShift = TFloat.NormalMantissaBits - initialMantissaBits; int normalExponent = initialExponent - normalMantissaShift; ulong mantissa = initialMantissa; @@ -835,7 +835,7 @@ private static ulong AssembleFloatingPointBits(ulong initialMantissa, in return shiftedExponent | mantissa; } - private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger value, uint integerBitsOfPrecision, bool hasNonZeroFractionalPart) + private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger value, int integerBitsOfPrecision, bool hasNonZeroFractionalPart) where TFloat : unmanaged, IBinaryFloatParseAndFormatInfo { int baseExponent = TFloat.DenormalMantissaBits; @@ -846,9 +846,9 @@ private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger return AssembleFloatingPointBits(value.ToUInt64(), baseExponent, !hasNonZeroFractionalPart); } - (uint topBlockIndex, uint topBlockBits) = Math.DivRem(integerBitsOfPrecision, 32); - uint middleBlockIndex = topBlockIndex - 1; - uint bottomBlockIndex = middleBlockIndex - 1; + (int topBlockIndex, int topBlockBits) = Math.DivRem(integerBitsOfPrecision, 32); + int middleBlockIndex = topBlockIndex - 1; + int bottomBlockIndex = middleBlockIndex - 1; ulong mantissa; int exponent = baseExponent + ((int)(bottomBlockIndex) * 32); @@ -881,7 +881,7 @@ private static ulong ConvertBigIntegerToFloatingPointBits(ref BigInteger hasZeroTail &= (bottomBlock & unusedBottomBlockBitsMask) == 0; } - for (uint i = 0; i != bottomBlockIndex; i++) + for (int i = 0; i < bottomBlockIndex; i++) { hasZeroTail &= (value.GetBlock(i) == 0); } @@ -1058,7 +1058,7 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer numb // 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)(TFloat.NormalMantissaBits + 1); + int requiredBitsOfPrecision = TFloat.NormalMantissaBits + 1; uint totalDigits = (uint)(number.DigitsCount); uint integerDigitsMissing = positiveExponent - integerDigitsPresent; @@ -1086,7 +1086,7 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer numb // 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); + int integerBitsOfPrecision = BigInteger.CountSignificantBits(ref integerValue); if ((integerBitsOfPrecision >= requiredBitsOfPrecision) || (fractionalDigitsPresent == 0)) { @@ -1139,10 +1139,10 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer numb // 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); + int fractionalNumeratorBits = BigInteger.CountSignificantBits(ref fractionalNumerator); + int fractionalDenominatorBits = BigInteger.CountSignificantBits(ref fractionalDenominator); - uint fractionalShift = 0; + int fractionalShift = 0; if (fractionalDenominatorBits > fractionalNumeratorBits) { @@ -1154,8 +1154,8 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer numb fractionalNumerator.ShiftLeft(fractionalShift); } - uint requiredFractionalBitsOfPrecision = requiredBitsOfPrecision - integerBitsOfPrecision; - uint remainingBitsOfPrecisionRequired = requiredFractionalBitsOfPrecision; + int requiredFractionalBitsOfPrecision = requiredBitsOfPrecision - integerBitsOfPrecision; + int remainingBitsOfPrecisionRequired = requiredFractionalBitsOfPrecision; if (integerBitsOfPrecision > 0) { @@ -1188,7 +1188,7 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer numb // 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; + int fractionalExponent = fractionalShift; if (BigInteger.Compare(ref fractionalNumerator, ref fractionalDenominator) < 0) { @@ -1203,7 +1203,7 @@ private static ulong NumberToFloatingPointBitsSlow(ref NumberBuffer numb // We may have produced more bits of precision than were required. Check, // and remove any "extra" bits: - uint fractionalMantissaBits = BigInteger.CountSignificantBits(fractionalMantissa); + int fractionalMantissaBits = BigInteger.CountSignificantBits(fractionalMantissa); if (fractionalMantissaBits > requiredFractionalBitsOfPrecision) {