Se você estiver pensando em usar ponto flutuante para ajudar na aritmética de números inteiros, tenha cuidado.
Eu costumo tentar evitar cálculos de FP sempre que possível.
As operações de ponto flutuante não são exatas. Você nunca pode ter certeza do que (int)(Math.log(65536)/Math.log(2))
avaliará. Por exemplo, Math.ceil(Math.log(1<<29) / Math.log(2))
é 30 no meu PC, onde matematicamente deveria ser exatamente 29. Não encontrei um valor para x em que (int)(Math.log(x)/Math.log(2))
falha (apenas porque existem apenas 32 valores "perigosos"), mas isso não significa que funcionará da mesma maneira. da mesma maneira em qualquer PC.
O truque usual aqui é usar "epsilon" ao arredondar. Como (int)(Math.log(x)/Math.log(2)+1e-10)
nunca deve falhar. A escolha deste "epsilon" não é uma tarefa trivial.
Mais demonstração, usando uma tarefa mais geral - tentando implementar int log(int x, int base)
:
O código de teste:
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);
}
}
}
Se usarmos a implementação mais direta do logaritmo,
static int log(int x, int base)
{
return (int) (Math.log(x) / Math.log(base));
}
isto imprime:
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
...
Para me livrar completamente dos erros, tive que adicionar o epsilon, que fica entre 1e-11 e 1e-14. Você poderia ter dito isso antes do teste? Eu definitivamente não podia.