获取整数的数字位数的方法?

463

有没有比这个方法更简洁的获取一个整数位数的方式?

int numDigits = String.valueOf(1000).length();

9
请定义整数的长度。 - Tom
40
我认为他想要数数字中的位数。 - Alberto Zaccagni
3
人们给你的答案是正确的,他们给出的是int的长度而不将其转换为字符串,但是为什么你不想将其转换为字符串呢?如果是速度问题,我不确定这些方法会更快。你可能需要进行一些测试(或者决定它是否真的很重要)。 - Beska
3
@ptomli 中的十六进制数字仍然是数字,只是处于不同的进位系统中。 - Mark Pim
3
@Ptomli 当然可以,但在 Integer.toString 函数和一般交谈中,十进制是默认选项。当银行告诉我:“请在此框中写入支票金额”时,我不会问他们是否应该用十进制、十六进制或八进制书写。我们默认使用十进制,除非另有规定或上下文需要。 - Jay
显示剩余4条评论
29个回答

407

你基于字符串的解决方案完全可行,没有任何“不整洁”的地方。你必须意识到,在数学上,数字没有长度,也没有数字。长度和数字都是特定进制下数字的 物理表示 的属性,即一个字符串。

基于对数的解决方案在内部执行与基于字符串的解决方案相同的任务(一些),并且可能会稍微快一些,因为它仅生成长度并忽略数字。但我认为它实际上并不清晰 - 这是最重要的因素。


63
在选择解决问题的方法时,要考虑代码意图。+1 - pupeno
6
数据点:在我的计算机上,日志方法的运行速度似乎比字符串长度方法快了近两倍。如果该方法频繁调用或在代码的时间关键部分中使用,我认为这并不是微不足道的。 - CPerkins
2
请查看我的基准单元测试(可能也存在缺陷,因为我不是基准测试专家)。在大量运行(100,000,000次)后,在我的机器上速度从11秒提高到8秒,仅快了两倍左右。 - Jean
6
@CPerkins. 过早优化。你知道这一套。 - Michael Borgwardt
12
一些稍晚的添加:根据您是否希望“-”是一个数字,它可能无法正常处理负值。添加 Math.abs() 将修复这个问题。 - YingYang
显示剩余3条评论

319

对数是你的朋友:

int n = 1000;
int length = (int)(Math.log10(n)+1);

注意:此条件仅适用于n > 0。


