Si está pensando en usar coma flotante para ayudar con la aritmética de enteros, debe tener cuidado.
Por lo general, trato de evitar los cálculos de FP siempre que sea posible.
Las operaciones de punto flotante no son exactas. Nunca se puede saber con certeza qué (int)(Math.log(65536)/Math.log(2))
evaluará. Por ejemplo, Math.ceil(Math.log(1<<29) / Math.log(2))
es 30 en mi PC donde matemáticamente debería ser exactamente 29. No encontré un valor para x donde (int)(Math.log(x)/Math.log(2))
falla (solo porque solo hay 32 valores "peligrosos"), pero eso no significa que funcionará de la misma manera en cualquier PC.
El truco habitual aquí es usar "epsilon" al redondear. Como (int)(Math.log(x)/Math.log(2)+1e-10)
nunca debería fallar. La elección de este "épsilon" no es una tarea trivial.
Más demostración, utilizando una tarea más general: tratar de implementar int log(int x, int base)
:
El código de prueba:
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);
}
}
}
Si usamos la implementación más directa de logaritmo,
static int log(int x, int base)
{
return (int) (Math.log(x) / Math.log(base));
}
esto 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 deshacerme completamente de los errores, tuve que agregar epsilon, que está entre 1e-11 y 1e-14. ¿Podría haber dicho esto antes de la prueba? Definitivamente no pude.