在C语言中如何找到一个整数的长度

93

我想知道如何在C语言中找到一个整数的长度。

例如:

  • 1 => 1
  • 25 => 2
  • 12512 => 5
  • 0 => 1

等等。

我该如何在C语言中实现这个?


1
如果整数是0,"length"的定义是什么?负数呢? - kennytm
1
请参见https://dev59.com/VXRB5IYBdhLWcg3wQFPu。这几乎是一个重复的问题,但不完全相同,因为它是一个.NET问题。 - ChrisF
7
正确的问题不是整数的长度,而是表示该数字所需的最小十进制位数(存储在C int中)。log10是你的好朋友:log10(10000) = 4,再加上一位数字(log10必须截断)...如果数字为负数,并且想要计算减号,则需要再增加一位,以及log10(-num) (因为负数的对数是“有问题的”)。 - ShinTakezou
2
嗯。实验表明,在gcc和glibc下,对于10的幂从1到2 ** 19,log10(10 ** n)产生精确值。但是我不会指望所有实现都能如此。(**表示指数运算符;C中没有这样的运算符。) - Keith Thompson
请问如何在C语言中确定一个整数的位数? - A Person
显示剩余3条评论
30个回答

132

C:

您可以对数的绝对值取以10为底的对数,向下舍入并加一。这适用于不为0的正负数,并且避免了使用任何字符串转换函数。

log10absfloor函数由math.h提供。例如:

int nDigits = floor(log10(abs(the_integer))) + 1;

由于 log10(0) 根据 man 3 log 的说法返回 -HUGE_VAL,因此您应该将其包装在一个保证 the_integer != 0 的子句中。

另外,如果您对数字的长度(包括负号)感兴趣,并且输入为负数,则可能需要将最终结果加一。

Java:

int nDigits = Math.floor(Math.log10(Math.abs(the_integer))) + 1;

注意:该方法涉及到计算中的浮点数性质,可能比更直接的方法慢。请参阅Kangkan答案的评论以了解一些效率讨论。


2
实际上,你应该使用 floor 并加 1。Math.Ceil(Math.Log(99)) = 2 但 Math.Ceil(Math.Log(10)) = 1。Math.Floor(Math.Log(99)) + 1 = 2 和 Math.Floor(Math.Log(10)) = 2。 - Sani Huttunen
1
+1 简洁明了 - 即使速度慢一些,这仍然是我首选的答案 - 毕竟,速度差异并不大,而且这种代码很少会成为性能瓶颈。 - Eamon Nerbonne
+1 对于关于效率的注释(以及后续的评论)。每个程序员确实都应该知道基本数学,但有时候由于可恶的快速二进制处理器,数学并不是最快的路线。谈到 C 语言,这很容易成为瓶颈。 - cregox
3
这对于C语言中的整数999999999999999999不起作用,因为它将被转换为更大的双精度值,从而产生错误的数字计数。原帖作者没有明确指定“int”,只是写了"integer"。 - chqrlie
解决 == 0 情况,这里有一个一行代码:(the_integer == 0 ? 1 : (int)floor(log10(abs(the_integer))) + 1) - luckydonald
显示剩余4条评论

62
如果您对一个快速非常简单的解决方案感兴趣,以下内容可能是最快的(这取决于所涉及数字的概率分布):
int lenHelper(unsigned x) {
    if (x >= 1000000000) return 10;
    if (x >= 100000000)  return 9;
    if (x >= 10000000)   return 8;
    if (x >= 1000000)    return 7;
    if (x >= 100000)     return 6;
    if (x >= 10000)      return 5;
    if (x >= 1000)       return 4;
    if (x >= 100)        return 3;
    if (x >= 10)         return 2;
    return 1;
}

int printLen(int x) {
    return x < 0 ? lenHelper(-x) + 1 : lenHelper(x);
}

虽然这种解决方案可能不会赢得最具创意的解决方案奖,但它非常容易理解和执行 - 因此它很快。

在Q6600上使用MSC编译器,我使用了以下循环来对其进行基准测试:

int res = 0;
for(int i = -2000000000; i < 2000000000; i += 200) res += printLen(i);

这个解决方案耗时0.062秒,使用聪明的对数方法的Pete Kirkham的第二快速解决方案需要0.115秒,几乎是两倍长。但是,对于约为10000以下的数字,聪明的对数方法更快。

以某些清晰度为代价,您可以更可靠地击败聪明的对数(至少在Q6600上):

int lenHelper(unsigned x) { 
    // this is either a fun exercise in optimization 
    // or it's extremely premature optimization.
    if(x >= 100000) {
        if(x >= 10000000) {
            if(x >= 1000000000) return 10;
            if(x >= 100000000) return 9;
            return 8;
        }
        if(x >= 1000000) return 7;
        return 6;
    } else {
        if(x >= 1000) {
            if(x >= 10000) return 5;
            return 4;
        } else {
            if(x >= 100) return 3;
            if(x >= 10) return 2;
            return 1;
        }
    }
}

