【问题标题】:Is comparing two same "literal" float numbers for equality wrong?比较两个相同的“文字”浮点数是否相等是错误的?
【发布时间】:2020-12-17 14:19:10
【问题描述】:

这个问题与语言无关,但代码是用 Java 编写的。

我们都听说过比较浮点数是否相等通常是错误的。但是,如果我想比较两个完全相同的文字浮点值(或表示完全相同的文字值转换为浮点数的字符串)怎么办?

我很确定这些数字会完全相等(嗯,因为它们必须在二进制中是相等的——完全相同的东西怎么会导致两个不同的二进制数?!)但我想确定一下。

案例一:

void test1() {
    float f1 = 4.7;
    float f2 = 4.7;
    print(f1 == f2);
}

案例 2:

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);
}

在这两种情况下,表达式f1 == f2 都应该生成true。我对吗?如果 ratings 具有相同的文字浮点或字符串值,我可以安全地比较它们是否相等?

【问题讨论】:

  • “比较浮点数的相等性通常是错误的。”是错误的夸大其词。学习者指南,但不是真理。

标签: java floating-point precision floating-accuracy ieee-754


【解决方案1】:

您应该将一条经验法则应用于所有编程经验法则(经验法则?):

它们过于简单化,如果推得太远,会导致做出愚蠢的决策。 如果你没有完全理解经验法则背后的意图,你会搞砸的。也许经验法则仍然是积极的(不加思索地应用它会使事情变得更糟,而不是让事情变得更糟),但它会造成损害,并且无论如何它不能用作辩论中的论据。

因此,考虑到这一点,很明显,问这个问题是没有意义的:

“鉴于存在‘不要使用 == 来比较浮点数’的经验法则,它总是很糟糕吗?”。

答案非常明显:呃,不。这并不总是很糟糕,因为根据定义,如果不是根据常识,经验法则几乎永远不会适用。

那么让我们分解一下吧。

为什么有一条经验法则不应该 == 比较浮点数?

您的问题表明您已经知道这一点:这是因为对 IEEE754 概念(例如 java 的 doublefloat)所表示的浮点进行任何数学运算都是不精确的(与 java 的 BigDecimal 等概念相比,这是精确的 * )。

当遇到经验法则时,做您应该始终做的事情,在了解经验法则为什么存在并意识到它不适用于您的场景时:完全忽略它。

也许您的问题可以归结为:我认为我了解经验法则,但也许我遗漏了一些东西;除了不适用于这种情况的“浮点数学引入了小的偏差,这会导致比较混乱”之外,还有其他我不知道的经验法则的原因吗?

在这种情况下,我的回答是:据我所知,没有。

*) 但是 BigDecimal 有其自身的相等问题,例如:两个 BigDecimal 对象是否精确地表示相同的数学数字,但配置为以不同的比例呈现“相等”?这取决于您的观点是它们是代表精确小数点数字的数字还是对象以及一些元属性,包括如何渲染它以及如果明确要求这样做时如何舍入。对于它的价值,BD 的equals 实现必须做出一个苏菲的选择,并在 2 个同等有效的平等含义解释之间进行选择,选择“我代表一个数字”,而不是“我代表一个数字和一个一堆元数据'。所有 JPA/Hibernate 堆栈中都存在相同的 sophie 选择:JPA 对象是否表示“数据库中的一行”(因此相等性仅由主键值定义,如果尚未保存,则两个对象不能相等,甚至到它自己,除非相同的引用标识),或者它是否代表该行所代表的东西,例如一个学生,而不是“代表学生的数据库中的一行”,在这种情况下,unid 是与身份无关的一个字段,而所有其他字段(姓名、出生日期、社会安全号码等)都可以。平等很难。

【讨论】:

    【解决方案2】:

    是的。一致地评估相同的编译时间常数。

    如果你仔细想想,它们一定是相同的,因为只有一个编译器,它会确定性地将文字转换为它们的浮点表示。

    【讨论】:

      【解决方案3】:

      是的,您可以像这样比较浮点数。问题是,即使 4.7 在转换为浮点数时不是 4.7,它也会一致地转换为相同的值。

      一般来说,像这样比较浮点数本身并没有错误。但对于更复杂的数学,您可能需要使用 Math.round() 或设置两者应在其中的“相同”差异跨度以计为“相同”。

      定点数也有任意性。比如

      1,000,000,001
      

      大于

      1.000,000,000
      

      这两个数字不同吗?这取决于您需要的精度。但在大多数情况下,这些数字在功能上是相同的

      【讨论】:

        【解决方案4】:

        这个问题与语言无关……

        其实这里不存在浮点问题,答案完全取决于语言。

        不存在浮点问题,因为 IEEE-754 很明确:两个浮点数据(有限数、无穷大和/或 NaN)当且仅当它们对应于相同的实数时才比较相等。

        存在语言问题,因为文字如何映射到浮点数以及源文本如何映射到操作因语言而异。例如,C 2018 6.4.4.2 5 说:

        相同源形式的所有浮动常量77)应转换为具有相同值的相同内部格式。

        脚注 77 说:

        1.231.230123e-2123e-021.23L 都是不同的源形式,因此无需转换为相同的内部格式和值。

        因此,C 标准允许1.23 == 1.230 评估为假。 (这是允许的历史原因,将其作为实现质量问题。)如果“相同的”字面浮点值是指完全相同的源文本,那么在 C 中不会出现此问题;在特定的 C 实现中,每次完全相同的源文本必须产生相同的浮点值。然而,这个例子告诉我们要谨慎。

        C 还允许实现在如何执行浮点运算方面具有灵活性:它允许实现在计算表达式时使用超过标称精度,并且允许在同一表达式的不同部分使用不同的精度。所以1./3. == 1./3. 可以评估为假。 一些语言,如 Python,没有良好的正式规范,并且在很大程度上对浮点运算的执行方式保持沉默。可以想象,Python 实现可以使用处理器寄存器中可用的超额精度将源文本1.3 转换为long double 或类似类型,然后将其保存为double,然后将源文本1.3 转换为long double,然后检索 double 以将其与仍在寄存器中的 long double 进行比较,并获得指示不等式的结果。

        在我知道的实现中不会出现此类问题,但是,当提出这样的问题时,无论语言如何,询问规则是否始终成立,都会为可能的例外情况敞开大门。

        【讨论】:

          猜你喜欢
          • 2021-09-14
          • 1970-01-01
          • 2013-08-18
          • 2013-06-27
          • 1970-01-01
          • 1970-01-01
          • 2023-03-11
          • 2010-12-05
          相关资源
          最近更新 更多