Якщо ви думаєте про використання плаваючої точки для допомоги з цілою арифметикою, ви повинні бути обережними.
Зазвичай я намагаюся уникати розрахунків ПЗ, коли це можливо.
Операції з плаваючою комою не є точними. Ніколи не можеш точно знати, на що (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
...
Щоб повністю позбутися помилок, мені довелося додати епсилон, який знаходиться між 1е-11 та 1е-14. Чи могли б ви сказати це перед тестуванням? Я точно не міг.