这个解决方案在处理大数字时仍然需要0.062秒,对于小数字则会降至约0.09秒- 在两种情况下都比智能日志方法更快。 (gcc生成更快的代码; 对于这种解决方案是0.052秒,智能日志方法为0.09秒)。


13
我不敢想象如果第二个版本完全使用三元运算符会是什么样子...... - Eamon Nerbonne
如果这个程序用于处理长列表的数字,分支代码的数量将会对CPU的分支预测造成严重影响,从而无法产生最快的执行效果。 - Lloyd Crawley
1
在我的基准测试中,它仍然是最快的解决方案 - 请注意,所有其他整数解决方案也需要多个分支,而唯一真正的替代方案是将int转换为double并使用浮点对数(结果证明这也不便宜)。 - Eamon Nerbonne
1
@earthdan:那个解决方案可以,但由于除法而相当慢。它总是使用比此代码的第二个版本更多的分支,并且平均使用比此处发布的第一个解决方案更多的分支。此外,那个解决方案相当聪明(以不好的方式),因为它能够工作的原因并不完全明显。如果您想要一个简短且明显的解决方案,请使用https://dev59.com/uXA75IYBdhLWcg3w3M9e#3068412;如果您想要一个快速且明显的解决方案,请使用此解决方案。无法想象https://dev59.com/2Ww15IYBdhLWcg3wYKqo#6655759的用例(尽管这当然是一项有趣的智力练习!) - Eamon Nerbonne

40
int get_int_len (int value){
  int l=1;
  while(value>9){ l++; value/=10; }
  return l;
}

第二个也适用于负数:

int get_int_len_with_negative_too (int value){
  int l=!value;
  while(value){ l++; value/=10; }
  return l;
}

3
我喜欢这个。没有对大小做出假设的临时字符缓冲区。 - Noufal Ibrahim
快速而优雅,但是这对负数不起作用。不知道这是否对问题发布者构成了问题。 - Jamie Wong
1
它会返回1,试一下吧。 - zed_0xff
你说得对 - 它确实会 :-). 对我来说,通过 if-return 区分这种情况比否定更清晰(而且不会更慢)。 - Eamon Nerbonne

22

你可以编写如下函数:

unsigned numDigits(const unsigned n) {
    if (n < 10) return 1;
    return 1 + numDigits(n / 10);
}

4
为什么要使用约18个递归函数调用,而不是一个log10调用来完成这个任务,这样做效率过低。 - Jordan Lewis
13
在我的笔记本电脑上,@Jordan Lewis的代码被调用20百万次只需要1.8秒钟; 而你的代码调用时需要6.6秒钟(gcc -O3编译器下)。这个代码中的所有递归调用比一次log10函数的调用要慢得多。 - Pete Kirkham
2
我在我的英特尔 i7 上使用 -O3 对 2000 万次运算进行测试,log10 版本用时 .001 秒,递归版本用时 .44 秒。 - Jordan Lewis
1
@Jordan 我在代码开头添加了 if ( x < 0 ) return 1 + printed_length ( -x ); - Pete Kirkham
6
@Pete 我之前肯定出了错,导致编译器在优化之前就将实际调用省略了。现在我发现与你一样存在一个差距——递归版本的运行时间为 0.26 秒,而浮点数算术版本的运行时间为 1.68 秒。有趣! - Jordan Lewis
显示剩余10条评论

12

n的长度:

length =  ( i==0 ) ? 1 : (int)log10(n)+1;

你应该避免通过强制转换进行四舍五入,而是采用更明确(即便携和一致)的四舍五入方法。 - Chris Lutz
“casting” 是如何实现的?像 floor 这样的函数是如何实现的?(我们假设处理器硬件支持 IEEE,或通过数学协处理器,或者有软件函数来执行与 fp-capable 处理器上通常存在的相同功能)... 最终 (int) 在大多数情况下是可移植和一致的(我敢说,在我们通常关心的所有情况下都是这样)。 - ShinTakezou
正如其他帖子中提到的那样,当 n = 0 时,这将失败。 - Jamie Wong
@Lutz:您认为,通过假设从double到int的强制转换是未定义的,您能获得什么样的可移植性?实际上,是否存在任何相关的平台呢? - Eamon Nerbonne
如果它是符合标准的 C 实现,那么它遵守以下规则:当实浮点类型的有限值被转换为除 _Bool 以外的整数类型时,其小数部分被舍弃(即该值向零舍入) - Pete Kirkham
@Pete,@Eamon - 我的错。我以为它是未定义或实现定义的。 - Chris Lutz

