152

我使用以下函数计算整数的对数基数 2:

public static int log2(int n){
    if(n <= 0) throw new IllegalArgumentException();
    return 31 - Integer.numberOfLeadingZeros(n);
}

它是否具有最佳性能?

有人知道为此目的准备好的 J2SE API 函数吗?

UPD1 令 我惊讶的是,浮点运算似乎比整数运算更快。

UPD2 由于评论,我将进行更详细的调查。

UPD3 我的整数算术函数比 Math.log(n)/Math.log(2) 快 10 倍。

4

10 回答 10

97

这是我用于此计算的函数:

public static int binlog( int bits ) // returns 0 for bits=0
{
    int log = 0;
    if( ( bits & 0xffff0000 ) != 0 ) { bits >>>= 16; log = 16; }
    if( bits >= 256 ) { bits >>>= 8; log += 8; }
    if( bits >= 16  ) { bits >>>= 4; log += 4; }
    if( bits >= 4   ) { bits >>>= 2; log += 2; }
    return log + ( bits >>> 1 );
}

它比 Integer.numberOfLeadingZeros() (20-30%) 稍快,并且比基于 Math.log() 的实现快近 10 倍(jdk 1.6 x64):

private static final double log2div = 1.000000000001 / Math.log( 2 );
public static int log2fp0( int bits )
{
    if( bits == 0 )
        return 0; // or throw exception
    return (int) ( Math.log( bits & 0xffffffffL ) * log2div );
}

对于所有可能的输入值,这两个函数都返回相同的结果。

更新: Java 1.7 服务器 JIT 能够用基于 CPU 内在函数的替代实现替换一些静态数学函数。这些函数之一是 Integer.numberOfLeadingZeros()。因此,对于 1.7 或更新的服务器 VM,问题中的实现实际上比binlog上面的要快一些。不幸的是,客户端 JIT 似乎没有这种优化。

public static int log2nlz( int bits )
{
    if( bits == 0 )
        return 0; // or throw exception
    return 31 - Integer.numberOfLeadingZeros( bits );
}

此实现还为所有 2^32 个可能的输入值返回与我在上面发布的其他两个实现相同的结果。

以下是我的 PC(Sandy Bridge i7)上的实际运行时间:

JDK 1.7 32 位客户端虚拟机:

binlog:         11.5s
log2nlz:        16.5s
log2fp:        118.1s
log(x)/log(2): 165.0s

JDK 1.7 x64 服务器虚拟机:

binlog:          5.8s
log2nlz:         5.1s
log2fp:         89.5s
log(x)/log(2): 108.1s

这是测试代码:

int sum = 0, x = 0;
long time = System.nanoTime();
do sum += log2nlz( x ); while( ++x != 0 );
time = System.nanoTime() - time;
System.out.println( "time=" + time / 1000000L / 1000.0 + "s -> " + sum );
于 2010-07-22T04:05:18.287 回答
82

如果您正在考虑使用浮点来帮助进行整数运算,则必须小心。

我通常尽量避免 FP 计算。

浮点运算并不精确。你永远无法确定(int)(Math.log(65536)/Math.log(2))评估结果是什么。例如,Math.ceil(Math.log(1<<29) / Math.log(2))在我的 PC 上是 30,在数学上它应该正好是 29。我没有找到(int)(Math.log(x)/Math.log(2))失败的 x 值(只是因为只有 32 个“危险”值),但这并不意味着它会起作用在任何 PC 上都一样。

这里通常的技巧是在舍入时使用“epsilon”。喜欢(int)(Math.log(x)/Math.log(2)+1e-10)永远不应该失败。这个“epsilon”的选择不是一项简单的任务。

更多演示,使用更一般的任务 - 尝试实现int log(int x, int base)

测试代码:

static int pow(int base, int power) {
    int result = 1;
    for (int i = 0; i < power; i++)
        result *= base;
    return result;
}

private static void test(int base, int pow) {
    int x = pow(base, pow);
    if (pow != log(x, base))
        System.out.println(String.format("error at %d^%d", base, pow));
    if(pow!=0 && (pow-1) != log(x-1, base))
        System.out.println(String.format("error at %d^%d-1", base, pow));
}

public static void main(String[] args) {
    for (int base = 2; base < 500; base++) {
        int maxPow = (int) (Math.log(Integer.MAX_VALUE) / Math.log(base));
        for (int pow = 0; pow <= maxPow; pow++) {
            test(base, pow);
        }
    }
}

如果我们使用最直接的对数实现,

static int log(int x, int base)
{
    return (int) (Math.log(x) / Math.log(base));
}

这打印:

error at 3^5
error at 3^10
error at 3^13
error at 3^15
error at 3^17
error at 9^5
error at 10^3
error at 10^6
error at 10^9
error at 11^7
error at 12^7
...

为了完全消除错误,我必须添加介于 1e-11 和 1e-14 之间的 epsilon。你能在测试前告诉这个吗?我绝对不能。

于 2010-07-22T02:38:40.930 回答
46

尝试Math.log(x) / Math.log(2)

于 2010-07-22T01:09:43.213 回答
34

你可以使用身份

            log[a]x
 log[b]x = ---------
            log[a]b

所以这适用于 log2。

            log[10]x
 log[2]x = ----------
            log[10]2

只需将其插入 java Math log10 方法....

http://mathforum.org/library/drmath/view/55565.html

于 2010-07-22T01:11:46.283 回答
21

为什么不:

public static double log2(int n)
{
    return (Math.log(n) / Math.log(2));
}
于 2010-07-22T01:09:28.097 回答
9

番石榴库中有这个功能:

LongMath.log2()

所以我建议使用它。

于 2015-04-24T21:47:13.350 回答
9

当我使用 Math.log10 时,有些情况才有效:

public static double log2(int n)
{
    return (Math.log10(n) / Math.log10(2));
}
于 2019-02-19T11:39:34.523 回答
3

要添加到 x4u 答案,它为您提供数字二进制日志的下限,此函数返回数字二进制日志的 ceil:

public static int ceilbinlog(int number) // returns 0 for bits=0
{
    int log = 0;
    int bits = number;
    if ((bits & 0xffff0000) != 0) {
        bits >>>= 16;
        log = 16;
    }
    if (bits >= 256) {
        bits >>>= 8;
        log += 8;
    }
    if (bits >= 16) {
        bits >>>= 4;
        log += 4;
    }
    if (bits >= 4) {
        bits >>>= 2;
        log += 2;
    }
    if (1 << log < number)
        log++;
    return log + (bits >>> 1);
}
于 2016-03-11T13:43:08.930 回答
0

让我们添加:

int[] fastLogs;

private void populateFastLogs(int length) {
    fastLogs = new int[length + 1];
    int counter = 0;
    int log = 0;
    int num = 1;
    fastLogs[0] = 0;
    for (int i = 1; i < fastLogs.length; i++) {
        counter++;
        fastLogs[i] = log;
        if (counter == num) {
            log++;
            num *= 2;
            counter = 0;
        }
    }
}

来源:https ://github.com/pochuan/cs166/blob/master/ps1/rmq/SparseTableRMQ.java

于 2014-09-23T20:57:24.677 回答
-4

要计算 n 的以 2 为底的对数,可以使用以下表达式:

double res = log10(n)/log10(2);
于 2018-07-25T14:52:44.393 回答