查找整数的位数

时间:2011-07-11 15:05:27

标签: algorithm digits counting

找到正整数位数的最佳方法是什么?

我找到了这三种基本方法:

  • 转换为字符串

    String s = new Integer(t).toString(); 
    int len = s.length();
    
  • for loop

    for(long long int temp = number; temp >= 1;)
    {
        temp/=10;
        decimalPlaces++;
    } 
    
  • logaritmic calculation

    digits = floor( log10( number ) ) + 1;
    

你可以在大多数语言中计算log10(x)= ln(x)/ ln(10)。

首先我认为字符串方法是最脏的,但我想的越多,我认为这是最快的方法。或者是吗?

18 个答案:

答案 0 :(得分:52)

总有这种方法:

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; }

答案 1 :(得分:22)

我不知道,答案可能会有所不同,具体取决于您的个人语言的实施方式。

所以,压力测试吧!实施所有三种解决方案在1到1,000,000(或代表解决方案将要运行的数字的其他一些大数字)上运行它们,并计算每个数字所花费的时间。

让你的解决方案相互对立,让他们对抗它。像智力角斗士一样。三种算法进入!一种算法离开了!

答案 2 :(得分:21)

正确的答案是测量它 - 但你应该能够猜测转换字符串并通过它们寻找结束标记所涉及的CPU步骤数

然后考虑一下处理器可以执行多少FPU操作以及计算单个日志的容易程度。

编辑:在星期一早上浪费更多时间: - )

String s = new Integer(t).toString(); 
int len = s.length();

高级语言的一个问题是猜测系统在一个看似简单的语句的幕后做了多少工作。强制Joel link

此语句涉及为字符串分配内存,可能还有一些字符串的临时副本。它必须解析整数并将其数字复制到一个字符串中,如果数字很大,可能需要重新分配和移动现有内存。它可能需要检查一堆区域设置以确定您的国家/地区是使用“,”还是“。”,它可能需要进行一堆unicode转换。
然后找到长度必须扫描整个字符串,再次考虑unicode和任何本地特定设置,如 - 你是否使用右 - >左语言。

或者:

digits = floor( log10( number ) ) + 1;

仅仅因为这对你在纸上做起来更难,并不意味着它对电脑来说很难!事实上,高性能计算中的一个好规则似乎是 - 如果人类很难(流体动力学,3d渲染)它对计算机来说很容易,而且对于人类来说很容易(面部识别,检测到声音)嘈杂的房间)电脑很难!

你通常可以假设内置数学函数log / sin / cos等 - 已经成为计算机设计50年的重要部分。因此,即使它们没有直接映射到FPU中的硬件功能,您也可以打赌替代实现非常有效。

答案 3 :(得分:8)

假设:

,此算法也可能不错
  • Number是整数和二进制编码(<<< operation is cheap)
  • 我们不知道数字边界

    var num = 123456789L;
    var len = 0;
    var tmp = 1L;
    while(tmp < num)
    {
        len++;
        tmp = (tmp << 3) + (tmp << 1);
    }
    

此算法的速度应与提供的for-loop(2)相当,但由于(2位移,加减,而不是除法),速度会快一些。

对于Log10算法,它只给出近似答案(接近真实但仍然接近),因为计算Log函数的解析公式具有无限循环且无法精确计算Wiki

答案 4 :(得分:6)

测试条件

  • 十进制数字系统
  • 正整数
  • 最多10位数
  • 语言:ActionScript 3

<强>结果

  

位数:[1,10],

     

没有。运行:1,000,000

     

随机样本:8777509,40442298,477894,329950,513,91751410,313,3159,131309,2

     

结果:7,8,6,6,3,8,3,4,6,1

     

转换为STRING:724ms

     

对数计算:349毫秒

     

DIV 10 ITERATION:229ms

     

手动调节:136毫秒

注意:作者不会对超过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('\nCONVERSION 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('\nLOGARITMIC 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('\nDIV 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('\nMANUAL CONDITIONING: ' + (getTimer() - time));
            trace('sample: ' + digits.slice(0, 10));
        }
    }
}

答案 5 :(得分:2)

  • 转换为字符串:这必须遍历每个数字,找到映射到当前数字的字符,将字符添加到字符集合中。然后获取生成的String对象的长度。将在O(n)中运行n =#digits。

  • for-loop:将执行2次数学运算:将数字除以10并递增计数器。将在O(n)中运行n =#digits。

  • logarithmic:将调用log10和floor,并添加1.看起来像O(1)但我不确定log10或floor功能有多快。我对这类事物的了解因缺乏使用而萎缩,因此这些功能可能存在隐藏的复杂性。

所以我想它归结为:比多个数学运算或log10中发生的任何事情更快地查找数字映射?答案可能会有所不同。可能存在字符映射更快的平台,以及进行计算的其他平台更快。另外要记住的是,第一种方法将创建一个新的String对象,该对象仅为获取长度而存在。这可能会比其他两种方法使用更多的内存,但它可能会或可能不重要。