8
整数 x 的位数等于 1 + log10(x)。因此,您可以这样做:
#include <math.h>
#include <stdio.h>

int main()
{
    int x;
    scanf("%d", &x);
    printf("x has %d digits\n", 1 + (int)log10(x));
}

或者你可以运行一个循环来自己计算数字:通过整数除法,直到数字为0:

int numDigits = 0;
do
{
    ++numDigits;
    x = x / 10;
} while ( x );

在第一种解决方案中,如果整数为0,则需要小心地返回1,而且您可能还希望在处理负整数时采用-x(如果x < 0)的方式。


是的,一个漂亮简单的“do”循环。 - chux - Reinstate Monica

8
一种正确的snprintf实现方法:
int count = snprintf(NULL, 0, "%i", x);

如果x是负数会发生什么? - Immanuel Weihnachten
1
然后,count 将因为负号而多一个单位。 - sam hocevar

7

最高效的方法可能是使用一个快速的基于对数的方法,类似于用于确定整数中最高位设置的方法。

size_t printed_length ( int32_t x )
{
    size_t count = x < 0 ? 2 : 1;

    if ( x < 0 ) x = -x;

    if ( x >= 100000000 ) {
        count += 8;
        x /= 100000000;
    }

    if ( x >= 10000 ) {
        count += 4;
        x /= 10000;
    }

    if ( x >= 100 ) {
        count += 2;
        x /= 100;
    }

    if ( x >= 10 )
        ++count;

    return count;
}

这种(可能过早的)优化在我的netbook上进行2000万次调用需要0.65秒;类似zed_0xff的迭代除法需要1.6秒,像Kangkan的递归除法需要1.8秒,而使用浮点函数(Jordan Lewis的代码)需要长达6.6秒。使用snprintf需要11.5秒,但是将为您提供snprintf对于任何格式所需的大小,而不仅仅是整数。Jordan报告说,他的处理器上浮点运算比我的更快,因此时间顺序不能保持一致。
最简单的方法可能是询问snprintf打印的长度:
#include <stdio.h>

size_t printed_length ( int x )
{
    return snprintf ( NULL, 0, "%d", x );
}

int main ()
{
    int x[] = { 1, 25, 12512, 0, -15 };

    for ( int i = 0; i < sizeof ( x ) / sizeof ( x[0] ); ++i )
        printf ( "%d -> %d\n", x[i], printed_length ( x[i] ) );

    return 0;
}

1
如果你要使用snprintf(),为什么不用snprintf(NULL, 0, "%d", x)并且不写任何东西呢?(至少,在你的函数中使用一个静态缓冲区。) - Chris Lutz
1
因为今天早上我喝的咖啡还不够,而且我在思考答案的第一部分。 - Pete Kirkham
2
这取决于你为什么需要长度 - 如果你想知道snprintf需要多少个字符,最好使用snprintf; 如果你想要愚蠢的优化代码,你可能需要第一个。 - Pete Kirkham
1
"C99允许在n==0的情况下str为NULL,并且始终返回将被写入的字符数(如有必要)以及已输出的字符串太大的情况。所以这是可以的,为什么不呢?" - ShinTakezou
1
你的解决方案对于 INT_MIN 不起作用。使用本地的 unsigned 变量进行测试,并使用 x >= 0 ? x : -(unsigned)x 进行初始化。 - chqrlie
显示剩余2条评论

6

是的,可以使用sprintf。

int num;
scanf("%d",&num);
char testing[100];
sprintf(testing,"%d",num);
int length = strlen(testing);

或者,您可以使用 log10 函数进行数学计算。

int num;
scanf("%d",&num);
int length;
if (num == 0) {
  length = 1;
} else {    
  length = log10(fabs(num)) + 1;
  if (num < 0) length++;
}

1
不,那实际上相当危险且容易出错。你应该使用 snprintf(),这样就不必编写(和冒溢风险)任何内容了。 - Chris Lutz
假设他指的是C整数(而不是大数类型),那么就不会出现溢出问题。 - Jamie Wong
1
当计算机变得更大时,就会出现问题。当然,你需要一台512位的计算机才能破解你的代码,但总有一天他们会制造出这样的计算机。 - Chris Lutz
不会,很可能128位将是最后的边界...没有任何理由去超越它(夸张一点,我说没有理由超过64位,但我已经几乎错了,但目前还没有真正的128位处理器可用,而且它们很难被使用,至少在消费级计算机上...我希望如此,因为如果他们需要它们,这意味着操作系统将变得太臃肿,我们会想念这些更好的日子)。 - ShinTakezou
1
你可以直接使用 sprintf() 的返回值,而无需调用 strlen() - phuclv

5
int digits=1;

while (x>=10){
    x/=10;
    digits++;
}
return digits;

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