Java:双机epsilon不是最小的x,1 + x != 1?

2022-09-02 12:52:19

我试图确定Java中的机器epsilon,使用它是最小可表示值的定义,就像在C / C++一样。根据维基百科,这台机器epsilon等于(52是尾数位数- 1)。doubledoublex1.0 + x != 1.02^-52double

我的实现使用函数: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

目前为止,一切都好。但是,如果我检查给定的确实是最小的,那么似乎有一个较小的值,即根据epsx1.0 + x != 1.0doubleMath.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)1.0epsx1.0 + x != 1.01.0 + Math.nextAfter(eps/2)1.0 + eps


答案 1

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

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

定义是机器epsilon是1与最小浮子/大于1的双倍之间的距离。

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

此外,缺少仅允许更大的指数范围,并且不应对ε的经验测量产生任何影响,因为这是从及其后继者计算的,每个指数及其差值都可以用标准指数范围表示。strictfp1.0


答案 2

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

对于给定的浮点格式,特定实数值的 ulp 是将该数值括起来的两个浮点值之间的距离

该方法的文档说:ulp

双精度值的 ulp 是此浮点值与大小稍大的双精度值之间的正距离

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

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