Java: double machine epsilon 不是满足 1+x != 1 的最小 x 吗?
Java: Double machine epsilon is not the smallest x such that 1+x != 1?
我正在尝试确定 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
,似乎有一个更小的,又名上一个 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)
计算到最小可表示双精度 > 1.0
的距离,但是——这就是重点——eps
是 而不是 最小的 x
与 1.0 + x != 1.0
,而是该值的 两倍 :将 1.0 + Math.nextAfter(eps/2)
添加到 up 到 1.0 + eps
.
我不确定你的实验方法/理论是否合理。数学 class 的文档指出:
For a given floating-point format, an ulp of a specific real number value is the distance between the two floating-point values bracketing that numerical value
ulp
方法的文档说:
An ulp of a double value is the positive distance between this floating-point value and the double value next larger in magnitude
所以,如果您想要最小的 eps
值 1.0 + eps != 1.0
,您的 eps 通常应该 小于 Math.ulp(1.0)
,因为至少对于任何大于 Math.ulp(1.0) / 2
的值,结果都会向上舍入。
我认为最小的这样的值将由Math.nextAfter(eps/2, 1.0)
给出。
using the definition of it being the smallest representable double value x such that 1.0 + x != 1.0, just as in C/C++
这从来不是定义,在 Java 中没有,在 C 中没有,在 C++ 中也没有。
定义是机器epsilon是1和最小float/double大于1的距离
您的“定义”是wrong by a factor of nearly 2。
此外,缺少 strictfp
只允许更大的指数范围,不应对 epsilon 的经验测量有任何影响,因为它是根据 1.0
及其后继计算得出的,每个which 和 which 的差可以用标准指数范围来表示。
我正在尝试确定 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
,似乎有一个更小的,又名上一个 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)
计算到最小可表示双精度 > 1.0
的距离,但是——这就是重点——eps
是 而不是 最小的 x
与 1.0 + x != 1.0
,而是该值的 两倍 :将 1.0 + Math.nextAfter(eps/2)
添加到 up 到 1.0 + eps
.
我不确定你的实验方法/理论是否合理。数学 class 的文档指出:
For a given floating-point format, an ulp of a specific real number value is the distance between the two floating-point values bracketing that numerical value
ulp
方法的文档说:
An ulp of a double value is the positive distance between this floating-point value and the double value next larger in magnitude
所以,如果您想要最小的 eps
值 1.0 + eps != 1.0
,您的 eps 通常应该 小于 Math.ulp(1.0)
,因为至少对于任何大于 Math.ulp(1.0) / 2
的值,结果都会向上舍入。
我认为最小的这样的值将由Math.nextAfter(eps/2, 1.0)
给出。
using the definition of it being the smallest representable double value x such that 1.0 + x != 1.0, just as in C/C++
这从来不是定义,在 Java 中没有,在 C 中没有,在 C++ 中也没有。
定义是机器epsilon是1和最小float/double大于1的距离
您的“定义”是wrong by a factor of nearly 2。
此外,缺少 strictfp
只允许更大的指数范围,不应对 epsilon 的经验测量有任何影响,因为它是根据 1.0
及其后继计算得出的,每个which 和 which 的差可以用标准指数范围来表示。