答案 6 :(得分:2)

你可以明显地从竞争中消除方法1,因为它使用的atoi / toString算法与方法2类似。

方法3的速度取决于是否为指令集包含日志库10的系统编译代码。

答案 7 :(得分:2)

关于你提出的“确定在给定基数中代表给定数字所需的位数”的三种方法,我实际上并不喜欢它们中的任何一种;我更喜欢下面给出的方法。

重新使用方法#1(字符串):任何涉及在字符串和数字之间来回转换的内容通常都很慢。

你的方法#2(temp / = 10):这是致命的缺陷,因为它假定x / 10总是意味着“x除以10”。但是在许多编程语言中(例如:C,C ++),如果“x”是整数类型,则“x / 10”表示“整数除法”,这与浮点除法不同,它引入了每次迭代时的舍入误差,它们会累积在递归公式中,例如您的解决方案#2使用。

重新使用你的方法#3(日志):对于大数字(至少在C语言中,以及可能还有其他语言)来说它是错误的,因为浮点数据类型往往不如64位整数那么精确。 / p>

因此,我不喜欢所有这三种方法:#1有效,但速度很慢,#2被打破,#3对于大数字来说是错误的。相反,我更喜欢这个,适用于从0到大约18.44 quintillion的数字:

unsigned NumberOfDigits (uint64_t Number, unsigned Base)
{
   unsigned Digits = 1;
   uint64_t Power  = 1;
   while ( Number / Power >=  Base )
   {
      ++Digits;
      Power *= Base;
   }
   return Digits;
}

答案 8 :(得分:2)

使用您使用的任何编程语言中最简单的解决方案。我想不出这样一种情况,即整数中的数字位数会成为任何(有用)程序的瓶颈。

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

答案 9 :(得分:2)

对于非常大的整数,log方法要快得多。例如,如果你需要2491327位数字(11920928th Fibonacci数字,如果你关心的话),Python需要几分钟来执行除以10的算法,并且执行1+floor(log(n,10))需要几毫秒。

答案 10 :(得分:2)

import math
def numdigits(n):
    return ( int(math.floor(math.log10(n))) + 1 )

答案 11 :(得分:1)

您可以使用递归解决方案而不是循环,但不知何故类似:

@tailrec
def digits (i: Long, carry: Int=1) : Int =  if (i < 10) carry else digits (i/10, carry+1)

digits (8345012978643L)

对于longs,图片可能会改变 - 根据不同的算法独立测量小数和长数,并根据您的典型输入选择合适的数字。 :)

当然没有什么能比开关更好:

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];

有些人会告诉你优化代码大小,但是yaknow,过早优化......

答案 12 :(得分:1)

这是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))")
  

输出

     

timeInterval0:1.92149806022644

     

timeInterval1:0.557608008384705

     

timeInterval2:2.83262193202972

在此测量基础上,字符串转换是Swift语言的最佳选择。

答案 13 :(得分:1)

保持简单:

long long int a = 223452355415634664;

int x;
for (x = 1; a >= 10; x++)
{
   a = a / 10;
}

printf("%d", x);

答案 14 :(得分:0)

log(x,n)-mod(log(x,n),1)+1

其中x是基数,n是数字。

答案 15 :(得分:0)

在看到@ 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)
  

80个元素的结果

     楼层的

0.105069875717163 (log10(x))
  对于div 10次迭代, 0.867973804473877

答案 16 :(得分:0)

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#版本,不强制转换为字符串。

答案 17 :(得分:0)

在许多已经提到的方法的基础上再添加一种方法。 这个想法是在包含基于 digits 数据类型的 int 的整数范围的数组上使用 binarySearch
Java Arrays 类 binarySearch 的签名是:
binarySearch(dataType[] array, dataType key) 返回搜索键的索引,如果它包含在数组中;否则,(-(insertion point) – 1)
插入点定义为将键插入数组的点。
下面是实现:

    static int [] digits = {9,99,999,9999,99999,999999,9999999,99999999,999999999,Integer.MAX_VALUE};
    static int digitsCounter(int N)
    {
        int digitCount = Arrays.binarySearch(digits , N<0 ? -N:N);
        return 1 + (digitCount < 0 ? ~digitCount : digitCount);
    }

请注意,上述方法仅适用于:Integer.MIN_VALUE <= N <= Integer.MAX_VALUE,但可以通过以下方式轻松扩展 Long 数据类型向 digits 数组添加更多值。


例如,
I) 对于 N = 555, digitCount = Arrays.binarySearch(digits , 555) 返回 -3 (-(2)-1) 因为它不存在于数组中但应该插入点 {{1} } 介于 9 和 99 之间,例如 [9, 55, 99]。
由于我们得到的索引是负数,我们需要对结果进行按位补码。 最后,我们需要将结果加 1 以得到数字 2 中的实际位数。

相关问题