3
这样做比使用我的变体更快或更好吗? - fnst
2
@Tom 为什么你会认为它很昂贵呢?有人可能会认为数学协处理器会执行它,所以它可能接近于加法的速度。即使Java现在没有使用协处理器,也可以做出这样的假设...(我们将忽略你更无知的暗示,即Java很慢,因为你可能对证据不感兴趣--或者如果你感兴趣,你可以去http://shootout.alioth.debian.org/自己找出答案) - Bill K
8
除非你所检查的值为0,否则函数会正常运行,但会给出奇怪的结果(-2147483647)。Math.log10 API:“如果参数为正零或负零,则结果为负无穷大。” - mujimu
3
提出一种不涉及对象内存分配的方法,这对于最大化重用以避免垃圾回收收集是必要的。 - Michael Wojcik
你可以使用 Math.abs(n)。 (int) (Math.log10(Math.abs(n)) + 1); - Kirill Parfenov
显示剩余3条评论

181
最快的方法:分而治之。
假设您的范围是0到MAX_INT,那么您有1到10位数。您可以使用分而治之的方法来处理这个区间,每个输入最多需要4次比较。首先,您用一次比较将[1..10]划分为[1..5]和[6..10],然后将每个长度为5的区间用一次比较划分为一个长度为3的区间和一个长度为2的区间。长度为2的区间需要进行一次额外的比较(总共3次比较),长度为3的区间可以划分为一个长度为1的区间(解决方案)和一个长度为2的区间。因此,您需要3或4次比较。
没有除法,没有浮点运算,没有昂贵的对数运算,只有整数比较。
代码(长但快速):
if (n < 100000) { // 1 to 5
    if (n < 100) { // 1 or 2
        if (n < 10) return 1;
        return 2;
    }
    else { // 3, 4 or 5
        if (n < 1000) return 3;
        if (n < 10000) return 4;
        return 5;
    }
}
else { // 6 to 7
    if (n < 10000000) { // 6 or 7
        if (n < 1000000) return 6;
        return 7;
    }
    else { // 8, 9 or 10
        if (n < 100000000) return 8;
        if (n < 1000000000) return 9;
        return 10;
    }
}

基准测试(JVM预热后)- 请参考下面的代码以了解如何运行基准测试:
  1. 基准方法(使用String.length):2145毫秒
  2. log10方法:711毫秒 = 基准方法的3.02倍速度
  3. 重复除法:2797毫秒 = 基准方法的0.77倍速度
  4. 分而治之:74毫秒 = 基准方法的28.99倍速度
完整代码:
public static void main(String[] args) throws Exception {
    
    // validate methods:
    for (int i = 0; i < 1000; i++)
        if (method1(i) != method2(i))
            System.out.println(i);
    for (int i = 0; i < 1000; i++)
        if (method1(i) != method3(i))
            System.out.println(i + " " + method1(i) + " " + method3(i));
    for (int i = 333; i < 2000000000; i += 1000)
        if (method1(i) != method3(i))
            System.out.println(i + " " + method1(i) + " " + method3(i));
    for (int i = 0; i < 1000; i++)
        if (method1(i) != method4(i))
            System.out.println(i + " " + method1(i) + " " + method4(i));
    for (int i = 333; i < 2000000000; i += 1000)
        if (method1(i) != method4(i))
            System.out.println(i + " " + method1(i) + " " + method4(i));
    
    // work-up the JVM - make sure everything will be run in hot-spot mode
    allMethod1();
    allMethod2();
    allMethod3();
    allMethod4();
    
    // run benchmark
    Chronometer c;
    
    c = new Chronometer(true);
    allMethod1();
    c.stop();
    long baseline = c.getValue();
    System.out.println(c);
    
    c = new Chronometer(true);
    allMethod2();
    c.stop();
    System.out.println(c + " = " + StringTools.formatDouble((double)baseline / c.getValue() , "0.00") + " times as fast as baseline");
    
    c = new Chronometer(true);
    allMethod3();
    c.stop();
    System.out.println(c + " = " + StringTools.formatDouble((double)baseline / c.getValue() , "0.00") + " times as fast as baseline");
    
    c = new Chronometer(true);
    allMethod4();
    c.stop();
    System.out.println(c + " = " + StringTools.formatDouble((double)baseline / c.getValue() , "0.00") + " times as fast as baseline");
}


private static int method1(int n) {
    return Integer.toString(n).length();
}

private static int method2(int n) {
    if (n == 0)
        return 1;
    return (int)(Math.log10(n) + 1);
}

private static int method3(int n) {
    if (n == 0)
        return 1;
    int l;
    for (l = 0 ; n > 0 ;++l)
        n /= 10;
    return l;
}

private static int method4(int n) {
    if (n < 100000) {
        // 5 or less
        if (n < 100) {
            // 1 or 2
            if (n < 10)
                return 1;
            else
                return 2;
        } else {
            // 3 or 4 or 5
            if (n < 1000)
                return 3;
            else {
                // 4 or 5
                if (n < 10000)
                    return 4;
                else
                    return 5;
            }
        }
    } else {
        // 6 or more
        if (n < 10000000) {
            // 6 or 7
            if (n < 1000000)
                return 6;
            else
                return 7;
        } else {
            // 8 to 10
            if (n < 100000000)
                return 8;
            else {
                // 9 or 10
                if (n < 1000000000)
                    return 9;
                else
                    return 10;
            }
        }
    }
}


private static int allMethod1() {
    int x = 0;
    for (int i = 0; i < 1000; i++)
        x = method1(i);
    for (int i = 1000; i < 100000; i += 10)
        x = method1(i);
    for (int i = 100000; i < 1000000; i += 100)
        x = method1(i);
    for (int i = 1000000; i < 2000000000; i += 200)
        x = method1(i);
    
    return x;
}

private static int allMethod2() {
    int x = 0;
    for (int i = 0; i < 1000; i++)
        x = method2(i);
    for (int i = 1000; i < 100000; i += 10)
        x = method2(i);
    for (int i = 100000; i < 1000000; i += 100)
        x = method2(i);
    for (int i = 1000000; i < 2000000000; i += 200)
        x = method2(i);
    
    return x;
}

private static int allMethod3() {
    int x = 0;
    for (int i = 0; i < 1000; i++)
        x = method3(i);
    for (int i = 1000; i < 100000; i += 10)
        x = method3(i);
    for (int i = 100000; i < 1000000; i += 100)
        x = method3(i);
    for (int i = 1000000; i < 2000000000; i += 200)
        x = method3(i);
    
    return x;
}

private static int allMethod4() {
    int x = 0;
    for (int i = 0; i < 1000; i++)
        x = method4(i);
    for (int i = 1000; i < 100000; i += 10)
        x = method4(i);
    for (int i = 100000; i < 1000000; i += 100)
        x = method4(i);
    for (int i = 1000000; i < 2000000000; i += 200)
        x = method4(i);
    
    return x;
}

再次进行基准测试:
1. 基准方法(使用String.length):2145毫秒 2. log10方法:711毫秒,比基准方法快3.02倍 3. 重复除法:2797毫秒,比基准方法快0.77倍 4. 分而治之:74毫秒,比基准方法快28.99倍

编辑

在我写完基准测试之后,我偷偷瞄了一眼 Java 6 中的 Integer.toString 方法,发现它使用了:

final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                                  99999999, 999999999, Integer.MAX_VALUE };

