19

我试图确定 Java 中的double 机器 epsilon,使用它的定义是最小的可表示doublex1.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的值确实是最小 x1.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最小的,而是该值的两倍:加法四舍五入.x1.0 + x != 1.01.0 + Math.nextAfter(eps/2)1.0 + eps

4

2 回答 2

21

使用它的定义是最小的可表示双精度值 x 使得 1.0 + x != 1.0,就像在 C/C++ 中一样

这从来都不是定义,不是在 Java 中,也不是在 C 中,也不是在 C++ 中。

定义是机器 epsilon 是 1 和大于 1 的最小 float/double 之间的距离。

您的“定义”错误了近 2 倍

此外,不存在strictfp仅允许更大的指数范围,并且不应对 epsilon 的经验测量产生任何影响,因为它是根据1.0及其后继计算的,其中每个及其差异都可以用标准指数范围表示。

于 2015-02-26T13:21:35.113 回答
7

我不确定您的实验方法/理论是否合理。Math 类的文档指出:

对于给定的浮点格式,特定实数值的 ulp 是包围该数值的两个浮点值之间的距离

ulp方法的文档说:

double 值的 ulp 是该浮点值与幅度下一个更大的 double 值之间的正距离

所以,如果你想要这样的最小值,那么你eps1.0 + eps != 1.0eps 通常应该小于 Math.ulp(1.0),因为至少对于任何大于Math.ulp(1.0) / 2的值,结果都会被四舍五入。

我认为最小的这样的值将由 给出Math.nextAfter(eps/2, 1.0)

于 2015-02-26T13:21:31.437 回答