【问题标题】:How do I determine the number of digits of an integer in C?如何确定C中整数的位数?
【发布时间】:2010-11-07 07:38:58
【问题描述】:

例如,

n = 3432, result 4

n = 45, result 2

n = 33215, result 5

n = -357, result 3

我想我可以把它变成一个字符串,然后得到字符串的长度,但这看起来很复杂而且很hack-y。

【问题讨论】:

  • 如果是负数,获取字符串长度会失败。所以取而代之的是绝对值的长度。 ;-)
  • char buff[100]; int r = sprintf(buff,"%s",n) - (r
  • 你是指十进制数字?根据定义,小数位是实数具有的,而整数则没有。
  • 呃 ... Pax,这是合法的表达方式吗?由于 r 在赋值之前没有值,因此 "(r
  • 必须...记住...到...单元...测试!字符缓冲区[100]; int r = sprintf(buff,"%d",n) - (n

标签: c math


【解决方案1】:

递归方法:-)

int numPlaces (int n) {
    if (n < 0) return numPlaces ((n == INT_MIN) ? INT_MAX: -n);
    if (n < 10) return 1;
    return 1 + numPlaces (n / 10);
}

或迭代:

int numPlaces (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX: -n;
    while (n > 9) {
        n /= 10;
        r++;
    }
    return r;
}

或原始速度:

int numPlaces (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    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;
    /*      2147483647 is 2^31-1 - add more ifs as needed
       and adjust this final return as well. */
    return 10;
}

以上内容已被修改以更好地处理 MININT。在任何不遵循合理的 2n 整数补码规则的奇怪系统上,它们可能需要进一步调整。

原始速度版本实际上优于浮点版本,修改如下:

int numPlaces (int n) {
    if (n == 0) return 1;
    return floor (log10 (abs (n))) + 1;
}

经过一亿次迭代,我得到以下结果:

Raw speed with 0:            0 seconds
Raw speed with 2^31-1:       1 second
Iterative with 2^31-1:       5 seconds
Recursive with 2^31-1:       6 seconds
Floating point with 1:       6 seconds
Floating point with 2^31-1:  7 seconds

这实际上让我有点惊讶 - 我认为英特尔芯片的 FPU 不错,但我猜一般的 FP 操作仍然无法与手动优化的整数代码竞争。

更新以下stormsoul的建议:

通过stormsoul测试乘法迭代解决方案给出了4秒的结果,因此虽然它比除法迭代解决方案快得多,但它仍然与优化的if语句解决方案不匹配。

从 1000 个随机生成的数字池中选择参数将原始速度超时推迟到 2 秒,因此,虽然每次使用相同的参数似乎有一些优势,但它仍然是列出的最快方法。

使用 -O2 编译提高了速度,但没有提高相对位置(我将迭代次数增加了 10 倍以检查这一点)。

任何进一步的分析都必须认真研究 CPU 效率的内部工作原理(不同类型的优化、缓存的使用、分支预测、您实际拥有的 CPU、房间的环境温度等等)会妨碍我的有偿工作:-)。这是一个有趣的转移,但在某些时候,优化的投资回报变得太小了。我认为我们有足够的解决方案来回答这个问题(毕竟,这与速度无关)。

进一步更新:

这将是我对这个答案的最后一次更新,除非出现不依赖于架构的明显错误。受到stormsoul 勇敢的测量努力的启发,我发布了我的测试程序(根据stormsoul 自己的测试程序修改)以及此处答案中显示的所有方法的一些示例图。请记住,这是在特定的机器上,你的里程可能会根据你运行它的位置而有所不同(这就是我发布测试代码的原因)。

随心所欲:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>
#include <time.h>

#define numof(a) (sizeof(a) / sizeof(a[0]))

/* Random numbers and accuracy checks. */

static int rndnum[10000];
static int rt[numof(rndnum)];

/* All digit counting functions here. */

static int count_recur (int n) {
    if (n < 0) return count_recur ((n == INT_MIN) ? INT_MAX : -n);
    if (n < 10) return 1;
    return 1 + count_recur (n / 10);
}

static int count_diviter (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    while (n > 9) {
        n /= 10;
        r++;
    }
    return r;
}

static int count_multiter (int n) {
    unsigned int num = abs(n);
    unsigned int x, i;
    for (x=10, i=1; ; x*=10, i++) {
        if (num < x)
            return i;
        if (x > INT_MAX/10)
            return i+1;
    }
}

static int count_ifs (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    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;
    /*      2147483647 is 2^31-1 - add more ifs as needed
    and adjust this final return as well. */
    return 10;
}

static int count_revifs (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n > 999999999) return 10;
    if (n > 99999999) return 9;
    if (n > 9999999) return 8;
    if (n > 999999) return 7;
    if (n > 99999) return 6;
    if (n > 9999) return 5;
    if (n > 999) return 4;
    if (n > 99) return 3;
    if (n > 9) return 2;
    return 1;
}

static int count_log10 (int n) {
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n == 0) return 1;
    return floor (log10 (n)) + 1;
}

