在 Java 中,Math.sqrt(x) 采用双精度值。您说 x 是这样的,x * x 低于Integer.MAX_VALUE。每个整数都可以用 double 完美表示 - java 中的 double 明确定义为带有 52 位尾数的 iEEE-754 样式的 double;因此,在 java 中,double 可以完美地表示 -2^52 和 +2^52 之间的所有整数值,这很容易涵盖所有 int 值(在 java 上定义为有符号 32 位),但是 它并未涵盖所有long 值。 (定义为有符号的 64 位;64 大于 52,所以不行)。
因此,x * x 在最终从 int 转换为 double 时不会丢失精度。然后,这个数字上的Math.sqrt() 将给出一个也可以完美表示为double 的结果(因为它是x,并且鉴于x*x 适合int,x 也必须适合) ,因此,是的,这将始终适用于所有 x。
但是,嘿,为什么不试一试,对吧?
public static void main(String[] args) {
int i = 1;
while (true) {
if (i * i < 0) break;
int j = (int) Math.sqrt(i * i);
if (i != j) System.out.println("Oh dear! " + i + " -> " + j);
i++;
}
System.out.println("Done at " + i);
}
> Done at 46341
因此通过详尽的尝试来证明这一点。
事实证明,不存在 - 任何long 值使得x * x 仍然适合(因此,是<2^63-1)具有x == (long) Math.sqrt(x * x); 的属性。这大概是因为在x*x,这个数字完全适合一个长整数,即使不是所有这么大的整数都可以。证明:
long p = 2000000000L;
for (; true; p++) {
long pp = p * p;
if (pp < 0) break;
long q = (long) Math.sqrt(pp);
if (q != p) System.out.println("PROBLEM: " + p + " -> " + q);
}
System.out.println("Abort: " + p);
> Abort: 3037000500
当然,如果存在任何不成立的数字,那么在这个高端范围内至少有一个。从 0 开始需要很长时间。
但是我们是否知道 sqrt 总是会返回一个完全平方的精确值,还是可能有点不准确?
我们应该 - 它是 java。与 C 不同,几乎所有东西都是“定义明确的”,如果 JVM 不能产生指定的确切答案,它就不能合法地称自己为一个。 Math.sqrt 文档提供的余地不足以让任何答案(除了 x 之外的任何答案都是合法实施),因此,是的,这是一个保证。
理论上,JVM 对浮点数有一些非常小的余地,strictfp 禁用,但是 [A] 更多的是使用 80 位寄存器来表示数字而不是 64,这不可能破坏这个假设,并且[B] 不久前出现了一个 java 标签问题,显示 strictfp 对任何硬件和任何 VM 版本都有任何影响,唯一可行的结果是 15 年前不可重现的事情。无论硬件或 VM 版本如何,我都非常有信心声明这将永远成立。