提问人:Mahozad 提问时间:8/29/2020 最后编辑:Mahozad 更新时间:8/29/2020 访问量:756
比较两个相同的“字面”浮点数是否相等是错误的?
Is comparing two same "literal" float numbers for equality wrong?
问:
这个问题有点与语言无关,但代码是用 Java 编写的。
我们都听说过,比较浮点数是否相等通常是错误的。但是,如果我想比较两个完全相同的文字浮点值(或表示转换为浮点数的完全相同文字值的字符串)怎么办?
我很确定这些数字会完全相等(好吧,因为它们在二进制中必须相等——完全相同的事情怎么会导致两个不同的二进制数?!)但我想确定。
案例一:
void test1() {
float f1 = 4.7;
float f2 = 4.7;
print(f1 == f2);
}
案例二:
class Movie {
String rating; // for some reason the type is String
}
void test2() {
movie1.rating = "4.7";
movie2.rating = "4.7";
float f1 = Float.parse(movie1.rating);
float f2 = Float.parse(movie2.rating);
print(f1 == f2);
}
在这两种情况下,表达式都应生成 。我说得对吗?如果 s 具有相同的文字浮点值或字符串值,我可以安全地比较它们是否相等吗?f1 == f2
true
rating
答:
是的。对相同的编译时常量进行一致的计算。
如果你仔细想想,它们一定是相同的,因为只有一个编译器,它确定性地将文字转换为它们的浮点表示。
有一条经验法则应该应用于所有编程经验法则(经验法则?
它们过于简单化,如果推得太远,将导致愚蠢的决策。如果你没有完全理解经验法则背后的意图,你就会搞砸。也许经验法则仍然是一个净积极因素(不假思索地应用它会改善事情,而不是让事情变得更糟),但它会造成损害,无论如何它都不能用作辩论中的论据。
因此,考虑到这一点,显然,提出这个问题是没有意义的:
“鉴于经验法则'不要使用==来比较浮点数'存在,它总是不好的吗?”
答案非常明显:呃,不。这并不总是坏事,因为根据定义,经验法则,如果不是常识,也永远不会总是适用。
那么让我们分解一下。
为什么有一条经验法则,你不应该==比较浮点数?
你的问题表明你已经知道这一点:这是因为对IEEE754概念(如 java's or )表示的浮点进行任何数学运算都是不精确的(与 java 等概念相比,这是精确的 *)。double
float
BigDecimal
在摸索为什么存在经验法则并意识到它不适用于你的场景时,做你应该始终做的事情:完全忽略它。
也许你的问题可以归结为:我想我摸索了经验法则,但也许我错过了一些东西;除了不适用于这种情况的“浮点数学引入了小偏差,搞砸了==比较”之外,还有其他我不知道的经验法则吗?
在这种情况下,我的回答是:据我所知,没有。
*) 但是 BigDecimal 有其自身的相等问题,例如:两个 BigDecimal 对象精确地表示相同的数学数字,但配置为以不同的比例呈现“相等”吗?这取决于您的观点是它们是表示精确小数点的数字还是对象,以及一些元属性,包括如何呈现它以及如何在明确要求的情况下四舍五入。值得一提的是,BD的实现必须做出一个苏菲的选择,并在两种同样有效的平等解释之间进行选择,选择“我代表一个数字”,而不是“我代表一个数字和一堆元数据”。所有 JPA/Hibernate 堆栈中都存在相同的 sophie 选择:JPA 对象是否表示“数据库中的一行”(因此相等性仅由主键值定义,如果尚未保存,则两个对象不能相等,甚至不能相等,除非具有相同的引用标识),或者它是否表示行所代表的事物, 例如,一个学生,而不是“数据库中代表学生的一行”,在这种情况下,unid 是一个与身份无关的字段,而所有其他字段(姓名、出生日期、社会安全号码等)都很重要。equals
是的,你可以像这样比较浮点数。问题是,即使 4.7 在转换为浮点数时不是 4.7,它也会始终如一地转换为相同的值。
一般来说,像这样比较浮点数本身并没有错。但对于更复杂的数学运算,您可能希望使用 Math.round() 或设置两者应该在“相同”范围内的差异跨度,以计为“相同”。
定点数也存在任意性。例如
1,000,000,001
大于
1.000,000,000
这两个数字不同吗?这取决于您需要的精度。但在大多数情况下,这些数字在功能上是相同的
这个问题有点与语言无关......
实际上,这里没有浮点问题,答案完全取决于语言。
没有浮点问题,因为 IEEE-754 很清楚:当且仅当两个浮点基准面(有限数、无穷大和/或 NaN)对应于相同的实数时,它们才会相等。
存在语言问题,因为文本如何映射到浮点数,以及源文本如何映射到操作,因语言而异。例如,C 2018 6.4.4.2 5 说:
同一源形式的所有浮点常量77) 应转换为具有相同值的相同内部格式。
脚注 77 说:
1.23
、、和都是不同的源形式,因此不需要转换为相同的内部格式和值。1.230
123e-2
123e-02
1.23L
因此,C 标准允许计算结果为 false。(这是有历史原因的,这是被允许的,把它作为一个实施质量问题。如果“相同”的文字浮点值是指完全相同的源文本,那么这个问题在 C 中不会发生;在特定的 C 实现中,完全相同的源文本每次都必须生成相同的浮点值。然而,这个例子教会我们要谨慎。1.23 == 1.230
C 还允许实现灵活地执行浮点运算:它允许实现在计算表达式时使用超过标称精度的精度,并允许在同一表达式的不同部分使用不同的精度。所以可以评估为错误。
一些语言,如 Python,没有很好的形式规范,并且在很大程度上对浮点运算的执行方式保持沉默。可以想象,Python 实现可以使用处理器寄存器中可用的超高精度将源文本转换为或类似类型,然后将其保存为 a ,然后将源文本转换为 ,然后检索 将其与仍在寄存器中进行比较,并获得指示不等式的结果。1./3. == 1./3.
1.3
long double
double
1.3
long double
double
long double
This sort of issue does not occur in implementations I am aware of, but, when asking a question like this, asking whether a rule always holds, regardless of language, leaves the door open for possible exceptions.
评论