如果你考虑使用浮点数来辅助整数算术,你必须小心谨慎。
我通常尽可能避免使用浮点计算。
浮点运算并不是精确的。你永远无法确定 (int)(Math.log(65536)/Math.log(2))
的值是多少。例如,在我的PC上,Math.ceil(Math.log(1<<29) / Math.log(2))
的值是30,但根据数学计算它应该是29。我没有发现任何使得 (int)(Math.log(x)/Math.log(2))
失败的x的值(因为只有32个“危险”值),但这并不意味着它在任何PC上都能够以同样的方式工作。
通常的技巧是在四舍五入时使用“epsilon”。例如:(int)(Math.log(x)/Math.log(2)+1e-10)
应该不会失败。选择“epsilon”的值并不是一项微不足道的任务。
更多的演示可以通过尝试实现 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之间的epsilon。你在测试之前能提前告诉我吗?当然不可能。
Math.floor(Math.log(n)/Math.log(2))
,因此它并没有真正计算以2为底的对数! - Dori