我正在尝试确定Java中的double
machine 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
,似乎有一个较小的,即根据{{1}}:
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));
正如我们所看到的,我们有一个小于机器epsilon,加上1,产生的不是1,与定义相矛盾。
根据这个定义,机器epsilon普遍接受的值有什么问题?还是我错过了什么?我怀疑浮点数学的另一个深奥的方面,但我看不出我错在哪里......
编辑:感谢评论者,我终于明白了。我实际上使用了错误的定义! epsPred = 2.2204460492503128E-16
epsPred < eps? true
1.0 + epsPred == 1.0? false
计算到最小可表示的双倍的距离&gt; eps = Math.ulp(1.0)
,但是 - 这就是重点 - 1.0
不最小eps
x
,而不是两次该值:添加1.0 + x != 1.0
将向上舍入为1.0 + Math.nextAfter(eps/2)
。
答案 0 :(得分:21)
使用它的定义是最小的可表示的双值x,使得1.0 + x!= 1.0,就像在C / C ++中一样
这从来就不是定义,不是在Java中,也不是在C中而不是在C ++中。
定义是机器epsilon是一个与最大浮点数/双倍之间的距离大于一。
您的“定义”是wrong by a factor of nearly 2。
此外,缺少strictfp
只允许更大的指数范围,并且不应对epsilon的经验测量产生任何影响,因为这是从1.0
及其后继者计算的,每个都是其差异可用标准指数范围表示。
答案 1 :(得分:7)
我不确定你的实验方法/理论是否合理。 Math类的文档声明:
对于给定的浮点格式,特定实数值的ulp是包含该数值的两个浮点值之间的距离
ulp
方法的文档说:
double值的ulp是此浮点值与接下来幅度较大的double值之间的正距离
因此,如果你想要eps
最小的1.0 + eps != 1.0
值,那么你的eps通常应该小于 Math.ulp(1.0)
,因为至少对于任何值大于Math.ulp(1.0) / 2
,结果将被四舍五入。
我认为最小的这样的值将由Math.nextAfter(eps/2, 1.0)
给出。