static int count_bchop (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n >= 100000000) {
        r += 8;
        n /= 100000000;
    }
    if (n >= 10000) {
        r += 4;
        n /= 10000;
    }
    if (n >= 100) {
        r += 2;
        n /= 100;
    }
    if (n >= 10)
        r++;

    return r;
}

/* Structure to control calling of functions. */

typedef struct {
    int (*fnptr)(int);
    char *desc;
} tFn;

static tFn fn[] = {
    NULL,                              NULL,
    count_recur,    "            recursive",
    count_diviter,  "     divide-iterative",
    count_multiter, "   multiply-iterative",
    count_ifs,      "        if-statements",
    count_revifs,   "reverse-if-statements",
    count_log10,    "               log-10",
    count_bchop,    "          binary chop",
};
static clock_t clk[numof (fn)];

int main (int c, char *v[]) {
    int i, j, k, r;
    int s = 1;

    /* Test code:
        printf ("%11d %d\n", INT_MIN, count_recur(INT_MIN));
        for (i = -1000000000; i != 0; i /= 10)
            printf ("%11d %d\n", i, count_recur(i));
        printf ("%11d %d\n", 0, count_recur(0));
        for (i = 1; i != 1000000000; i *= 10)
            printf ("%11d %d\n", i, count_recur(i));
        printf ("%11d %d\n", 1000000000, count_recur(1000000000));
        printf ("%11d %d\n", INT_MAX, count_recur(INT_MAX));
    /* */

    /* Randomize and create random pool of numbers. */

    srand (time (NULL));
    for (j = 0; j < numof (rndnum); j++) {
        rndnum[j] = s * rand();
        s = -s;
    }
    rndnum[0] = INT_MAX;
    rndnum[1] = INT_MIN;

    /* For testing. */
    for (k = 0; k < numof (rndnum); k++) {
        rt[k] = (fn[1].fnptr)(rndnum[k]);
    }

    /* Test each of the functions in turn. */

    clk[0] = clock();
    for (i = 1; i < numof (fn); i++) {
        for (j = 0; j < 10000; j++) {
            for (k = 0; k < numof (rndnum); k++) {
                r = (fn[i].fnptr)(rndnum[k]);
                /* Test code:
                    if (r != rt[k]) {
                        printf ("Mismatch error [%s] %d %d %d %d\n",
                            fn[i].desc, k, rndnum[k], rt[k], r);
                        return 1;
                    }
                /* */
            }
        }
        clk[i] = clock();
    }

    /* Print out results. */

    for (i = 1; i < numof (fn); i++) {
        printf ("Time for %s: %10d\n", fn[i].desc, (int)(clk[i] - clk[i-1]));
    }

    return 0;
}

请记住,您需要确保使用正确的命令行来编译它。特别是,您可能需要明确列出数学库以使 log10() 正常工作。我在Debian下使用的命令行是gcc -o testprog testprog.c -lm

而且,就结果而言,这是我所在环境的排行榜

优化级别 0:

Time for reverse-if-statements:       1704
Time for         if-statements:       2296
Time for           binary chop:       2515
Time for    multiply-iterative:       5141
Time for      divide-iterative:       7375
Time for             recursive:      10469
Time for                log-10:      26953

优化级别 3:

Time for         if-statements:       1047
Time for           binary chop:       1156
Time for reverse-if-statements:       1500
Time for    multiply-iterative:       2937
Time for      divide-iterative:       5391
Time for             recursive:       8875
Time for                log-10:      25438

【讨论】:

  • 在我看来,递归版本是最干净、最简单、最好的自记录解决方案。
  • @moogs,您可以使用此处提供的任何解决方案或任何其他解决方案,请在此处回答。速度测试实际上只是一个旁白(失控)。而且无论如何,您仍然可以使用那段时间 - 这只是 我的 时间可能在这里被浪费了,所以请随意使用我的劳动成果 :-)
  • 一个小的性能提示 - 当您知道某些值始终为非负数时,请使用无符号类型。它们的乘法和除法稍快一些。编译器可能会为您猜测某些变量永远不会是负数并自动进行此优化,但同样可能不会。在更复杂的情况下,它永远不会。
  • 对。 IRC 中的某个人进行了一些性能测试,然后他使用了 unsigned 并且他的上帝得到了一些非常好的提升。我敦促您尝试未签名的世界! :)
  • Nice answer =) 我喜欢原始速度版本,但我认为可以通过以二进制方式分支来改进它,以将最坏情况的比较次数减少到四个(忽略否定测试),显然是以牺牲可读性为代价的。在这方面,可以专门针对预期的数据范围调整它的一个版本。
【解决方案2】:
floor (log10 (abs (x))) + 1

http://en.wikipedia.org/wiki/Logarithm

