查找正整数的位数的最佳方法是什么?
我发现了这3种基本方法:
转换为字符串
String s = new Integer(t).toString();
int len = s.length();
for循环
for(long long int temp = number; temp >= 1;)
{
temp/=10;
decimalPlaces++;
}
对数计算
digits = floor( log10( number ) ) + 1;
在大多数语言中,您都可以在其中计算log10(x)= ln(x)/ ln(10)。
首先,我认为字符串方法是最肮脏的方法,但是我想得越多,它就越是最快的方法。 还是?
首先定义"最佳"。然后选择"最佳"算法很容易。
使用Luke来源:docjar.com/html/api/java/lang/Integer.java.html
这个问题可能属于codegolf。
似乎这里没有人对非基数10的整数没有任何考虑。不使用Integer.toString(t, radix),temp = radix;(以及相应的numDigits++;,从十进制开始概括)或ln(x) ln(radix) ...
我无法想象使用对数会击败任何事物。没有数据转换,没有循环,只是一个简单,直接的计算。
@TMN:当您没有log10函数或必须考虑精度损失的影响并证明您的公式将始终为给定范围内的值返回正确的结果时,这并不容易。
您如何期望通过思考解决问题?您应该考虑测量。
您在很多方面都是正确的,我没有定义"最佳"条件,因此我应该做比较冷杉(后来我做了作业,请参阅下文),但是此讨论带来了很多好主意;)
@Joey Adams:任何对数都可以。例如,log10(x)= ln(x)/ ln(10)。
@让-弗朗索瓦·科贝特(Jean-Franois Corbett):的确,这在数学上是正确的。但是您是否100%确信它将与机器浮点算法一起正常工作?
@Joey Adams:好点……嗯,我的浮点数ln(e)有信心。
对于整数:floor(ln(n + 0.5) ln(10))可能有助于降低精度...
总有这种方法:
n = 1;
if ( i >= 100000000 ) { n += 8; i /= 100000000; }
if ( i >= 10000 ) { n += 4; i /= 10000; }
if ( i >= 100 ) { n += 2; i /= 100; }
if ( i >= 10 ) { n += 1; }
如果您知道一些需要支持的整数范围,那么这将非常有效。
@jimreed:对。您需要知道ceil(log2(log10(MAXINT)))。然后,它最多执行截断分割的数量。
或者,如果这是一个真正的编码问题,而不仅仅是难题,并且您知道整数始终小于10,000,那么您甚至不需要前两个if语句。
正确的答案是对其进行度量-但是您应该能够猜测转换字符串并遍历字符串以寻找结束标记所涉及的CPU步骤数
然后考虑您的处理器可以执行多少个FPU操作,以及计算单个日志有多容易。
编辑:在星期一早上浪费更多时间:-)
String s = new Integer(t).toString();
int len = s.length();
高级语言的问题之一是猜测一个看起来很简单的语句在幕后系统正在做多少工作。强制Joel链接
该语句涉及为字符串以及可能为字符串的几个临时副本分配内存。它必须解析整数并将其数字复制到字符串中,如果数量很大,可能必须重新分配并移动现有内存。它可能必须检查一堆区域设置来确定您的国家使用的是","还是"。",它可能必须进行一堆unicode转换。
然后找到长度必须扫描整个字符串,再次考虑unicode和任何本地特定设置,例如-您是右语言还是左语言?
或者:
digits = floor( log10( number ) ) + 1;
仅仅因为这对您来说很难在纸上做并不意味着对计算机来说就很难了!实际上,高性能计算的一个好规则似乎是-如果某件事对人来说很困难(流体动力学,3d渲染),则对计算机来说很容易;如果某件事对人来说很容易(人脸识别,检测语音)嘈杂的房间)很难用电脑!
您通常可以假定内置的数学函数log / sin / cos等-50年来一直是计算机设计的重要组成部分。因此,即使它们没有直接映射到FPU的硬件功能中,您也可以打赌,替代实施非常有效。
@PMF…或ValueError或鼻恶魔。因此答案是floor(log10(number || 1)) + 1,或更明确地说是:(number == 0) ? 1 : (floor(log10(number)) + 1)
为什么不使用ceil()而不是floor() + 1?
@JosieThompson ceil(0)= 0,而floor(0)+1 = 1。
我在OCaml中创建了一个小脚本来测试不同的方法(字符串,递归,循环,日志),结果表明log是最快的。从最快到最慢的顺序为(1)log,(2)string,(3)recursion,(4)loop。
我不知道,答案可能会有所不同,具体取决于您所使用的语言的实现方式。
所以,压力测试吧!实施所有三个解决方案。以1到1,000,000(或代表解决方案的数字代表的其他一些巨大数字)上运行它们,以及每个模型花费的时间。
将您的解决方案相互对立,让他们相互竞争。就像知识分子的角斗士。输入三种算法!一种算法离开了!
假设以下条件,此算法也可能很好:
数字是整数和二进制编码(<
我们不知道数字界限
var num = 123456789L;
var len = 0;
var tmp = 1L;
while(tmp < num)
{
len++;
tmp = (tmp << 3) + (tmp << 1);
}
该算法的速度应与提供的for循环(2)相当,但是由于(2个移位,加法和减法,而不是除法)而快一些。
对于Log10算法,由于用于计算Log函数的解析公式具有无限循环且无法精确计算Wiki,因此它只会给您近似的答案(接近真实值,但仍是)。
这里我们只需要对数的整数部分,这会使"无限循环"无效。
是的,您是对的,它使计算变得有限,但对于小数而言似乎仍然很昂贵。
不应该是:tmp += (tmp << 3) + (tmp << 1);吗?
如果输入数字可被10整除,这实际上无法正确计算。要解决此问题,如果数字可被10整除,则应将数字长度增加1。
如果有人想知道<
@mmstick不,不是被10整除的数字,该算法仅对10(包括1)和0的所有幂均无效。它给出20、30、40等的正确答案。
测试条件
十进制数字系统
正整数
最多10位数字
语言:ActionScript 3
结果
digits: [1,10],
no. of runs: 1,000,000
random sample: 8777509,40442298,477894,329950,513,91751410,313,3159,131309,2
result: 7,8,6,6,3,8,3,4,6,1
CONVERSION TO STRING: 724ms
LOGARITMIC CALCULATION: 349ms
DIV 10 ITERATION: 229ms
MANUAL CONDITIONING: 136ms
注意:作者不愿对超过10位数字的结论做出任何结论。
脚本
package {
import flash.display.MovieClip;
import flash.utils.getTimer;
/**
* @author Daniel
*/
public class Digits extends MovieClip {
private const NUMBERS : uint = 1000000;
private const DIGITS : uint = 10;
private var numbers : Array;
private var digits : Array;
public function Digits() {
// ************* NUMBERS *************
numbers = [];
for (var i : int = 0; i < NUMBERS; i++) {
var number : Number = Math.floor(Math.pow(10, Math.random()*DIGITS));
numbers.push(number);
}
trace('Max digits: ' + DIGITS + ', count of numbers: ' + NUMBERS);
trace('sample: ' + numbers.slice(0, 10));
// ************* CONVERSION TO STRING *************
digits = [];
var time : Number = getTimer();
for (var i : int = 0; i < numbers.length; i++) {
digits.push(String(numbers[i]).length);
}
trace('
CONVERSION TO STRING - time: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
// ************* LOGARITMIC CALCULATION *************
digits = [];
time = getTimer();
for (var i : int = 0; i < numbers.length; i++) {
digits.push(Math.floor( Math.log( numbers[i] ) / Math.log(10) ) + 1);
}
trace('
LOGARITMIC CALCULATION - time: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
// ************* DIV 10 ITERATION *************
digits = [];
time = getTimer();
var digit : uint = 0;
for (var i : int = 0; i < numbers.length; i++) {
digit = 0;
for(var temp : Number = numbers[i]; temp >= 1;)
{
temp/=10;
digit++;
}
digits.push(digit);
}
trace('
DIV 10 ITERATION - time: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
// ************* MANUAL CONDITIONING *************
digits = [];
time = getTimer();
var digit : uint;
for (var i : int = 0; i < numbers.length; i++) {
var number : Number = numbers[i];
if (number < 10) digit = 1;
else if (number < 100) digit = 2;
else if (number < 1000) digit = 3;
else if (number < 10000) digit = 4;
else if (number < 100000) digit = 5;
else if (number < 1000000) digit = 6;
else if (number < 10000000) digit = 7;
else if (number < 100000000) digit = 8;
else if (number < 1000000000) digit = 9;
else if (number < 10000000000) digit = 10;
digits.push(digit);
}
trace('
MANUAL CONDITIONING: ' + (getTimer() - time));
trace('sample: ' + digits.slice(0, 10));
}
}
}
感谢您运行所有这些测试;这非常有帮助!当然,确切的速度会因环境而异,但这是对预期结果以及如何执行此过程的很好概述。
您缺少通过使用移位循环而不是除法来计算数字的选项。
转换为字符串:这将必须遍历每个数字,找到映射到当前数字的字符,然后将一个字符添加到字符集合中。然后获取生成的String对象的长度。将在O(n)中运行n =#个数字。
for循环:将执行2个数学运算:将数字除以10并增加一个计数器。将在O(n)中运行n =#个数字。
对数:将调用log10和floor并加1。看起来像O(1),但我不确定Log10或floor函数有多快。我对这类事情的了解因缺乏使用而萎缩,因此这些功能可能隐藏着复杂性。
因此,我想可以归结为:查找数字映射的速度是否比多个数学运算或log10中发生的一切更快?答案可能会有所不同。在某些平台上,字符映射更快,而在其他平台上,计算速度更快。还要记住的是,第一个方法将创建一个新的String对象,该对象仅出于获取长度的目的而存在。这可能会比其他两种方法使用更多的内存,但这可能会或可能不会。
转换为字符串也可能会在内部执行类似第二个循环的操作。
您显然可以从竞争中删除方法1,因为它使用的atoi / toString算法类似于方法2。
方法3的速度取决于是否为指令集包括日志基数10的系统编译代码。
在您使用的任何编程语言中使用最简单的解决方案。我想不出在任何(有用的)程序中计数整数的位数都会成为瓶颈的情况。
C,C ++:
char buffer[32];
int length = sprintf(buffer,"%ld", (long)123456789);
Haskell:
len = (length . show) 123456789
JavaScript:
length = String(123456789).length;
PHP:
$length = strlen(123456789);
Visual Basic(未经测试):
length = Len(str(123456789)) - 1
如果知道位数并且相对较低,那么我只能同意你的看法。
R:nchar(123456789)
对于非常大的整数,log方法要快得多。例如,使用2491327数字(如果需要的话,第11920928个斐波那契数字),Python花费几分钟执行10分频算法,并花费毫秒执行1+floor(log(n,10))。
import math
def numdigits(n):
return ( int(math.floor(math.log10(n))) + 1 )
您的意思是int(math.floor(math.log10(n or 1))) + 1,因为log10(0)会提高ValueError,而ceil(log10(1)), ceil(log10(10)), ...是0, 1, ...,而floor(...) + 1是正确的值。
它不适用于999999999999999(15位数字),返回16
关于您提议的"确定在给定基数中表示给定数字所必需的数字位数"的三种方法,我实际上并不喜欢它们中的任何一种。我更喜欢下面提供的方法。
重新使用方法1(字符串):任何涉及在字符串和数字之间来回转换的操作通常都很慢。
重新执行方法2(temp / = 10):这是致命的错误,因为它假定x / 10始终表示" x除以10"。但是在许多编程语言(例如C,C ++)中,如果" x"是整数类型,则" x / 10"表示"整数除法",这与浮点除法并不相同,因此它引入了在每次迭代中都舍入错误,并且它们以递归公式累积,例如您的解决方案2使用的公式。
重新使用方法3(日志):对于大量数字(至少在C语言以及其他语言中也是如此)来说,这是有问题的,因为浮点数据类型往往不如64位整数那么精确。
因此,我不喜欢这3种方法:#1有效,但速度慢,#2损坏,#3对于大号车有故障。相反,我更喜欢这种方法,它适用于从0到大约18.44亿五千万的数字:
unsigned NumberOfDigits (uint64_t Number, unsigned Base)
{
unsigned Digits = 1;
uint64_t Power = 1;
while ( Number / Power >= Base )
{
++Digits;
Power *= Base;
}
return Digits;
}
把事情简单化:
long long int a = 223452355415634664;
int x;
for (x = 1; a >= 10; x++)
{
a = a / 10;
}
printf("%d", x);
这是Swift 4中的度量。
算法代码:
extension Int {
var numberOfDigits0: Int {
var currentNumber = self
var n = 1
if (currentNumber >= 100000000) {
n += 8
currentNumber /= 100000000
}
if (currentNumber >= 10000) {
n += 4
currentNumber /= 10000
}
if (currentNumber >= 100) {
n += 2
currentNumber /= 100
}
if (currentNumber >= 10) {
n += 1
}
return n
}
var numberOfDigits1: Int {
return String(self).count
}
var numberOfDigits2: Int {
var n = 1
var currentNumber = self
while currentNumber > 9 {
n += 1
currentNumber /= 10
}
return n
}
}
测量代码:
var timeInterval0 = Date()
for i in 0...10000 {
i.numberOfDigits0
}
print("timeInterval0: \(Date().timeIntervalSince(timeInterval0))")
var timeInterval1 = Date()
for i in 0...10000 {
i.numberOfDigits1
}
print("timeInterval1: \(Date().timeIntervalSince(timeInterval1))")
var timeInterval2 = Date()
for i in 0...10000 {
i.numberOfDigits2
}
print("timeInterval2: \(Date().timeIntervalSince(timeInterval2))")
Output
timeInterval0: 1.92149806022644
timeInterval1: 0.557608008384705
timeInterval2: 2.83262193202972
在此基础上,字符串转换是Swift语言的最佳选择。
您可以使用递归解决方案而不是循环,但是在某种程度上类似:
@tailrec
def digits (i: Long, carry: Int=1) : Int = if (i < 10) carry else digits (i/10, carry+1)
digits (8345012978643L)
对于长整数,图片可能会发生变化-根据不同的算法分别测量较小和较长的数字,然后选择合适的数字。 :)
当然,没有什么能比开关更好的了:
switch (x) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: return 1;
case 10: case 11: // ...
case 99: return 2;
case 100: // you get the point :)
default: return 10; // switch only over int
}
除了普通数组:
int [] size = {1,1,1,1,1,1,1,1,1,2,2,2,2,2,... };
int x = 234561798;
return size [x];
有人会告诉您优化代码大小,但是要知道,过早的优化...
let numDigits num =
let num = abs(num)
let rec numDigitsInner num =
match num with
| num when num < 10 -> 1
| _ -> 1 + numDigitsInner (num / 10)
numDigitsInner num
F#版本,不强制转换为字符串。
看到@ daniel.sedlacek结果后,我感到很好奇,因此我使用Swift对超过10位数字进行了测试。我在操场上运行了以下脚本。
let base = [Double(100090000000), Double(100050000), Double(100050000), Double(100000200)]
var rar = [Double]()
for i in 1...10 {
for d in base {
let v = d*Double(arc4random_uniform(UInt32(1000000000)))
rar.append(v*Double(arc4random_uniform(UInt32(1000000000))))
rar.append(Double(1)*pow(1,Double(i)))
}
}
print(rar)
var timeInterval = NSDate().timeIntervalSince1970
for d in rar {
floor(log10(d))
}
var newTimeInterval = NSDate().timeIntervalSince1970
print(newTimeInterval-timeInterval)
timeInterval = NSDate().timeIntervalSince1970
for d in rar {
var c = d
while c > 10 {
c = c/10
}
}
newTimeInterval = NSDate().timeIntervalSince1970
print(newTimeInterval-timeInterval)
Results of 80 elements
0.105069875717163 for floor(log10(x))
0.867973804473877 for div 10 iterations
log(x,n)-mod(log(x,n),1)+1
其中x是基数,n是数字。
我在实验" Math.floor(Math.log(numbers [i])/ Math.log(10))+ 1"中使用了此公式,并且有很多更快的方法。