Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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
{
Expand Down Expand Up @@ -109,36 +111,52 @@ public static int CountDigits(ulong value)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static int CountDigits(uint value)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update CountDigits(ulong value) above to take advantage of this?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update CountDigits(ulong value) above to take advantage of this?

FYI: There is a 64-bit version in the comments of Lemire's post

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

FYI: There is a 64-bit version in the comments of Lemire's post

Which specifically? The variants I'd seen were only good up to a smaller number of bits, like 57.

Copy link
Copy Markdown
Member

@EgorBo EgorBo Oct 3, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume Jan meant

if (longValue <= uint.MaxValue)
    return CountDigits((uint)longValue);

?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More like:

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static int CountDigits(ulong value)
        {
            int digits;
            uint part;
            if (value >= 1_000_000_000)
            {
                if (part >= 1_000_000_000_000_000_000)
                {
                    part = (uint)(value / 1_000_000_000_000_000_000);
                    digits = 18;
                }
                else
                {
                    part = (uint)(value / 1_000_000_000);
                    digits = 9;
                }
            }
            else
            {
                part = (uint)value;
                digits = 0;
            }
            
            return digits + CountDigits(part);
        }

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More like

Yeah, that's what I was planning on doing, but I want to experiment.

{
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<long> instead of ReadOnlySpan<byte>.
ReadOnlySpan<byte> 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<long>(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)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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)
{
Expand Down