【讨论】:

  • 这将是不必要的慢。不要无缘无故地使用 log10() 等昂贵的函数。快速的整数函数很简单,可以编写它。
  • 天啊..你们还在运行 8088 吗?谁在乎几个额外的时钟周期。 Paz 需要 100,000,000 次迭代才能产生可​​衡量的差异,甚至可以忽略不计! 6秒! Whoop-dee-do .. 继续你的生活。明年是 3 秒。
  • @eduffy:这里是一毫秒,那里是一毫秒……突然,用户在单击按钮后感到明显的延迟。说真的,这些小的低效率加起来。为什么要浪费时钟周期,而您并没有从中获得任何收益?
  • @eduffy:如果这是在嵌入式处理器上运行,可能没有任何浮点支持,更不用说日志功能,而且时钟速度可能只有几十 MHz - 所以完全基于整数的解决方案肯定是首选。
  • 事实证明,虽然简单除法对于小值来说更快,但对数的缩放效果要好得多。如果您使用从 MIN_INT 到 MAX_INT 的每个 int 调用除法算法(并像 Paz 的示例一样重复 100m 次),那么每次调用的平均时间为 13.337 秒。对对数执行相同操作平均需要 8.143 秒,递归需要 11.971 秒,级联 If 语句最终平均需要 0.953 秒。所以,看起来 Daily-WTF 的解决方案要快一个数量级,但从长远来看,这是第二位的。
【解决方案3】:

最短答案:snprintf(0,0,"%+d",n)-1

【讨论】:

  • snprintfn=0 不存储任何内容并允许空缓冲区指针;返回值是要写入的字符数。 + 修饰符用于打印符号(+-),即使该值为非负数;从结果中减去 1 会使符号不被计为数字。
  • 来自我(Debian Linux)系统的man-页面snprintf“关于snprintf()SUSv2的返回值C99 相互矛盾:当 snprintf()size=0 [size 是上面的第二个参数] 调用时,SUSv2 规定了一个小于 1 的未指定返回值,而 C99 在这种情况下允许 str 为 NULL,并且将返回值(一如既往)作为在输出字符串足够大的情况下将写入的字符数。" {SUS = Single UNIX Specification}
  • @SlySven:SUSv2 是古老且无关紧要的。
  • 嗯,Debian 以有些保守而著称,并且不是以最快的速度接受新东西。 8-P 感谢您的回答 - 我已在我正在编码的 FOSS 项目中使用它(并相应地将其归因于此处)...!
  • @SlySven:这不是来自 Debian AFAIK,只是来自 Linux 手册页项目。我认为从来没有一个 Linux libc 有错误的 snprintf 行为,但可能有一些古老的专有 unices,MSVC 的 _snprintf 也有这个错误。
【解决方案4】:

二分查找伪算法得到v中r的个数。

if (v < 0 ) v=-v;

r=1;

if (v >= 100000000)
{
  r+=8;
  v/=100000000;
}

if (v >= 10000) {
    r+=4;
    v/=10000;
}

if (v >= 100) {
    r+=2;
    v/=100;
}

if( v>=10)
{
    r+=1;
}

return r;

【讨论】:

  • 为什么要投反对票,这看起来比除以十的循环更有效率?
  • Downvote 不是来自我,但我怀疑这是因为这比 Wayne Shephard 的变体可读性差(而且可能更慢)。
  • 我明白了,但我认为因为 less 有帮助而投反对票是不对的 - 弹出窗口明确指出“这个答案没有帮助”。在这种情况下,我会赞成另一个,而不要理会这个。这是对 /10 迭代的真正改进。尽管如此,它现在是积极的,所以没有伤害,没有犯规。 (这不是针对您,Brian,因为正如您已经说过的,您没有这样做)。只是让做过的人深思。
  • 我的算法可以很容易地扩展为 longlong 变量,方法是在开头添加另一个 if 语句 if (v >= 10000000000000000LL) { r+=16; v/=10000000000000000LL; } 并且会比所有方法都快。
【解决方案5】:

这是一种非常快速的计算小数位数by Kendall Willets的方法:

int count_digits(uint32_t n) {
#ifndef __has_builtin
#  define __has_builtin(x) 0
#endif
#if __has_builtin(__builtin_clz)
  // This increments the upper 32 bits (log10(T) - 1) when >= T is added.
  #  define K(T) (((sizeof(#T) - 1ull) << 32) - T)
  static const uint64_t table[] = {
      K(0),          K(0),          K(0),           // 8
      K(10),         K(10),         K(10),          // 64
      K(100),        K(100),        K(100),         // 512
      K(1000),       K(1000),       K(1000),        // 4096
      K(10000),      K(10000),      K(10000),       // 32k
      K(100000),     K(100000),     K(100000),      // 256k
      K(1000000),    K(1000000),    K(1000000),     // 2048k
      K(10000000),   K(10000000),   K(10000000),    // 16M
      K(100000000),  K(100000000),  K(100000000),   // 128M
      K(1000000000), K(1000000000), K(1000000000),  // 1024M
      K(1000000000), K(1000000000)                  // 4B
  };
  return (n + table[__builtin_clz(n | 1) ^ 31]) >> 32u;
#else
  int count = 1;
  for (;;) {
    if (n < 10) return count;
    if (n < 100) return count + 1;
    if (n < 1000) return count + 2;
    if (n < 10000) return count + 3;
    n /= 10000u;
    count += 4;
  }
  return count;
#endif
}

