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

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

当前回答

理想情况下,一个整数除以10的倍数将返回位数,只要该整数不为零。这样一个简单的方法可以创建如下所示。

public static int getNumberOfDigits(int number) {
    int numberOfDigits = 0;                
    while(number != 0) {
        number /= 10;
        numberOfDigits++;
    }
    
    return numberOfDigits;
}

其他回答

Marian的解决方案适用于长类型数字(高达9,223,372,036,854,775,807),以防有人想要复制和粘贴它。 在程序中,我写了这个,因为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;
                }
            }
        }
    }
}

我在看了Integer.java源代码后写了这个函数。

private static int stringSize(int x) {
    final int[] sizeTable = {9, 99, 999, 9_999, 99_999, 999_999, 9_999_999,
            99_999_999, 999_999_999, Integer.MAX_VALUE};
    for (int i = 0; ; ++i) {
        if (x <= sizeTable[i]) {
            return i + 1;
        }
    }
}

最快的方法:分而治之。

Assuming your range is 0 to MAX_INT, then you have 1 to 10 digits. You can approach this interval using divide and conquer, with up to 4 comparisons per each input. First, you divide [1..10] into [1..5] and [6..10] with one comparison, and then each length 5 interval you divide using one comparison into one length 3 and one length 2 interval. The length 2 interval requires one more comparison (total 3 comparisons), the length 3 interval can be divided into length 1 interval (solution) and a length 2 interval. So, you need 3 or 4 comparisons.

没有除法,没有浮点运算,没有昂贵的对数,只有整数比较。

代码(长但快):

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

基准测试(在JVM预热之后)——查看下面的代码以了解基准测试是如何运行的:

基线方法(使用String.length): 2145毫秒 Log10方法:711ms = 3.02次 和基线一样快 重复除:2797ms = 0.77次 和基线一样快 分治:74ms = 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;
}

基准:

基线方法(String.length): 2145ms Log10方法:711ms =基线速度的3.02倍 重复除:2797ms =基线速度的0.77倍 分治:74毫秒= 28.99倍的基线速度


Edit

在我写完基准测试之后,我偷偷地看了一下Integer。toString来自Java 6,我发现它使用:

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

我以我的分治方案为基准:

分治法:104毫秒 Java 6解决方案-迭代和比较:406ms

我的速度大约是Java 6解决方案的4倍。

简单的解决方案:

public class long_length {
    long x,l=1,n;
    for (n=10;n<x;n*=10){
        if (x/n!=0){
            l++;
        }
    }
    System.out.print(l);
}

Two comments on your benchmark: Java is a complex environment, what with just-in-time compiling and garbage collection and so forth, so to get a fair comparison, whenever I run a benchmark, I always: (a) enclose the two tests in a loop that runs them in sequence 5 or 10 times. Quite often the runtime on the second pass through the loop is quite different from the first. And (b) After each "approach", I do a System.gc() to try to trigger a garbage collection. Otherwise, the first approach might generate a bunch of objects, but not quite enough to force a garbage collection, then the second approach creates a few objects, the heap is exhausted, and garbage collection runs. Then the second approach is "charged" for picking up the garbage left by the first approach. Very unfair!

也就是说,上述两种方法在本例中都没有产生显著差异。

不管有没有这些修改,我得到的结果和你完全不同。当我运行这个时,是的,toString方法给出的运行时间为6400到6600 millis,而log方法给出的运行时间为20,000到20,400 millis。对数方法对我来说不是稍微快一点,而是慢了3倍。

请注意,这两种方法涉及非常不同的代价,所以这并不完全令人震惊:toString方法将创建许多必须清理的临时对象,而log方法需要更密集的计算。因此,可能区别在于,在内存较少的机器上,toString需要更多的垃圾收集回合,而在处理器较慢的机器上,额外的log计算将更加痛苦。

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

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方法的1/3,log方法的1/10。

如果您的数字范围很广,您可以通过开始除以1000或1,000,000来进一步加快速度,以减少循环的次数。我还没玩过。