我试图确定 Java 中的double
机器 epsilon,使用它的定义是最小的可表示double
值x
,1.0 + x != 1.0
就像在 C/C++ 中一样。根据维基百科,这台机器 epsilon 等于2^-52
(52 是double
尾数位数 - 1)。
我的实现使用该Math.ulp()
功能:
double eps = Math.ulp(1.0);
System.out.println("eps = " + eps);
System.out.println("eps == 2^-52? " + (eps == Math.pow(2, -52)));
结果是我所期望的:
eps = 2.220446049250313E-16
eps == 2^-52? true
到目前为止,一切都很好。但是,如果我检查给定eps
的值确实是最小 x
的1.0 + x != 1.0
,那么似乎有一个更小的值,也就是 double
根据Math.nextAfter()
:
double epsPred = Math.nextAfter(eps, Double.NEGATIVE_INFINITY);
System.out.println("epsPred = " + epsPred);
System.out.println("epsPred < eps? " + (epsPred < eps));
System.out.println("1.0 + epsPred == 1.0? " + (1.0 + epsPred == 1.0));
产生:
epsPred = 2.2204460492503128E-16
epsPred < eps? true
1.0 + epsPred == 1.0? false
正如我们所看到的,我们有一个小于机器的 epsilon,它加到 1 后得到的不是 1,这与定义相矛盾。
那么根据这个定义,机器 epsilon 的普遍接受值有什么问题呢?还是我错过了什么?我怀疑浮点数学的另一个深奥方面,但我看不出我哪里出错了......
编辑:感谢评论者,我终于明白了。我实际上使用了错误的定义!eps = Math.ulp(1.0)
计算到最小可表示的 double > 的距离1.0
,但是——这就是关键——这不是eps
最小的,而是该值的两倍:加法四舍五入到.x
1.0 + x != 1.0
1.0 + Math.nextAfter(eps/2)
1.0 + eps