diff --git a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs index 22eff2b8b16834..cb9f5369c0315b 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Buffers/Text/FormattingHelpers.CountDigits.cs @@ -1,9 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.Buffers.Binary; using System.Diagnostics; using System.Numerics; using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; namespace System.Buffers.Text { @@ -109,36 +111,52 @@ public static int CountDigits(ulong value) [MethodImpl(MethodImplOptions.AggressiveInlining)] public static int CountDigits(uint value) { - int digits = 1; - if (value >= 100000) - { - value /= 100000; - digits += 5; - } - - if (value < 10) - { - // no-op - } - else if (value < 100) - { - digits++; - } - else if (value < 1000) - { - digits += 2; - } - else if (value < 10000) - { - digits += 3; - } - else - { - Debug.Assert(value < 100000); - digits += 4; - } - - return digits; + // Algorithm based on https://lemire.me/blog/2021/06/03/computing-the-number-of-digits-of-an-integer-even-faster. + // TODO https://github.com/dotnet/runtime/issues/60948: Use ReadOnlySpan instead of ReadOnlySpan. + ReadOnlySpan table = new byte[] + { + 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, // 4294967296 + 0xF6, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, // 8589934582 + 0xF6, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, // 8589934582 + 0xF6, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0x00, // 8589934582 + 0x9C, 0xFF, 0xFF, 0xFF, 0x02, 0x00, 0x00, 0x00, // 12884901788 + 0x9C, 0xFF, 0xFF, 0xFF, 0x02, 0x00, 0x00, 0x00, // 12884901788 + 0x9C, 0xFF, 0xFF, 0xFF, 0x02, 0x00, 0x00, 0x00, // 12884901788 + 0x18, 0xFC, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, // 17179868184 + 0x18, 0xFC, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, // 17179868184 + 0x18, 0xFC, 0xFF, 0xFF, 0x03, 0x00, 0x00, 0x00, // 17179868184 + 0xF0, 0xD8, 0xFF, 0xFF, 0x04, 0x00, 0x00, 0x00, // 21474826480 + 0xF0, 0xD8, 0xFF, 0xFF, 0x04, 0x00, 0x00, 0x00, // 21474826480 + 0xF0, 0xD8, 0xFF, 0xFF, 0x04, 0x00, 0x00, 0x00, // 21474826480 + 0xF0, 0xD8, 0xFF, 0xFF, 0x04, 0x00, 0x00, 0x00, // 21474826480 + 0x60, 0x79, 0xFE, 0xFF, 0x05, 0x00, 0x00, 0x00, // 25769703776 + 0x60, 0x79, 0xFE, 0xFF, 0x05, 0x00, 0x00, 0x00, // 25769703776 + 0x60, 0x79, 0xFE, 0xFF, 0x05, 0x00, 0x00, 0x00, // 25769703776 + 0xC0, 0xBD, 0xF0, 0xFF, 0x06, 0x00, 0x00, 0x00, // 30063771072 + 0xC0, 0xBD, 0xF0, 0xFF, 0x06, 0x00, 0x00, 0x00, // 30063771072 + 0xC0, 0xBD, 0xF0, 0xFF, 0x06, 0x00, 0x00, 0x00, // 30063771072 + 0x80, 0x69, 0x67, 0xFF, 0x07, 0x00, 0x00, 0x00, // 34349738368 + 0x80, 0x69, 0x67, 0xFF, 0x07, 0x00, 0x00, 0x00, // 34349738368 + 0x80, 0x69, 0x67, 0xFF, 0x07, 0x00, 0x00, 0x00, // 34349738368 + 0x80, 0x69, 0x67, 0xFF, 0x07, 0x00, 0x00, 0x00, // 34349738368 + 0x00, 0x1F, 0x0A, 0xFA, 0x08, 0x00, 0x00, 0x00, // 38554705664 + 0x00, 0x1F, 0x0A, 0xFA, 0x08, 0x00, 0x00, 0x00, // 38554705664 + 0x00, 0x1F, 0x0A, 0xFA, 0x08, 0x00, 0x00, 0x00, // 38554705664 + 0x00, 0x36, 0x65, 0xC4, 0x09, 0x00, 0x00, 0x00, // 41949672960 + 0x00, 0x36, 0x65, 0xC4, 0x09, 0x00, 0x00, 0x00, // 41949672960 + 0x00, 0x36, 0x65, 0xC4, 0x09, 0x00, 0x00, 0x00, // 41949672960 + 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, // 42949672960 + 0x00, 0x00, 0x00, 0x00, 0x0A, 0x00, 0x00, 0x00, // 42949672960 + }; + Debug.Assert(table.Length == (32 * sizeof(long)), "Every result of uint.Log2(value) needs a long entry in the table."); + + long tableValue = Unsafe.ReadUnaligned(ref Unsafe.Add(ref MemoryMarshal.GetReference(table), uint.Log2(value) * sizeof(long))); + if (!BitConverter.IsLittleEndian) + { + tableValue = BinaryPrimitives.ReverseEndianness(tableValue); + } + + return (int)((value + tableValue) >> 32); } [MethodImpl(MethodImplOptions.AggressiveInlining)] 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 9268f341d7dac5..2faf5707a7f7bb 100644 --- a/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs +++ b/src/libraries/System.Private.CoreLib/src/System/Number.Formatting.cs @@ -1593,15 +1593,14 @@ private static unsafe void UInt32ToNumber(uint value, ref NumberBuffer number) internal static unsafe string UInt32ToDecStr(uint value) { - // Intrinsified in mono interpreter - int bufferLength = FormattingHelpers.CountDigits(value); - // For single-digit values that are very common, especially 0 and 1, just return cached strings. - if (bufferLength == 1) + if (value < 10) { return s_singleDigitStringCache[value]; } + int bufferLength = FormattingHelpers.CountDigits(value); + string result = string.FastAllocateString(bufferLength); fixed (char* buffer = result) { @@ -1935,15 +1934,14 @@ private static uint Int64DivMod1E9(ref ulong value) internal static unsafe string UInt64ToDecStr(ulong value) { - // Intrinsified in mono interpreter - int bufferLength = FormattingHelpers.CountDigits(value); - // For single-digit values that are very common, especially 0 and 1, just return cached strings. - if (bufferLength == 1) + if (value < 10) { return s_singleDigitStringCache[value]; } + int bufferLength = FormattingHelpers.CountDigits(value); + string result = string.FastAllocateString(bufferLength); fixed (char* buffer = result) {