Я использую следующую функцию для вычисления логарифмической базы 2 для целых чисел:
public static int log2(int n){
if(n <= 0) throw new IllegalArgumentException();
return 31 - Integer.numberOfLeadingZeros(n);
}
Оптимальная производительность?
Кто-нибудь знает готовую для этого функцию J2SE API?
UPD1 На мой взгляд , арифметика с плавающей запятой оказалась быстрее, чем целочисленная арифметика.
UPD2 По комментариям проведу более детальное расследование.
UPD3 Моя целочисленная арифметическая функция в 10 раз быстрее, чем Math.log (n) / Math.log (2).
java
performance
discrete-mathematics
logarithm
Nulldevice
источник
источник
Math.floor(Math.log(n)/Math.log(2))
, поэтому на самом деле это не вычисление логической базы 2!Ответы:
Если вы думаете об использовании чисел с плавающей запятой для помощи с целочисленной арифметикой, будьте осторожны.
Обычно я стараюсь по возможности избегать вычислений FP.
Операции с плавающей запятой неточны. Никогда нельзя знать наверняка, что будет
(int)(Math.log(65536)/Math.log(2))
оценивать. Например,Math.ceil(Math.log(1<<29) / Math.log(2))
на моем компьютере это 30, где математически должно быть ровно 29. Я не нашел значения x, где(int)(Math.log(x)/Math.log(2))
не работает (только потому, что есть только 32 «опасных» значения), но это не означает, что он будет работать так же на любом ПК.Обычный трюк здесь - использовать "эпсилон" при округлении. Вроде
(int)(Math.log(x)/Math.log(2)+1e-10)
никогда не должно подводить. Выбор этого «эпсилона» - задача нетривиальная.Дополнительная демонстрация с использованием более общей задачи - попытки реализовать
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. Не могли бы вы сказать это перед тестированием? Я точно не мог.
источник
strictfp
, нет?strictfp
кажется, на самом деле получил много дерьма за то, что был строгим. :-)return ((long)Math.log(x) / (long)Math.log(base));
решения всех ошибок?Это функция, которую я использую для этого расчета:
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%) и почти в 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; // or throw exception 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; // or throw exception 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 );
источник
BSR
Инструкция x86 делает32 - numberOfLeadingZeros
, но не определена для 0, поэтому (JIT) компилятор должен проверять ненулевое значение, если он не может доказать, что это не обязательно. Введены расширения набора инструкций BMI (Haswell и новее)LZCNT
, которые полностью реализуютсяnumberOfLeadingZeros
точно, в одной инструкции. Оба они имеют задержку в 3 цикла, по 1 на пропускную способность цикла. Поэтому я абсолютно рекомендую использоватьnumberOfLeadingZeros
, потому что это упрощает создание хорошей JVM. (Одна странность в том,lzcnt
что он имеет ложную зависимость от старого значения регистра, который он перезаписывает.)Пытаться
Math.log(x) / Math.log(2)
источник
вы можете использовать личность
так что это применимо для log2.
log[10]x log[2]x = ---------- log[10]2
просто подключите это к методу java Math log10 ....
http://mathforum.org/library/drmath/view/55565.html
источник
Почему нет:
public static double log2(int n) { return (Math.log(n) / Math.log(2)); }
источник
В библиотеках гуавы есть функция:
Поэтому я предлагаю его использовать.
источник
Некоторые случаи просто работали, когда я использовал Math.log10:
public static double log2(int n) { return (Math.log10(n) / Math.log10(2)); }
источник
Чтобы добавить к ответу 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); }
источник
добавим:
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
источник
Чтобы вычислить логарифмическую базу 2 из n, можно использовать следующее выражение:
double res = log10(n)/log10(2);
источник