如何确定C中整数的位数?


74

例如,

我想我可以把它变成一个字符串,然后得到字符串的长度,但是这看起来很复杂且容易破解。


6
如果为负数,则获取字符串长度将失败。因此,请改为获取绝对值的长度。;-)
Wim 10 Brink

1
char buff [100]; 整数r = sprintf(buff,“%s”,n)-(r <0);
paxdiablo

1
你是说十进制数字?根据定义,小数位是实数,而整数则没有。
威尔

1
呃...帕克斯,这是合法的表达吗?由于r在赋值之前没有值,因此“(r <0)”部分似乎很可怕。或者,也许您的意思是应该将其作为第二步完成,所以这只是我没有得到的表示(我正在阅读它,就像C语言一样)。
放松

3
必须...记住...到...单元...测试!char buff [100]; int r = sprintf(buff,“%d”,n)-(n <0);
paxdiablo

Answers:


106

http://en.wikipedia.org/wiki/对数


10
这将是不必要的缓慢。不要无故使用昂贵的函数,例如log10()。快速的整数函数非常简单,无需打扰。
–stormsoul

30
哎呀..你们还在运行8088吗?谁在乎几个额外的时钟周期。Paz进行了100,000,000次迭代才能产生可​​测量的变化,甚至可以忽略不计!6秒!哎呀,迪..继续你的生活。明年将是3秒。
eduffy

7
@eduffy:这里是毫秒,那里是毫秒...突然之间,用户在单击按钮后感觉到明显的延迟。严重地,那些小的低效率加起来了。如果您一无所获,为什么还要浪费时钟周期呢?
stormsoul”,2009年

7
@eduffy:如果它在嵌入式处理器上运行,则可能没有任何浮点支持,更不用说日志功能了,并且时钟速度可能只有几十MHz-因此,完全基于整数的解决方案肯定是首选选项。
史蒂夫·梅尔尼科夫

9
事实证明,尽管对于较小的值,简单除法速度更快,但对数比例更好。如果您使用从MIN_INT到MAX_INT的每个int调用除法算法(并重复与Paz的示例相同的100m倍),则每次调用平均要花费13.337秒。用对数进行的平均时间为8.143秒,递归需要11.971秒,而级联的If语句最终平均需要0.953秒。因此,使用Daily-WTF的解决方案要快一个数量级,但从长远来看,它仅次于第二名。
马特·普什

139

递归方法:-)

或迭代:

或原始速度:

修改了上面的内容,以更好地处理MININT。在任何不遵循明智的2 n二进制补码规则的怪异系统上,可能需要进一步调整。

原始速度版本实际上胜过浮点版本,对其进行了以下修改:

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

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

更新以下Stormsoul的建议:

通过stormsoul测试乘法迭代解决方案可获得4秒钟的结果,因此尽管它比除法迭代解决方案要快得多,但仍与优化的if语句解决方案不匹配。

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

使用-O2进行编译可以提高速度,但不能改善相对位置(我将迭代计数增加了十倍来进行检查)。

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

进一步更新:

这将是我对此答案的最终更新,其中不包含不依赖于体系结构的明显错误。受Stormsoul勇于测量的启发,我发布了我的测试程序(根据Stormsoul自己的测试程序进行了修改)以及此处答案中显示的所有方法的一些示例图。请记住,这是在特定计算机上,您的行驶里程可能会因运行位置而异(这就是为什么我发布测试代码的原因)。

随心所欲地处理它:

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

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

优化级别0:

优化级别3:


3
在我看来,递归版本似乎是发布的最干净,最简单,最好的自文档解决方案。

4
@moogs,您可以使用此处提供的任何解决方案,或此处的任何其他答案。速度测试实际上只是放在一边(无法控制)。无论如何,您仍然有时间可利用-只有我的时间可能在这里浪费了,因此可以随意使用我的劳动成果:-)
paxdiablo

3
一个小的性能提示-当您知道某个值始终为非负数时,请使用无符号类型。它们的繁殖和分裂速度稍快。编译器可能会为您猜测某些变量永远不会为负,并自动进行此优化,但事实并非如此。在更复杂的情况下,它永远不会做。
–stormsoul

1
对。IRC中的某人进行了一些性能测试,然后他使用了未签名的,他认为这确实有很大的提升。我敦促您尝试这个未签名的世界!:)
Johannes Schaub-litb

1
不错的答案=)我喜欢原始速度版本,但我认为可以通过以二进制方式分支将最坏情况的比较次数减少到4(不考虑否定测试)来改进它,显然是以牺牲可读性为代价的。在这方面,人们可以将其版本专门调整到预期的数据范围。
水稻2012年

29

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


4
snprintfwithn=0不存储任何内容,并允许空缓冲区指针;返回值是将要写入的字符数。所述+改性剂被用来打印一个符号(+-)即使该值是非负的; 从结果中减去1会使符号不算作数字。
R .. GitHub停止帮助ICE

