Это функция, которую я использую для этого расчета:
public static int binlog( int bits )
{
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%) и почти в 10 раз быстрее (jdk 1.6 x64), чем реализация на основе Math.log (), подобная этой:
private static final double log2div = 1.000000000001 / Math.log( 2 );
public static int log2fp0( int bits )
{
if( bits == 0 )
return 0;
return (int) ( Math.log( bits & 0xffffffffL ) * log2div );
}
Обе функции возвращают одинаковые результаты для всех возможных входных значений.
Обновление:
JIT-сервер Java 1.7 может заменить несколько статических математических функций альтернативными реализациями, основанными на встроенных функциях ЦП. Одна из таких функций - Integer.numberOfLeadingZeros (). Таким образом, с серверной виртуальной машиной версии 1.7 или новее реализация, подобная рассматриваемой, на самом деле немного быстрее, чем указанная binlog
выше. К сожалению, клиентская JIT, похоже, не имеет этой оптимизации.
public static int log2nlz( int bits )
{
if( bits == 0 )
return 0;
return 31 - Integer.numberOfLeadingZeros( bits );
}
Эта реализация также возвращает те же результаты для всех 2 ^ 32 возможных входных значений, что и две другие реализации, которые я опубликовал выше.
Вот фактическое время работы на моем ПК (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 );