快速路径依赖于__builtin_clz,它在 GCC 和 clang 中可用,但由于后备工作相当好,count_digits 是完全可移植的。

这会生成非常高效的代码 (godbolt):

count_digits(unsigned int):
  mov edx, edi
  mov eax, edi
  or edx, 1
  bsr edx, edx
  movsx rdx, edx
  add rax, QWORD PTR count_digits(unsigned int)::table[0+rdx*8]
  shr rax, 32
  ret

【讨论】:

  • #if defined(__has_builtin) &amp;&amp; __has_builtin(__builtin_clz) 不可移植。
  • 对于使用__builtin_clz() 的变体not,您替换int_log2_64() 也是完全错误的!如果你只是复制粘贴原始代码而不是试图破解它,也许会更好。
  • @GregA.Woods 为什么不呢?
  • 为什么不什么
  • 啊,是的,抱歉——你对 Willit 的原始代码进行了如此多的变异,以至于我错过了你并没有试图提供更便携的 int_log2_64(),而是只是替换了所有count_digits() 用于没有内置功能的编译器。
【解决方案6】:

在循环中除以 10,直到结果为零。迭代次数将对应小数位数。

假设您希望在零值中获得 0 位数字:

int countDigits( int value )
{
    int result = 0;
    while( value != 0 ) {
       value /= 10;
       result++;
    }
    return result;
}

【讨论】:

  • floor(log10(abs(x)))+1 会更快,但 eduffy 已经建议了! :-)
  • 我很想知道那个时间。我几乎认为一系列优化的 if 语句(基于 maxint)可能会优于浮点对数(但我懒得自己测试)。
  • 它永远不会达到零,是吗?
  • @John Pirie:为什么不呢?我的意思是整数除法,当迭代地应用于同一个变量时,它最终会为零。
  • @JP,如果你一直将一个整数除以 10,它最终会变为零。
【解决方案7】:

使用 x86 程序集和查找表的恒定成本版本:

int count_bsr(int i) {
    struct {
            int max;
            int count;
    } static digits[32] = {
            { 9, 1 }, { 9, 1 }, { 9, 1 }, { 9, 1 },
            { 99, 2 }, { 99, 2 }, { 99, 2 },
            { 999, 3 }, { 999, 3 }, { 999, 3 },
            { 9999, 4 }, { 9999, 4 }, { 9999, 4 }, { 9999, 4 },
            { 99999, 5 }, { 99999, 5 }, { 99999, 5 },
            { 999999, 6 }, { 999999, 6 }, { 999999, 6 },
            { 9999999, 7 }, { 9999999, 7 }, { 9999999, 7 }, { 9999999, 7 },
            { 99999999, 8 }, { 99999999, 8 }, { 99999999, 8 },
            { 999999999, 9 }, { 999999999, 9 }, { 999999999, 9 },
            { INT_MAX, 10 }, { INT_MAX, 10 }
    };
        register const int z = 0;
        register unsigned log2;
        if (i < 0) i = -i;
        __asm__ __volatile__ (
                "bsr %1, %0;"  \
                "cmovz %2, %0;"\
                : "=r" (log2)  \
                : "rm" (i), "r"(z));
        return digits[log2].count + ( i > digits[log2].max );
}

另一个,具有较小的查找表和取自 here 的 log10 近似值。

int count_bsr2( int i ) {
    static const unsigned limits[] =
            {0, 10, 100, 1000, 10000, 100000,
             1000000, 10000000, 100000000, 1000000000};
        register const int z = 0;
        register int l, log2;
        if (i < 0) i = -i;
        __asm__ __volatile__ (
                "bsr %1, %0;"  \
                "cmovz %2, %0;"\
                : "=r" (log2)  \
                : "rm" (i), "r"(z));
       l = (log2 + 1) * 1233 >> 12;
       return (l + ((unsigned)i >= limits[l]));
}

这两者都利用了在 x86 上 -INT_MIN 等于 INT_MIN 的事实。

更新:

根据这里的建议,count_bsr 的时间安排和仅 64 位 count_bsr_mod 例程相比使用非常好的 paxdiablo 的二分搜索和二分斩算法稍快修改测试程序以生成具有随机符号分布的集合。测试是使用 gcc 4.9.2 构建的,使用“-O3 -falign-functions=16 -falign-jumps=16 -march=corei7-avx”选项,并在涡轮增压和睡眠状态关闭的其他静止的 Sandy Bridge 系统上执行。

bsr mod的时间:270000 bsr时间:340000 二进制斩波时间:800000 二分查找时间:770000 二分查找时间:470000

测试来源,

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <limits.h>
#include <time.h>

#define numof(a) (sizeof(a) / sizeof(a[0]))

/* Random numbers and accuracy checks. */

static int rndnum[10000];
static int rt[numof(rndnum)];

/* All digit counting functions here. */