1
从我(Debian Linux)系统的页面mansnprintf“关于的返回值snprintf()SUSv2C99相互矛盾:当snprintf()使用size=0[size是上面的第二个参数]调用时,SUSv2规定了未指定的返回值,小于1,而C99在这种情况下,允许str为NULL,并提供返回值(一如既往)作为在输出字符串足够大的情况下应写入的字符数。” {SUS =单一UNIX规范}
SlySven '16

5
@SlySven:SUSv2是古老且无关紧要的。
R .. GitHub停止帮助ICE

众所周知Debian有点保守,并且不是最快采用新东西的人。8-P感谢您的回答-我在我正在为其编码的FOSS项目中使用了它(并相应地归因于此)...!
SlySven '16

1
@SlySven:不是来自Debian AFAIK,而是Linux手册页项目。我不认为有一个Linux libc的snprintf行为错误,但是可能有一些古老的专有unices,而MSVC_snprintf也有bug。
R .. GitHub停止帮助ICE

26

二进制搜索伪算法,获取v中r的数字。


4
为什么下降投票,这看起来比十分频循环更有效率?
paxdiablo

1
Downvote不是来自我,而是我怀疑这是因为与Wayne Shephard的变体相比,它的可读性较差(并且可能更慢)。
布赖恩

5
我知道了,但是我认为不应该因为不那么有用而对某些东西投反对票-弹出窗口明确指出“此答案没有帮助”。在这种情况下,我会投票反对另一个人,而不管这个。这是对/ 10迭代的真正改进。尽管如此,现在仍然是积极的,因此没有伤害也没有犯规。(这不是针对Brian的,因为,正如您已经说过的,您没有这样做)。只是为任何人深思熟虑而已。
paxdiablo

5
通过在开头添加另一个if语句if(v> = 10000000000000000LL){r + = 16; v / = 10000000000000000LL; },并且比所有方法都快。
lakshmanaraj

9

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

另一个,具有较小的查找表,并且从此处获取log10近似值。

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

更新:

根据建议,这里是count_bsr的计时和仅比count_bsr_mod快一点的64位使用复杂的paxdiablo的测试程序修改为生成具有随机符号分布的集合的二进制搜索和二进制剁碎算法相比,比例程。测试是在gcc 4.9.2中使用“ -O3 -falign-functions = 16 -falign-jumps = 16 -march = corei7-avx”选项构建的,并在其他处于静止状态且关闭了Turbo和睡眠状态的Sandy Bridge系统上执行。

bsr mod的时间:270000  
BSR时间:340000  
排骨时间:800000  
二进制搜索的时间:770000  
进行二进制搜索的时间:470000  

测试来源


1
+1为最令人讨厌的答案。您应该添加性能数据以显示其性能,尤其是与二进制印章相比。
CodeManX

有一个错误count_bsearch():在OP的语义,它应该返回10i == INT_MIN
chqrlie

-i对于INT_MIN具有未定义的行为int i。使用unsigned absval = 0U - i(或i肯定的)避免在C语言中使用它,但仍然可以将其有效地编译为相同的asm,以求否定。除非你用-fwrapv,,它比完全安全地继承目标ISA的行为更像是一种“工作机会”。
彼得·科德斯

8

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

假设您希望以零值获取0位数字:


floor(log10(abs(x)))+ 1会更快,但是eduffy已经建议这样做!:-)
Wim 10 Brink

我很好奇看到这个时间。我几乎会认为,一系列优化的if语句(基于maxint)可能会胜过浮点对数(但是我懒得自己测试它)。
paxdiablo

它永远不会达到零,是吗?

@约翰·皮里:为什么不呢?我的意思是整数除法,当迭代地应用于同一变量时,最终将得到零。
Sharptooth

@JP,如果将整数除以10,则最终将为零。
paxdiablo

7

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

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


1
或者我可以抛出一个对话框询问用户,嘿
willc2

2
为何在这里大选?事实证明这是如此之快。
paxdiablo

如果此解决方案对于一般情况而言更快,则日志功能将非常糟糕
David Cournapeau 09年

2
@David:在我头顶上,对数大约需要250-700个周期,具体取决于CPU。即使您认为此答案中的每个分支都需要25个周期,您也需要10到30位数字才能使它比对数慢,这是最坏的情况。如果您的典型数字很小,那就更好了。
R .. GitHub STOP HELPING ICE 2012年

7

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

由于随机数分布均匀,涵盖了整个范围,因此在我的计算机上,它平均执行paxdiablo的count_bchop()执行时间的79%,count_ifs()时间的88%和count_revifs()时间的97%。

使用指数分布(n位数字的概率等于m位数字的概率,其中mn)count_ifs()和count_revifs()都击败了我的函数。我现在不知道为什么。


