如何计算一个数字中数字总数的数量?

159

如何在C#中获取一个数字的总位数?例如,数字887979789有9位。


8
如果使用.Length不起作用,尝试将其转换为字符串。 - Breezer
假设 x = 887979789; x.ToString().Count(); 将会给你这个结果。 - nPcomp
4
我对以下最佳答案进行了一些性能测试,以找出最快的一个。 - sɐunıɔןɐqɐp
18个回答

230

如果不将其转换为字符串,您可以尝试:

Math.Floor(Math.Log10(n) + 1);

15
很抱歉,ceil(log10(10)) = ceil(1) = 1,而不是此问题所应该的2! - ysap
6
谢谢,这是一个不错的方法。尽管它并不比下面的代码更快: int count = 0; do { count++; } while ((i /= 10) >= 1); :( - Puterdo Borato
3
如果您的数字范围包括负数,则需要使用 Math.Floor(Math.Log10(Math.Abs(n)) + 1);。 - mrcrowl
3
我的性能测试实际上表明,当数字小于5位时,你的方法更快。超过这个数字,Steve的Math.floor就更快了。 - stack247
6
这在零时是不起作用的。Math.Floor(Math.Log10(0) + 1) = -2147483648 (负无穷加1)。 请参见https://learn.microsoft.com/en-us/dotnet/api/system.math.log10?view=netframework-4.8文档。 - Idan P
显示剩余7条评论

107

尝试这样做:

myint.ToString().Length

那是否有效呢?


33
值得指出的是,如果你处理负数时使用这种方法可能会遇到问题。(当然,小数也会有问题,但是示例使用了“int”,所以我认为这不是问题。) - Cody Gray
1
我到处都看到这样的代码。请查看我的答案中的性能测试。 - sɐunıɔןɐqɐp
3
不是1980年代了,你的计算机有足够的RAM来在一个操作期间将一个10个字符的字符串保存在内存中。 - MrLore
2
@MrLore 在简单的应用程序中这可能是正确的,但在游戏开发领域,它是完全不同的野兽。 - Krythic

99

有4个好的可能解决方案,但其中1个更快!

以下任何一个扩展方法都可以完成工作。它们都将减号视为数字,并且对于所有可能的输入值都能正确工作。它们也适用于.NET Framework和.NET Core、.NET6。然而,根据您选择的平台/框架,存在相关的性能差异(在下面讨论)。

Int32版本:

public static class Int32Extensions
{
    // IF-CHAIN:
    public static int Digits_IfChain(this int n)
    {
        if (n >= 0)
        {
            if (n < 10) return 1;
            if (n < 100) return 2;
            if (n < 1000) return 3;
            if (n < 10000) return 4;
            if (n < 100000) return 5;
            if (n < 1000000) return 6;
            if (n < 10000000) return 7;
            if (n < 100000000) return 8;
            if (n < 1000000000) return 9;
            return 10;
        }
        else
        {
            if (n > -10) return 2;
            if (n > -100) return 3;
            if (n > -1000) return 4;
            if (n > -10000) return 5;
            if (n > -100000) return 6;
            if (n > -1000000) return 7;
            if (n > -10000000) return 8;
            if (n > -100000000) return 9;
            if (n > -1000000000) return 10;
            return 11;
        }
    }

    // USING LOG10:
    public static int Digits_Log10(this int n) =>
        n == 0 ? 1 : (n > 0 ? 1 : 2) + (int)Math.Log10(Math.Abs((double)n));

    // WHILE LOOP:
    public static int Digits_While(this int n)
    {
        int digits = n < 0 ? 2 : 1;
        while ((n /= 10) != 0) ++digits;
        return digits;
    }

    // STRING CONVERSION:
    public static int Digits_String(this int n) =>
        n.ToString().Length;
}

Int64版本:

public static class Int64Extensions
{
    // IF-CHAIN:
    public static int Digits_IfChain(this long n)
    {
        if (n >= 0)
        {
            if (n < 10L) return 1;
            if (n < 100L) return 2;
            if (n < 1000L) return 3;
            if (n < 10000L) return 4;
            if (n < 100000L) return 5;
            if (n < 1000000L) return 6;
            if (n < 10000000L) return 7;
            if (n < 100000000L) return 8;
            if (n < 1000000000L) return 9;
            if (n < 10000000000L) return 10;
            if (n < 100000000000L) return 11;
            if (n < 1000000000000L) return 12;
            if (n < 10000000000000L) return 13;
            if (n < 100000000000000L) return 14;
            if (n < 1000000000000000L) return 15;
            if (n < 10000000000000000L) return 16;
            if (n < 100000000000000000L) return 17;
            if (n < 1000000000000000000L) return 18;
            return 19;
        }
        else
        {
            if (n > -10L) return 2;
            if (n > -100L) return 3;
            if (n > -1000L) return 4;
            if (n > -10000L) return 5;
            if (n > -100000L) return 6;
            if (n > -1000000L) return 7;
            if (n > -10000000L) return 8;
            if (n > -100000000L) return 9;
            if (n > -1000000000L) return 10;
            if (n > -10000000000L) return 11;
            if (n > -100000000000L) return 12;
            if (n > -1000000000000L) return 13;
            if (n > -10000000000000L) return 14;
            if (n > -100000000000000L) return 15;
            if (n > -1000000000000000L) return 16;
            if (n > -10000000000000000L) return 17;
            if (n > -100000000000000000L) return 18;
            if (n > -1000000000000000000L) return 19;
            return 20;
        }
    }

    // USING LOG10:
    public static int Digits_Log10(this long n) =>
        n == 0L ? 1 : (n > 0L ? 1 : 2) + (int)Math.Log10(Math.Abs((double)n));

    // WHILE LOOP:
    public static int Digits_While(this long n)
    {
        int digits = n < 0 ? 2 : 1;
        while ((n /= 10L) != 0L) ++digits;
        return digits;
    }

    // STRING CONVERSION:
    public static int Digits_String(this long n) =>
        n.ToString().Length;
}

#讨论#

这个答案包括对Int32Int64类型进行的测试,使用了一个包含100,000,000个随机抽样的int / long数字的数组。在执行测试之前,随机数据集被预处理成一个数组。

还对4种不同方法进行了一致性测试,包括MinValue、负边界情况、-101、正边界情况、MaxValue以及整个随机数据集。上述提供的方法中没有一致性测试失败,除了LOG10方法(稍后会讨论)。

这些测试是在64位Intel处理器的机器上,使用Windows 10VS2017 v.15.9.17,在.NET Framework 4.7.2.NET Core 2.2上进行的;针对x86x64平台进行了测试。以下4种情况对性能结果有相同的影响:

.NET Framework (x86)

  • 平台 = x86

  • 平台 = AnyCPU,在项目设置中选中了Prefer 32-bit

.NET Framework (x64)

  • 平台 = x64

  • 平台 = AnyCPU,在项目设置中取消选中了Prefer 32-bit

.NET Core (x86)

  • "C:\Program Files (x86)\dotnet\dotnet.exe" bin\Release\netcoreapp2.2\ConsoleApp.dll

  • "C:\Program Files (x86)\dotnet\dotnet.exe" bin\x86\Release\netcoreapp2.2\ConsoleApp.dll

.NET Core (x64)

  • "C:\Program Files\dotnet\dotnet.exe" bin\Release\netcoreapp2.2\ConsoleApp.dll

  • "C:\Program Files\dotnet\dotnet.exe" bin\x64\Release\netcoreapp2.2\ConsoleApp.dll

#Results#

以下性能测试产生了一个在整数可能的广泛范围内值之间均匀分布的结果。这意味着测试具有大量位数的值的机会更高。在实际情况中,大多数值可能较小,因此IF-CHAIN的性能表现会更好。此外,处理器将根据您的数据集缓存和优化IF-CHAIN决策。

正如@AlanSingfield在评论部分指出的那样,LOG10方法必须通过在Math.Abs()内进行double转换来修复当输入值为int.MinValuelong.MinValue的情况。

关于在编辑这个问题之前我实施的早期性能测试(已经修改了无数次),有一个特定的情况被@GyörgyKőszeg指出,即IF-CHAIN方法比LOG10方法执行得更慢。
尽管在@AlanSingfield指出的问题修复后,差异的大小大大降低,但这种情况仍然存在。这个修复(添加一个double类型的转换)在输入值恰好为-999999999999999999时会导致计算错误:LOG10方法返回20而不是19。LOG10方法还必须对输入值为零的情况进行if保护。
LOG10方法对于所有值都很棘手,这意味着你应该避免使用它。如果有人找到一种使其在下面的一致性测试中正确工作的方法,请发表评论!
WHILE方法也有一个最近重构的版本,速度更快,但对于Platform = x86仍然很慢(直到现在我还找不到原因)。

STRING方法一直很慢:它贪婪地分配过多的内存却毫无意义。有趣的是,在.NET Core中,字符串分配似乎比.NET Framework快得多。好好知道。

在99.99%的情况下,IF-CHAIN方法应该优于所有其他方法;而且,在我个人的观点中,它是你最好的选择(考虑到使LOG10方法正确工作所需的所有调整以及其他两种方法的性能不佳)。

最后,结果如下:

enter image description here

由于这些结果取决于硬件,如果您确实需要在特定情况下百分之百确定,我建议您在自己的计算机上运行以下性能测试。
#测试代码#
以下是性能测试和一致性测试的代码。相同的代码适用于.NET Framework和.NET Core。
using System;
using System.Diagnostics;

namespace NumberOfDigits
{
    // Performance Tests:
    class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("\r\n.NET Core");

            RunTests_Int32();
            RunTests_Int64();
        }

        // Int32 Performance Tests:
        private static void RunTests_Int32()
        {
            Console.WriteLine("\r\nInt32");

            const int size = 100000000;
            int[] samples = new int[size];
            Random random = new Random((int)DateTime.Now.Ticks);
            for (int i = 0; i < size; ++i)
                samples[i] = random.Next(int.MinValue, int.MaxValue);

            Stopwatch sw1 = new Stopwatch();
            sw1.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_IfChain();
            sw1.Stop();
            Console.WriteLine($"IfChain: {sw1.ElapsedMilliseconds} ms");

            Stopwatch sw2 = new Stopwatch();
            sw2.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_Log10();
            sw2.Stop();
            Console.WriteLine($"Log10: {sw2.ElapsedMilliseconds} ms");

            Stopwatch sw3 = new Stopwatch();
            sw3.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_While();
            sw3.Stop();
            Console.WriteLine($"While: {sw3.ElapsedMilliseconds} ms");

            Stopwatch sw4 = new Stopwatch();
            sw4.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_String();
            sw4.Stop();
            Console.WriteLine($"String: {sw4.ElapsedMilliseconds} ms");


            // Start of consistency tests:
            Console.WriteLine("Running consistency tests...");
            bool isConsistent = true;

            // Consistency test on random set:
            for (int i = 0; i < samples.Length; ++i)
            {
                int s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test of special values:
            samples = new int[]
            {
                0,
                int.MinValue, -1000000000, -999999999, -100000000, -99999999, -10000000, -9999999, -1000000, -999999, -100000, -99999, -10000, -9999, -1000, -999, -100, -99, -10, -9, - 1,
                int.MaxValue, 1000000000, 999999999, 100000000, 99999999, 10000000, 9999999, 1000000, 999999, 100000, 99999, 10000, 9999, 1000, 999, 100, 99, 10, 9,  1,
            };
            for (int i = 0; i < samples.Length; ++i)
            {
                int s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test result:
            if (isConsistent)
                Console.WriteLine("Consistency tests are OK");
        }

        // Int64 Performance Tests:
        private static void RunTests_Int64()
        {
            Console.WriteLine("\r\nInt64");

            const int size = 100000000;
            long[] samples = new long[size];
            Random random = new Random((int)DateTime.Now.Ticks);
            for (int i = 0; i < size; ++i)
                samples[i] = Math.Sign(random.Next(-1, 1)) * (long)(random.NextDouble() * long.MaxValue);

            Stopwatch sw1 = new Stopwatch();
            sw1.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_IfChain();
            sw1.Stop();
            Console.WriteLine($"IfChain: {sw1.ElapsedMilliseconds} ms");

            Stopwatch sw2 = new Stopwatch();
            sw2.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_Log10();
            sw2.Stop();
            Console.WriteLine($"Log10: {sw2.ElapsedMilliseconds} ms");

            Stopwatch sw3 = new Stopwatch();
            sw3.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_While();
            sw3.Stop();
            Console.WriteLine($"While: {sw3.ElapsedMilliseconds} ms");

            Stopwatch sw4 = new Stopwatch();
            sw4.Start();
            for (int i = 0; i < size; ++i) samples[i].Digits_String();
            sw4.Stop();
            Console.WriteLine($"String: {sw4.ElapsedMilliseconds} ms");

            // Start of consistency tests:
            Console.WriteLine("Running consistency tests...");
            bool isConsistent = true;

            // Consistency test on random set:
            for (int i = 0; i < samples.Length; ++i)
            {
                long s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test of special values:
            samples = new long[] 
            {
                0,
                long.MinValue, -1000000000000000000, -999999999999999999, -100000000000000000, -99999999999999999, -10000000000000000, -9999999999999999, -1000000000000000, -999999999999999, -100000000000000, -99999999999999, -10000000000000, -9999999999999, -1000000000000, -999999999999, -100000000000, -99999999999, -10000000000, -9999999999, -1000000000, -999999999, -100000000, -99999999, -10000000, -9999999, -1000000, -999999, -100000, -99999, -10000, -9999, -1000, -999, -100, -99, -10, -9, - 1,
                long.MaxValue, 1000000000000000000, 999999999999999999, 100000000000000000, 99999999999999999, 10000000000000000, 9999999999999999, 1000000000000000, 999999999999999, 100000000000000, 99999999999999, 10000000000000, 9999999999999, 1000000000000, 999999999999, 100000000000, 99999999999, 10000000000, 9999999999, 1000000000, 999999999, 100000000, 99999999, 10000000, 9999999, 1000000, 999999, 100000, 99999, 10000, 9999, 1000, 999, 100, 99, 10, 9,  1,
            };
            for (int i = 0; i < samples.Length; ++i)
            {
                long s = samples[i];
                int a = s.Digits_IfChain();
                int b = s.Digits_Log10();
                int c = s.Digits_While();
                int d = s.Digits_String();
                if (a != b || c != d || a != c)
                {
                    Console.WriteLine($"Digits({s}): IfChain={a} Log10={b} While={c} String={d}");
                    isConsistent = false;
                    break;
                }
            }

            // Consistency test result:
            if (isConsistent)
                Console.WriteLine("Consistency tests are OK");
        }
    }
}

6
我喜欢这个解决方案,它比数学技巧更易读,速度也很快,赞。 - MrLore
6
为什么这个没有被标记为解决方案?性能很重要,而且这似乎是最详尽的答案。 - Martien de Jong
有趣的是,我在这里得到了不同的结果(https://dotnetfiddle.net/fqEbWc)。对于随机值,Log10和暴力搜索几乎是相同的,但对于“long.MaxValue”,Log10要好得多。或者这只是在.NET Core中的情况? - György Kőszeg
@GyörgyKőszeg:另外,低级性能测量非常棘手。我通常喜欢尽可能保持代码简单(我更喜欢简单的for循环而不是enumerations,我预处理随机数据集,并避免使用泛型、Tasks、Function<>Action<>或任何黑盒测量框架)。总之,保持简单。我还会关闭所有不必要的应用程序(如Skype、Windows Defender、禁用反病毒软件、Chrome、Microsoft Office缓存等)。 - sɐunıɔןɐqɐp
如果传递 int.MinValue,则 Log10 会崩溃并显示“Negating the minimum value of a twos complement number is invalid.”。Math.Abs(int.MinValue) 无法将 2147483648 作为 int 返回。 - Alan Singfield
显示剩余6条评论

13

这不是直接的C#代码,但公式为:n = floor(log10(x)+1)


4
log10(0) 的值为负无穷,意思是以10为底数的对数函数无法计算0的对数。 - Alex Klaus
2
@Klaus - log10(0)实际上是未定义的。但是,您是正确的,它是需要进行测试并单独处理的特殊情况。对于任何非正整数数字也是如此。请参见Steve答案中的注释。 - ysap
@ysap:Log10 很难得到正确的工作。您有没有想法如何为所有可能的输入值范围正确实现它? - sɐunıɔןɐqɐp
@sɐunıɔןɐqɐp - log10 在大多数情况下是一个库函数。你为什么想要自己实现它,以及你会遇到哪些问题?log10(x) = log2(x) / log2(10),或者一般地说,logA(x) = logB(x) / logB(A) - ysap
我不是想再次实现Log10,我的意思是Log10(0)等于负无穷。除非您在将值传递给Log10之前使用Math.Abs(),否则Log10不能用于计算负数的数字位数。但是,如果我们在将数字转换为double之前将其强制转换为double,则它适用于几乎所有数字,但对于Int64的情况下的-999999999999999999除外。您知道任何使用log10计算数字位数并接受任何int32或int64值作为输入并仅输出有效值的公式吗? - sɐunıɔןɐqɐp
@sɐunıɔןɐqɐp - 是的,log10()有其局限性,需要知道自己在做什么。如上所述。 - ysap

9
Steve的回答是正确的,但该方法不能用于小于1的整数。

这里有一个更新版本,可以处理负数:

int digits = n == 0 ? 1 : Math.Floor(Math.Log10(Math.Abs(n)) + 1)

你缺少一个转换为整数的强制类型转换:digits = n == 0 ? 1 : (int)Math.Floor(Math.Log10(Math.Abs(n)) + 1); - sɐunıɔןɐqɐp
我没有使用if语句完成了它: digits = (int)Math.Floor(Math.Abs(Math.Log10(Math.Abs(n))) + 1) - KOLRH
n = int.MinValue 时,会抛出一个异常。 - sɐunıɔןɐqɐp

9

这里已经有可用于无符号整数的答案,但我尚未找到获取小数和双精度浮点数位数的好方法。

public static int Length(double number)
{
    number = Math.Abs(number);
    int length = 1;
    while ((number /= 10) >= 1)
        length++;
    return length;
}
//number of digits in 0 = 1,
//number of digits in 22.1 = 2,
//number of digits in -23 = 2

如果精度很重要,您可以将输入类型从double更改为decimal,但是decimal也有限制。


5

使用递归(有时在面试中会问到)

public int CountDigits(int number)
{
    // In case of negative numbers
    number = Math.Abs(number);

    if (number >= 10)
        return CountDigits(number / 10) + 1;
    return 1;
 }

2
number = int.MinValue时,这将抛出异常。 - sɐunıɔןɐqɐp

4
static void Main(string[] args)
{
    long blah = 20948230498204;
    Console.WriteLine(blah.ToString().Length);
}

3
注意负数:-1 = 2。 - MrLore

2
这里是使用二分查找实现的代码。在int32上看起来是目前最快的。
留给读者的练习是Int64实现!
我尝试使用Array.BinarySearch而不是硬编码树,但速度只有一半。
编辑: 一个查找表比二分查找快得多,但代价是使用更多的内存。实际上,我可能会在生产中使用二分查找,查找表是为了获得速度优势而增加了很多复杂性,而这种速度优势很可能被软件的其他部分所掩盖。
Lookup-Table: 439 ms
Binary-Search: 1069 ms
If-Chain: 1409 ms
Log10: 1145 ms
While: 1768 ms
String: 5153 ms

查找表版本:

static byte[] _0000llll = new byte[0x10000];
static byte[] _FFFFllll = new byte[0x10001];
static sbyte[] _hhhhXXXXdigits = new sbyte[0x10000];

// Special cases where the high DWORD is not enough information to find out how
// many digits.
static ushort[] _lowordSplits = new ushort[12];
static sbyte[] _lowordSplitDigitsLT = new sbyte[12];
static sbyte[] _lowordSplitDigitsGE = new sbyte[12];

static Int32Extensions()
{
    // Simple lookup tables for number of digits where value is 
    //    0000xxxx (0 .. 65535)
    // or FFFFxxxx (-1 .. -65536)
    precomputePositiveLo16();
    precomputeNegativeLo16();

    // Hiword is a little more complex
    precomputeHiwordDigits();
}

private static void precomputeHiwordDigits()
{
    int b = 0;

    for(int hhhh = 0; hhhh <= 0xFFFF; hhhh++)
    {
        // For hiword hhhh, calculate integer value for loword of 0000 and FFFF.
        int hhhh0000 = (unchecked(hhhh * 0x10000));  // wrap around on negatives
        int hhhhFFFF = hhhh0000 + 0xFFFF;

        // How many decimal digits for each?
        int digits0000 = hhhh0000.Digits_IfChain();
        int digitsFFFF = hhhhFFFF.Digits_IfChain();

        // If same number of decimal digits, we know that when we see that hiword
        // we don't have to look at the loword to know the right answer.
        if(digits0000 == digitsFFFF)
        {
            _hhhhXXXXdigits[hhhh] = (sbyte)digits0000;
        }
        else
        {
            bool negative = hhhh >= 0x8000;

            // Calculate 10, 100, 1000, 10000 etc
            int tenToThePower = (int)Math.Pow(10, (negative ? digits0000 : digitsFFFF) - 1);

            // Calculate the loword of the 10^n value.
            ushort lowordSplit = unchecked((ushort)tenToThePower);
            if(negative)
                lowordSplit = unchecked((ushort)(2 + (ushort)~lowordSplit));

            // Store the split point and digits into these arrays
            _lowordSplits[b] = lowordSplit;
            _lowordSplitDigitsLT[b] = (sbyte)digits0000;
            _lowordSplitDigitsGE[b] = (sbyte)digitsFFFF;

            // Store the minus of the array index into the digits lookup. We look for
            // minus values and use these to trigger using the split points logic.
            _hhhhXXXXdigits[hhhh] = (sbyte)(-b);
            b++;
        }
    }
}

private static void precomputePositiveLo16()
{
    for(int i = 0; i <= 9; i++)
        _0000llll[i] = 1;

    for(int i = 10; i <= 99; i++)
        _0000llll[i] = 2;

    for(int i = 100; i <= 999; i++)
        _0000llll[i] = 3;

    for(int i = 1000; i <= 9999; i++)
        _0000llll[i] = 4;

    for(int i = 10000; i <= 65535; i++)
        _0000llll[i] = 5;
}

private static void precomputeNegativeLo16()
{
    for(int i = 0; i <= 9; i++)
        _FFFFllll[65536 - i] = 1;

    for(int i = 10; i <= 99; i++)
        _FFFFllll[65536 - i] = 2;

    for(int i = 100; i <= 999; i++)
        _FFFFllll[65536 - i] = 3;

    for(int i = 1000; i <= 9999; i++)
        _FFFFllll[65536 - i] = 4;

    for(int i = 10000; i <= 65535; i++)
        _FFFFllll[65536 - i] = 5;
}



public static int Digits_LookupTable(this int n)
{
    // Split input into low word and high word.
    ushort l = unchecked((ushort)n);
    ushort h = unchecked((ushort)(n >> 16));

    // If the hiword is 0000 or FFFF we have precomputed tables for these.
    if(h == 0x0000)
    {
        return _0000llll[l];
    }
    else if(h == 0xFFFF)
    {
        return _FFFFllll[l];
    }

    // In most cases the hiword will tell us the number of decimal digits.
    sbyte digits = _hhhhXXXXdigits[h];

    // We put a positive number in this lookup table when
    // hhhh0000 .. hhhhFFFF all have the same number of decimal digits.
    if(digits > 0)
        return digits;

    // Where the answer is different for hhhh0000 to hhhhFFFF, we need to
    // look up in a separate array to tell us at what loword the change occurs.
    var splitIndex = (sbyte)(-digits);

    ushort lowordSplit = _lowordSplits[splitIndex];

    // Pick the correct answer from the relevant array, depending whether
    // our loword is lower than the split point or greater/equal. Note that for
    // negative numbers, the loword is LOWER for MORE decimal digits.
    if(l < lowordSplit)
        return _lowordSplitDigitsLT[splitIndex];
    else
        return _lowordSplitDigitsGE[splitIndex];
}

二分查找版本

        public static int Digits_BinarySearch(this int n)
        {
            if(n >= 0)
            {
                if(n <= 9999) // 0 .. 9999
                {
                    if(n <= 99) // 0 .. 99
                    {
                        return (n <= 9) ? 1 : 2;
                    }
                    else // 100 .. 9999
                    {
                        return (n <= 999) ? 3 : 4;
                    }
                }
                else // 10000 .. int.MaxValue
                {
                    if(n <= 9_999_999) // 10000 .. 9,999,999
                    {
                        if(n <= 99_999)
                            return 5;
                        else if(n <= 999_999)
                            return 6;
                        else
                            return 7;
                    }
                    else // 10,000,000 .. int.MaxValue
                    {
                        if(n <= 99_999_999)
                            return 8;
                        else if(n <= 999_999_999)
                            return 9;
                        else
                            return 10;
                    }
                }
            }
            else
            {
                if(n >= -9999) // -9999 .. -1
                {
                    if(n >= -99) // -99 .. -1
                    {
                        return (n >= -9) ? 1 : 2;
                    }
                    else // -9999 .. -100
                    {
                        return (n >= -999) ? 3 : 4;
                    }
                }
                else // int.MinValue .. -10000
                {
                    if(n >= -9_999_999) // -9,999,999 .. -10000
                    {
                        if(n >= -99_999)
                            return 5;
                        else if(n >= -999_999)
                            return 6;
                        else
                            return 7;
                    }
                    else // int.MinValue .. -10,000,000 
                    {
                        if(n >= -99_999_999)
                            return 8;
                        else if(n >= -999_999_999)
                            return 9;
                        else
                            return 10;
                    }
                }
            }
        }

        Stopwatch sw0 = new Stopwatch();
        sw0.Start();
        for(int i = 0; i < size; ++i) samples[i].Digits_BinarySearch();
        sw0.Stop();
        Console.WriteLine($"Binary-Search: {sw0.ElapsedMilliseconds} ms");

非常有趣的方法。对于均匀分布的整数值,它确实比“Log10”、“string.Length”和“While”方法更快。在实际情况下,整数值的分布必须始终考虑到 if-chain-like 解决方案中。+1 - sɐunıɔןɐqɐp
LookUpTable方法在内存访问不是瓶颈的情况下似乎非常快。我坚信,在频繁访问内存的情况下,LookUpTable比像你建议的BinSearch这样的if-chain-like方法要慢。顺便问一下,你有LookUpTable的Int64实现吗?或者你认为它太复杂了,无法实现?我想稍后在完整集上运行性能测试。 - sɐunıɔןɐqɐp
嘿,我没有做到64位的那个。原则上会稍有不同,因为你需要4个级别而不仅仅是hiword和loword。绝对同意在现实世界中,你的CPU缓存将有许多其他竞争空间的需求,并且在减少查找大小方面有很大的改进空间(>>1然后只考虑偶数)。二分查找可以通过偏向于9、10、8位数字而不是1、2、3、4来改进-考虑到你的随机数据集的分布。 - Alan Singfield
请注意,任何基于分支的实现都很难测量,因为CPU分支预测器在任何数据集上的行为都会影响到此。您可以通过强制数据交替采取分支来构造最坏情况数据集。 - Jeremy Lakeman

1
int i = 855865264;
int NumLen = i.ToString().Length;

3
对负整数和类似23.00这样的数字无法正常运行。建议使用string.TrimStart('-')来解决。 - nawfal

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接