static int count_bchop (int n) {
    int r = 1;
    if (n < 0) n = (n == INT_MIN) ? INT_MAX : -n;
    if (n >= 100000000) {
        r += 8;
        n /= 100000000;
    }
    if (n >= 10000) {
        r += 4;
        n /= 10000;
    }
    if (n >= 100) {
        r += 2;
        n /= 100;
    }
    if (n >= 10)
        r++;

    return r;
}

static int count_bsearch(int i)
{
    if (i < 0)
    {
        if (i == INT_MIN)
            return 11; // special case for -2^31 because 2^31 can't fit in a two's complement 32-bit integer
        i = -i;
    }
    if              (i < 100000) {
        if          (i < 1000) {
            if      (i < 10)         return 1;
            else if (i < 100)        return 2;
            else                     return 3;
        } else {
            if      (i < 10000)      return 4;
            else                     return 5;
        }
    } else {
        if          (i < 10000000) {
            if      (i < 1000000)    return 6;
            else                     return 7;
        } else {
            if      (i < 100000000)  return 8;
            else if (i < 1000000000) return 9;
            else                     return 10;
        }
    }
}

// Integer log base 10, modified binary search.
static int count_bsearch_mod(int i) {
   unsigned x = (i >= 0) ? i : -i;
   if (x > 99)
      if (x > 999999)
         if (x > 99999999)
            return 9 + (x > 999999999);
         else
            return 7 + (x > 9999999);
      else
         if (x > 9999)
            return 5 + (x > 99999);
         else
            return 3 + (x > 999);
   else
         return 1 + (x > 9);
}

static int count_bsr_mod(int i) {
    struct {
            int m_count;
            int m_threshold;
    } static digits[32] =
    {
      { 1, 9 }, { 1, 9 }, { 1, 9 }, { 1, 9 },
      { 2, 99 }, { 2, 99 }, { 2, 99 },
      { 3, 999 }, { 3, 999 }, { 3, 999 },
      { 4, 9999 }, { 4, 9999 }, { 4, 9999 }, { 4, 9999 },
      { 5, 99999 }, { 5, 99999 }, { 5, 99999 },
      { 6, 999999 }, { 6, 999999 }, { 6, 999999 },
      { 7, 9999999 }, { 7, 9999999 }, { 7, 9999999 }, { 7, 9999999 },
      { 8, 99999999 }, { 8, 99999999 }, { 8, 99999999 },
      { 9, 999999999 }, { 9, 999999999 }, { 9, 999999999 },
      { 10, INT_MAX }, { 10, INT_MAX }
    };
        __asm__ __volatile__ (
            "cdq                    \n\t"
            "xorl %%edx, %0         \n\t"
            "subl %%edx, %0         \n\t"
            "movl %0, %%edx         \n\t"
            "bsrl %0, %0            \n\t"
            "shlq $32, %%rdx        \n\t"
            "movq %P1(,%q0,8), %q0  \n\t"
            "cmpq %q0, %%rdx        \n\t"
            "setg %%dl              \n\t"
            "addl %%edx, %0         \n\t"
                : "+a"(i)
                : "i"(digits)
                : "rdx", "cc"
        );
    return i;
}

static int count_bsr(int i) {
    struct {
            int max;
            int count;
    } static digits[32] = {
            { 9, 1 }, { 9, 1 }, { 9, 1 }, { 9, 1 },
            { 99, 2 }, { 99, 2 }, { 99, 2 },
            { 999, 3 }, { 999, 3 }, { 999, 3 },
            { 9999, 4 }, { 9999, 4 }, { 9999, 4 }, { 9999, 4 },
            { 99999, 5 }, { 99999, 5 }, { 99999, 5 },
            { 999999, 6 }, { 999999, 6 }, { 999999, 6 },
            { 9999999, 7 }, { 9999999, 7 }, { 9999999, 7 }, { 9999999, 7 },
            { 99999999, 8 }, { 99999999, 8 }, { 99999999, 8 },
            { 999999999, 9 }, { 999999999, 9 }, { 999999999, 9 },
            { INT_MAX, 10 }, { INT_MAX, 10 }
    };
        register const int z = 0;
        register unsigned log2;
        if (i < 0) i = -i;
        __asm__ __volatile__ (
                "bsr %1, %0;"  \
                "cmovz %2, %0;"\
                : "=r" (log2)  \
                : "rm" (i), "r"(z));
        return digits[log2].count + ( i > digits[log2].max );
}

/* Structure to control calling of functions. */

typedef struct {
    int (*fnptr)(int);
    const char *desc;
} tFn;

static tFn fn[] = {
 {   NULL,                              NULL },
 {   count_bsr_mod,  "              bsr mod" },
 {   count_bsr,      "                  bsr" },
 {   count_bchop,    "          binary chop" },
 {   count_bsearch,  "        binary search" },
 {   count_bsearch_mod,"    binary search mod"}
};
static clock_t clk[numof (fn)];