// Requires positive x
static int stringSize(int x) {
    for (int i=0; ; i++)
        if (x <= sizeTable[i])
            return i+1;
}

我将其与我的分治解决方案进行了基准测试:
  1. 分治:104毫秒
  2. Java 6解决方案 - 迭代和比较:406毫秒
我的解决方案大约比Java 6解决方案快4倍。

10
看起来很不错。你可以使用 ?: 运算符使其更加简洁,以获得更多的接受度。 - André Pareis
97
讨论过早优化。 - Gordon Gustafson
3
我喜欢它!用switch块代替这么多嵌套的if-else怎么样? - Kebman
2
我没有意识到所有这些if else语句会比将int转换为String然后调用.length快那么多。+1 - Ogen
19
使用三元运算符,可以将其缩减到101个字符:n<100000?n<100?n<10?1:2:n<1000?3:n<10000?4:5:n<10000000?n<1000000?6:7:n<100000000?8:n<1000000000?9:10 - jgawrych
显示剩余5条评论

13

对于你的基准测试,我有两点评论:Java是一个复杂的环境,涉及即时编译和垃圾回收等问题,为了公平比较,每当我运行基准测试时,我总是:(a)将两个测试用例封装在一个循环中,按顺序运行5或10次。经常情况下,第二遍循环的运行时间与第一遍循环的运行时间截然不同。(b) 在每个“方法”之后,我使用System.gc()尝试触发垃圾回收。否则,第一个方法可能会生成一堆对象,但不足以强制进行垃圾回收,然后第二个方法创建了一些对象,堆被耗尽,垃圾回收开始运行。接着,第二种方法就要“负责”清理第一种方法留下的垃圾,这非常不公平!

也就是说,在这个例子中,上述两点都没有作出重大的差异。

无论是加入还是不加入那些修改,我得到的结果与你的结果非常不同。当我运行时,toString方法给出的运行时间为6400到6600毫秒,而日志方法则需要20000到20400毫秒。日志方法并没有比toString方法稍微快一点,而是慢了三倍。

请注意,这两种方法涉及非常不同的成本,所以这并不完全令人震惊:toString方法将创建许多临时对象,这些对象必须被清理,而日志方法需要更强烈的计算。因此,在内存较少的机器上,toString方法可能需要更多的垃圾回收轮次,而在处理器速度较慢的机器上,日志的额外计算工作可能更加痛苦。

我还尝试了第三种方法。我编写了这个小函数:

static int numlength(int n)
{
    if (n == 0) return 1;
    int l;
    n=Math.abs(n);
    for (l=0;n>0;++l)
        n/=10;
    return l;           
}

在我的计算机上,该方法的运行时间为1600到1900毫秒,少于toString方法的三分之一,以及log方法的十分之一。

如果你有一个广泛的数字范围,你可以通过从1,000或1,000,000开始除法来减少循环次数,从而进一步提高速度。我没有尝试过这种方法。


你尝试过改变输入吗?否则,热点虚拟机可能会对此图进行优化,导致错误的基准测试结果,因为它每次都返回相同的预计算内容。 - user187676
请注意,Math.abs() 对于 Integer.MIN_VALUE 不起作用,在这种情况下该方法将返回0。 - nmatt

12

我目前无法留下评论,因此我会发布一个单独的答案。

基于对数的解决方案无法正确计算非常大的长整数的位数,例如:

long n = 99999999999999999L;

// correct answer: 17
int numberOfDigits = String.valueOf(n).length();

// incorrect answer: 18
int wrongNumberOfDigits = (int) (Math.log10(n) + 1); 

基于对数的解法计算大整数位数时出现错误


尝试使用(int)(Math.log10(n+j))代替,其中j为10 - (n - n/10*10)。 - Erick Stone

