连续值在内存中以近似方式表示,因此使用浮点数进行计算涉及到舍入误差。这些是位模式中微小的差异;因此,如果e和f是浮点数,则测试e==f是不安全的。
这是否正确?我已经使用
double
和float
的比较语句,并从未遇到过舍入问题。我从来没有在教科书上读到过类似的东西。虚拟机肯定会考虑到这一点吧?double
和float
的比较语句,并从未遇到过舍入问题。我从来没有在教科书上读到过类似的东西。虚拟机肯定会考虑到这一点吧?它是正确的。
由于浮点数值在内存中以有限数量的位表示的固有局限性,这是不可避免的。
例如,此程序会输出“false”:
public class Main {
public static void main(String[] args) {
double a = 0.7;
double b = 0.9;
double x = a + 0.1;
double y = b - 0.1;
System.out.println(x == y);
}
}
与其使用 '==' 进行精确比较,你通常会决定一个精度级别,并询问这些数是否“足够接近”:
System.out.println(Math.abs(x - y) < 0.0001);
是的,在二进制中精确表示0.1就像在十进制中精确表示1/3一样困难。
这是一条永恒的真理。有些数字不能用浮点数精确地表示。例如,考虑圆周率。如何在有限的存储空间内表示一个具有无限位数的数字呢?因此,在比较数字时,应检查它们之间的差异是否小于某个 epsilon。此外,还有几个类可以帮助您实现更高的精度,例如 BigDecimal 和 BigInteger。
这是正确的。请注意,Java与此无关,问题根源在于任何语言中的浮点数学。
你经常可以在课堂级别的问题中解决它,但在现实世界中不起作用。有时在课堂上也行不通。
很久以前在学校发生的一件事。一位入门班的老师布置了一道期末考试题目,对许多优秀学生来说都是一个难题——它不起作用,他们不知道为什么。(我作为实验室助手看到了这一点,我不在这个班上。)最后,一些人开始向我寻求帮助,一些探究揭示了问题:他们从未被教过浮点数学固有的不准确性。
现在,对于这个问题有两种基本方法,一种是暴力方法(在这种情况下偶然起作用,因为它每次都会犯同样的错误),另一种是更优雅的方法(会产生不同的错误,而且不起作用)。任何尝试优雅方法的人都会撞上一堵墙,却不知道为什么。我帮了他们一堆人,并加了一条评论解释原因,如果他有问题可以联系我。
当然,下个学期我从他那里听到了这件事,我基本上用一个简单的小程序打败了整个部门:
10 X = 3000000
20 X = X + 1
30 If X < X + 1 goto 20
40 Print "X = X + 1"
尽管该部门的每位教师都认为不可能,但这个程序一定会终止。3百万种子只是为了加快终止速度。(如果你不懂基础知识:这里没有任何花招,只是耗尽浮点数的精度。)
大多数 CPU(和计算机语言)使用 IEEE 754 浮点运算。使用这种表示法,有一些十进制数在该表示法中没有精确的表示,例如 0.1。因此,如果你将 1 除以 10,你不会得到一个精确的结果。当连续执行多个计算时,误差会累加。请在 Python 中尝试以下示例:
>>> 0.1
0.10000000000000001
>>> 0.1 / 7 * 10 * 7 == 1
False
这并不是数学上所期望的。
顺便说一下: 关于浮点数,一个常见的误解是,结果不精确,不能安全地进行比较。如果你真的只使用整数,那么这个误解是错误的。双精度和单精度浮点数与整数完全相同,也可以安全地进行比较。它们可以安全地用作循环计数器。