int main (int c, char *v[]) {
    int i, j, k, r;
    int s = 1;

    /* Test code:
        printf ("%11d %d\n", INT_MIN, count_bsearch(INT_MIN));
        //for (i = -1000000000; i != 0; i /= 10)
        for (i = -999999999; i != 0; i /= 10)
            printf ("%11d %d\n", i, count_bsearch(i));
        printf ("%11d %d\n", 0, count_bsearch(0));
        for (i = 1; i != 1000000000; i *= 10)
            printf ("%11d %d\n", i, count_bsearch(i));
        printf ("%11d %d\n", 1000000000, count_bsearch(1000000000));
        printf ("%11d %d\n", INT_MAX, count_bsearch(INT_MAX));
    return 0;
    /* */

    /* Randomize and create random pool of numbers. */

    int p, n;
    p = n = 0;
    srand (time (NULL));
    for (j = 0; j < numof (rndnum); j++) {
        rndnum[j] = ((rand() & 2) - 1) * rand();
    }
    rndnum[0] = INT_MAX;
    rndnum[1] = INT_MIN;

    /* For testing. */
    for (k = 0; k < numof (rndnum); k++) {
        rt[k] = (fn[1].fnptr)(rndnum[k]);
    }

    /* Test each of the functions in turn. */

    clk[0] = clock();
    for (i = 1; i < numof (fn); i++) {
        for (j = 0; j < 10000; j++) {
            for (k = 0; k < numof (rndnum); k++) {
                r = (fn[i].fnptr)(rndnum[k]);
                /* Test code:
                    if (r != rt[k]) {
                        printf ("Mismatch error [%s] %d %d %d %d\n",
                            fn[i].desc, k, rndnum[k], rt[k], r);
                        return 1;
                    }
                /* */
            }
        }
        clk[i] = clock();
    }

    /* Print out results. */

    for (i = 1; i < numof (fn); i++) {
        printf ("Time for %s: %10d\n", fn[i].desc, (int)(clk[i] - clk[i-1]));
    }

    return 0;
}

【讨论】:

  • +1 以获得最令人讨厌的答案。您应该添加性能数据以显示它的性能,尤其是与二元斩相比。
  • count_bsearch() 中有一个错误:对于 OP 的语义,它应该为 i == INT_MIN 返回10
  • -i 对于已签名的int i 具有未定义的 INT_MIN 行为。使用unsigned absval = 0U - i(或i,如果是肯定的)在C 中避免它,但仍然可以有效地编译为否定的相同asm。除非您使用 -fwrapv 进行编译,否则它更像是一种“可以工作”的情况,而不是完全安全地继承您所针对的 ISA 的行为。
【解决方案8】:

你可以这样做: floor (log10 (abs (x))) + 1 或者如果你想节省周期,你可以做比较

if(x<10)
  return 1;
if(x<100)
  return 2;
if(x<1000)
  return 3;
etc etc

这避免了任何计算量大的函数,例如对数甚至乘法或除法。虽然它不优雅,但可以通过将其封装到函数中来隐藏它。它并不复杂或难以维护,因此我不会因为糟糕的编码实践而放弃这种方法;我觉得这样做会把婴儿和洗澡水一起扔出去。

【讨论】:

  • 或者我可以直接弹出一个对话框询问用户,呵呵
  • 为什么在这里投反对票?事实证明这快得令人眼花缭乱。
  • 如果这个解决方案在一般情况下更快,那么日志功能将非常糟糕
  • @David:在我的脑海中,对数大约需要 250-700 个周期,具体取决于 CPU。即使您认为此答案中的每个分支都需要 25 个周期,您也需要 10-30 位数字才能比对数慢,这是最坏的情况。如果您的典型数字很小,那就更好了。
【解决方案9】:

这是一个展开的二进制搜索,没有任何除法或乘法。根据分配给它的数字的分布,它可能会也可能不会胜过使用展开 if 语句完成的其他语句,但应该始终胜过使用循环和乘法/除法/log10 的语句。

随机数均匀分布在整个范围内,在我的机器上,它平均占 paxdiablo 的 count_bchop() 执行时间的 79%、count_ifs() 的 88% 和 count_revifs() 的 97% )。

具有指数分布(一个数字有 n 位的概率等于它有 m 位的概率,其中 mn) count_ifs() 和 count_revifs() 都击败了我的功能。我现在不知道为什么。

int count_bsearch(int i)
{
    if (i < 0)
    {
        if (i == INT_MIN)
            return 10; // special case for -2^31 because 2^31 can't fit in a two's complement 32-bit integer
        i = -i;
    }
    if              (i < 100000) {
        if          (i < 1000) {
            if      (i < 10)         return 1;
            else if (i < 100)        return 2;
            else                     return 3;
        } else {
            if      (i < 10000)      return 4;
            else                     return 5;
        }
    } else {
        if          (i < 10000000) {
            if      (i < 1000000)    return 6;
            else                     return 7;
        } else {
            if      (i < 100000000)  return 8;
            else if (i < 1000000000) return 9;
            else                     return 10;
        }
    }
}