12

使用Java

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

在开头使用 import java.lang.Math.*;

使用C语言

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

在开头使用include math.h


2
只是提醒一下,如果“the_integer”为“0”,结果将为无穷大,所以请检查一下。 - user187676
请注意,Math.abs() 不适用于 Integer.MIN_VALUE - nmatt
为什么要使用floor函数?我认为这并不必要,您能否解释一下?即使需要加上+1来获取实际数字计数,我认为floor也是不必要的。 - PerracoLabs

9

由于一个整数在十进制下的位数只是1 + 截断(log10(数字)),因此你可以这样做:

public class Test {

    public static void main(String[] args) {

        final int number = 1234;
        final int digits = 1 + (int)Math.floor(Math.log10(number));

        System.out.println(digits);
    }
}

编辑: 上次编辑只是修改了代码示例,但未修改描述。


很酷。但我认为它需要 abs(number) 以及“0”也是特殊情况,对吗? - DmitryK
是的。如果你需要考虑符号,你需要做类似于 1 + (int)Math.floor(Math.log10(Math.abs(number))) + ((number < 0)? 1 : 0) 的操作。 - Dirk
5
Math.floor 有点多余,因为转换为 int 会自动向下取整。 - CompuChip
@DmitryK @Dirk:请注意Math.abs()不能处理Integer.MIN_VALUE - nmatt

9

另一种字符串方法。简短而甜美-适用于任何整数n

int length = ("" + n).length();

1
仅适用于正整数n和零。可以使用("" + Math.abs(n))。length()来获取负整数的长度。 - ThisClark
1
请注意,Math.abs() 不适用于 Integer.MIN_VALUE - nmatt
("" + n).replace("-", "").length(); // prevent negatives with replace - ThisClark

5

为了适应长达9,223,372,036,854,775,807的数字,Marian的解决方案进行了调整,以防有人想要复制并粘贴它。在我编写此程序时,数字最多只有10000,因此我为它们创建了一个特定的分支。无论如何,这不会产生明显的差异。

public static int numberOfDigits (long n) {     
    // Guessing 4 digit numbers will be more probable.
    // They are set in the first branch.
    if (n < 10000L) { // from 1 to 4
        if (n < 100L) { // 1 or 2
            if (n < 10L) {
                return 1;
            } else {
                return 2;
            }
        } else { // 3 or 4
            if (n < 1000L) {
                return 3;
            } else {
                return 4;
            }
        }           
    } else  { // from 5 a 20 (albeit longs can't have more than 18 or 19)
        if (n < 1000000000000L) { // from 5 to 12
            if (n < 100000000L) { // from 5 to 8
                if (n < 1000000L) { // 5 or 6
                    if (n < 100000L) {
                        return 5;
                    } else {
                        return 6;
                    }
                } else { // 7 u 8
                    if (n < 10000000L) {
                        return 7;
                    } else {
                        return 8;
                    }
                }
            } else { // from 9 to 12
                if (n < 10000000000L) { // 9 or 10
                    if (n < 1000000000L) {
                        return 9;
                    } else {
                        return 10;
                    }
                } else { // 11 or 12
                    if (n < 100000000000L) {
                        return 11;
                    } else {
                        return 12;
                    }
                }
            }
        } else { // from 13 to ... (18 or 20)
            if (n < 10000000000000000L) { // from 13 to 16
                if (n < 100000000000000L) { // 13 or 14
                    if (n < 10000000000000L) { 
                        return 13;
                    } else {
                        return 14;
                    }
                } else { // 15 or 16
                    if (n < 1000000000000000L) {
                        return 15;
                    } else {
                        return 16;
                    }
                }
            } else { // from 17 to ...¿20?
                if (n < 1000000000000000000L) { // 17 or 18
                    if (n < 100000000000000000L) {
                        return 17;
                    } else {
                        return 18;
                    }
                } else { // 19? Can it be?
                    // 10000000000000000000L is'nt a valid long.
                    return 19;
                }
            }
        }
    }
}

1
这个问题的标题应该改为“如何获取int/long中数字的位数?”(并添加'long'标签)吗? - J.A.I.L.

4

我看到一些人使用字符串库甚至使用Integer类。这并没有什么问题,但是获取数字位数的算法并不复杂。在此示例中,我使用了long类型,但使用int类型同样也能很好地工作。

 private static int getLength(long num) {

    int count = 1;

    while (num >= 10) {
        num = num / 10;
        count++;
    }

    return count;
}

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