1
真是有趣。。。在看到paxdiablo答案中的“原始速度”版本之后,我就在此之前发表了评论。然后我发现您大约在15分钟之前写了这个答案。哦,+ 1 =)请注意,您可以更改边界来调整函数的性能,以支持特定的数据范围。
水稻2012年

你一定是在跟我开玩笑!几率是多少?所有其他答案都发布于3年前。我们的故事甚至有点相似。我八岁的时候就开始在IBM XT上用BASIC编程。
Deadcode '10 -10-26

我正在查看“活动帖子”列表。这出现并看起来很有趣。我开始关注paxdiablo的帖子,发表评论,然后徘徊...稍后回来,看到另一处修改,所以我很好奇。是你的 您是否认为我们是互助军?
稻谷2012年

有一个错误count_bsearch():在OP的语义,它应该返回10i == INT_MIN
chqrlie '17


5

我在Google搜索中偶然发现了这一点:http : //www.hackersdelight.org/hdcodetxt/ilog.c.txt

快速基准测试清楚地表明二元搜索方法是成功的。lakshmanaraj的代码相当不错,Alexander Korobka的速度快了约30%,Deadcode的速度还快了约10%,但我发现上述链接提供了以下技巧,使速度进一步提高了10%。

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

不支持负片,但可以使用轻松解决-


3

非常优雅。但是比所有其他解决方案都快。整数分区和FP日志的执行成本很高。如果性能不是问题,那么我最喜欢log10解决方案。


实际上,即使在最坏的情况下(2 ^ 32-1),这实际上也是最快的方法-请参阅我的更新时间。
paxdiablo

13
我经常怀疑“代码气味”是一个不喜欢该代码的人抛出的术语-这似乎是一个非常不科学的术语。这段代码可读性很强(至少对我来说,如果添加一个简单的注释行,至少对半个头脑的人来说),并且将优于此处列出的任何其他解决方案(在我伪造的环境中非常重要)。而算法是O(log n)的可扩展性和便携,如果你只需要添加更多的if语句,以适应环境,你在工作。
paxdiablo

2
这个问题被标记为C和数学。任何解决方案都是可以接受的,甚至是最快的。

@Pax:实际上,使其循环不会使它变慢(将阈值重复乘以10),并且会使其更紧凑。另外,当您通过MAX_INT等限制它时,它可以完美地移植到任何可能的sizeof(int)。
–stormsoul

2
之所以快,是因为每个数字只有一个比较。迭代解决方案是每个数字一个比较,一个除法和一个增量。整数除法很昂贵,在C2D上需要17个循环。log10已超过100个周期。
韦恩·谢泼德

2

1
对于负数也可以正常工作-将循环除数直到n变为零,然后循环停止。
Sharptooth

1
那就事先取消它吧。
人工

这里不需要否定。它将迭代直到结果等于零。
sharptooth

@sharptooth -但“循环结束”测试是10不为0。
ChrisF

1
@ChrisF:这段代码中的循环结束测试表达式是ASSIGNMENT运算符,不是比较!读为:while(n = n / 10,n!= 0)-逗号后的最后一个表达式是真正的循环结束测试。
stormsoul


1

我知道我迟到了,但是这段代码比所有其他答案快了x10倍。

...

出:


0

不要使用floor(log10(...))。这些是浮点函数,并且很慢。我相信最快的方法将是此功能:

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

编辑:

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

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

结果如下:

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


好吧,最快的方法竟然是将循环展开为手动优化的if语句。但是,您对浮点运算的缓慢性一无所知。
paxdiablo

@Pax:浮点数比整数慢是一回事,而log10()和floor()非常慢是另一回事。我指的是后者。
stormsoul

我将把这一点投赞成票,是因为巧妙地在阈值上使用了乘法,而不是在值上进行了除法。我想您可以发明一种分裂速度更快的CPU,但我想我从未见过,所以我可能会解雇做到这一点的工程师:-)
paxdiablo

放到我身边,@ stormsoul,我会在大约8个小时内回复您(这是奥兹的午夜)。
paxdiablo

@Pax:你不能。除法运算本质上比乘法要复杂得多(并且顺序要多得多),这使得任何实现都比mult慢得多。顺便提一句,优化的编译器不会为“除法”代码发出任何除法!它将通过互逆将其转换为乘法。由于除数是一个小常数,因此可以在编译时计算倒数。它也不会为“乘法”代码发出任何乘法。这将转换为两个移位和一个加法-总共2个时钟周期。
stormsoul

0

您可以使用此公式ceil(log10(abs(x)))查找数字中的位数,其中ceil返回一个整数,该整数刚好大于number


0

我想,最简单的方法是:

数字给出了答案。


1
对于负整数,此方​​法将给出错误的结果(减一),这种情况下0它将计数为零数字。
2014年

0

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

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

abs()如果仅使用正整数,则on的调用是可选的。


0

对于C#,这是一个非常快速且简单的解决方案...


-1
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.