【讨论】:

  • 这很有趣...在看到 paxdiablo 的答案中的“原始速度”版本之后,我之前写了一篇关于这样做的评论。然后我发现你在大约 15 分钟前写了这个答案。哦,好吧,+1 =) 请注意,您可以更改边界以调整函数的性能以支持特定的数据范围。
  • 你在开玩笑吧!几率是多少?所有其他答案都是在 3 年前发布的。我们的故事甚至有点相似。我 8 岁时开始在 IBM XT 上使用 BASIC 编程。
  • 我正在查看“活跃帖子”列表。这出现了,看起来很有趣。我开始阅读 paxdiablo 的帖子,发表了评论,然后就走了……后来回来看到另一个修改,所以我很好奇。那是你的。你认为我们是相互的分身吗?
  • count_bsearch() 中有一个错误:对于 OP 的语义,它应该为 i == INT_MIN 返回10
【解决方案10】:

来自 Bit Twiddling Hacks:

Find integer log base 10 of an integer the obvious way

注意其中的比较顺序。

【讨论】:

    【解决方案11】:

    我在谷歌搜索时偶然发现了这个:http://web.archive.org/web/20190108211528/http://www.hackersdelight.org/hdcodetxt/ilog.c.txt

    快速基准测试清楚地表明二分搜索方法获胜。 lakshmanaraj's 代码非常好,Alexander Korobka's 快了约 30%,Deadcode's 仍然快一点(约 10%),但我发现上述链接中的以下技巧可以进一步提高 10%。

    // Integer log base 10, modified binary search.
    int ilog10c(unsigned x) {
       if (x > 99)
          if (x < 1000000)
             if (x < 10000)
                return 3 + ((int)(x - 1000) >> 31);
             // return 3 - ((x - 1000) >> 31);              // Alternative.
             // return 2 + ((999 - x) >> 31);               // Alternative.
             // return 2 + ((x + 2147482648) >> 31);        // Alternative.
             else
                return 5 + ((int)(x - 100000) >> 31);
          else
             if (x < 100000000)
                return 7 + ((int)(x - 10000000) >> 31);
             else
                return 9 + ((int)((x-1000000000)&~x) >> 31);
             // return 8 + (((x + 1147483648) | x) >> 31);  // Alternative.
       else
          if (x > 9)
                return 1;
          else
                return ((int)(x - 1) >> 31);
             // return ((int)(x - 1) >> 31) | ((unsigned)(9 - x) >> 31);  // Alt.
             // return (x > 9) + (x > 0) - 1;                             // Alt.
    }
    

    注意这是日志 10,而不是位数,所以是 digits = ilog10c(x)+1

    不支持否定,但这很容易通过- 解决。

    【讨论】:

      【解决方案12】:
      if (x == MININT) return 10;  //  abs(MININT) is not defined
      x = abs (x);
      if (x<10) return 1;
      if (x<100) return 2;
      if (x<1000) return 3;
      if (x<10000) return 4;
      if (x<100000) return 5;
      if (x<1000000) return 6;
      if (x<10000000) return 7;
      if (x<100000000) return 8;
      if (x<1000000000) return 9;
      return 10; //max len for 32-bit integers
      

      非常不雅。但比所有其他解决方案更快。整数除法和 FP 日志的成本很高。如果性能不是问题,我最喜欢 log10 解决方案。

      【讨论】:

      • 即使在最坏的情况下(2^32-1),这实际上也是最快的方法 - 请参阅我的更新时间。
      • 我经常怀疑“代码气味”是不喜欢代码的人小跑出来的一个术语——这似乎是一个非常不科学的术语。这段代码是完全可读的(至少对我来说,如果你添加一个简单的注释行,对任何有半脑的人来说)并且将优于此处列出的任何其他解决方案(在我被伪造的环境中非常重要)。如果您只需添加更多 if 语句以适应您工作的环境,该算法的可扩展性为 O(log n) 且可移植。
      • 问题标记为 C 和数学。欢迎任何解决方案,即使是最快的。
      • @Pax:实际上,将其设为循环不应使其显着变慢(反复将阈值乘以 10),并且会使其更紧凑。作为奖励,当您将其限制为 MAX_INT 等时,它可以完美地移植到任何可能的 sizeof(int) 。
      • 速度很快,因为每个数字只有一个比较。迭代解决方案是一个比较、一个除法和每个数字一个增量。整数除法很昂贵,在 C2D 上需要 17 个周期。 log10 远远超过 100 个周期。
      【解决方案13】:
          int n = 437788;
          int N = 1; 
          while (n /= 10) N++; 
      

      【讨论】:

      • 对于负数也可以正常工作 - 将在一个循环中除法,直到 n 变为零,然后循环将停止。
      • 那么事先否定它,呵呵。
      • 这里不需要否定。它将迭代直到结果为零。
      • @sharptooth - 但“循环结束”测试是 10 而不是 0。
      • @ChrisF:这段代码中的循环结束测试表达式是一个赋值运算符,而不是比较!将其读作:while(n = n/10, n!=0) - 逗号后的最后一个表达式是真正的循环结束测试。
      【解决方案14】:

      稍微调整一下 C 语言:

      floor( log10( abs( (number)?number:1 ) ) + 1 );
      

      【讨论】:

        【解决方案15】:

        由于没有人提到,小于 10^ 可以用 SIMD 完成。 这是一个带有 sse2、avx2 和 arm-v8 的 eve 库的实现。

        https://godbolt.org/z/bscr3MWr4

        我不知道这有多快,虽然 AVX-2 看起来很不错

        count_digits(int):                      # @count_digits(int)
                vmovd   xmm0, edi
                vpbroadcastd    ymm0, xmm0
                vmovdqa ymm1, ymmword ptr [rip + .LCPI0_0] # ymm1 = [10,100,1000,10000,100000,1000000,10000000,100000000]
                vpcmpgtd        ymm0, ymm1, ymm0
                vmovmskps       ecx, ymm0
                bsf     edx, ecx
                add     edx, 1
                xor     esi, esi
                cmp     edi, 1000000000
                setl    sil
                mov     eax, 10
                sub     eax, esi
                test    cl, cl
                cmovne  eax, edx
                vzeroupper
                ret
        

        【讨论】:

          【解决方案16】:

          不要使用 floor(log10(...))。这些是要添加的浮点函数,而且速度较慢。我相信最快的方法是这个函数:

          int ilog10(int num)
          {
             unsigned int num = abs(num);
             unsigned int x, i;
             for(x=10, i=1; ; x*=10, i++)
             {
                if(num < x)
                   return i;
                if(x > INT_MAX/10)
                   return i+1;
             }
          }
          

          请注意,由于分支预测错误,某些人建议的二进制搜索版本可能会变慢。

          编辑:

          我做了一些测试,得到了一些非常有趣的结果。我将我的函数与 Pax 测试的所有函数以及 lakshmanaraj 给出的二进制搜索函数一起计时。 测试通过以下代码sn-p完成:

          start = clock();
          for(int i=0; i<10000; i++)
             for(int j=0; j<10000; j++)
                tested_func(numbers[j]);
          end = clock();
          tested_func_times[pass] = end-start;
          

          其中 numbers[] 数组包含在整个 int 类型范围内随机生成的数字(除了 MIN_INT)。对 THE SAME numbers[] 数组上的每个测试函数重复测试。整个测试进行了 10 次,结果取所有通过的平均值。代码使用 GCC 4.3.2 编译,优化级别为 -O3。

          结果如下:

          floating-point log10:     10340ms
          recursive divide:         3391ms
          iterative divide:         2289ms
          iterative multiplication: 1071ms
          unrolled tests:           859ms
          binary search:            539ms
          

          我必须说我真的很惊讶。二进制搜索的性能比我想象的要好得多。我检查了 GCC 如何将此代码编译为 asm。 O_O。现在这令人印象深刻。它的优化比我想象的要好得多,以非常聪明的方式避免了大多数分支。难怪它很快。

          【讨论】:

          • 嗯,最快的方法是将该循环展开为手动优化的 if 语句。但是你对浮点的缓慢性是完全正确的。
          • @Pax:浮点比整数慢是一回事,而 log10() 和 floor() 非常慢是另一回事。我指的是后者。
          • 我将投票赞成这一点,因为它巧妙地使用了阈值上的乘法而不是值上的除法。我想您可以发明一种除法更快的 CPU,但我认为我从未见过,我可能会解雇这样做的工程师 :-)
          • 把它留给我,@stormsoul,我会在大约 8 小时后回复你(现在是奥兹的午夜)。
          • @Pax:你不能。除法运算本质上比乘法复杂得多(并且顺序) - 使得任何实现都比使用 mults 可能慢得多。顺便说一句,优化编译器不会为“除法”代码发出任何除法!它会将其转换为倒数乘法。因为除数是一个小常数,所以可以在编译时计算倒数。它也不会为“乘法”代码发出任何乘法。这将被转换为两个班次和一个加法 - 总共 2 个时钟周期。
          【解决方案17】:

          你可以使用这个公式找到数字中的位数 ceil (log10 (abs (x))) 其中 ceil 返回一个刚好大于 number 的整数

          【讨论】:

            【解决方案18】:

            我猜,最简单的方法是:

             int digits = 0;
            if (number < 0) digits = 1;
            while (number) {
                number /= 10;
                digits++;
            }
            

            digits给出了答案。

            【讨论】:

            • 这个方法会给出不正确的结果(减一),对于负整数,0 的这种情况会计算零位。
            【解决方案19】:

            查找有符号整数长度(即位数)的简单方法是:

            while ( abs(n) > 9 )
            {
                num /= 10;
                ++len;
            }
            

            其中n 是您要查找长度的整数,其中len 等于整数中的位数。这适用于 n 的两个值(负值或正值)。

            abs() 的调用是可选的,如果您只使用正整数。

            【讨论】:

              【解决方案20】:
              void main()
              {     
                  int a,i;
                  printf("Enter the number :");       
                  scanf("%d",&a);
              
                  while(a>0)     
                  {
                      a=a/10;   
                      i++;  
                  }
              
                  getch();
              }
              

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2021-03-09
                • 1970-01-01
                • 2012-03-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2011-04-25
                • 2010-12-02
                相关资源
                最近更新 更多