比较两个相同的“字面”浮点数是否相等是错误的?

huangapple go评论61阅读模式
英文:

Is comparing two same "literal" float numbers for equality wrong?

问题

这个问题在某种程度上与语言无关,但代码是用Java编写的。

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

我相当确信这些数字将完全相等(嗯,因为它们在二进制中必须相等 —— 怎么可能完全相同的事物导致两个不同的二进制数呢?!),但我想要确定一下。

案例1:

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

案例2:

class Movie {
    String rating; // 由于某种原因,类型是String
}
void test2() {
    movie1.rating = "4.7";
    movie2.rating = "4.7";

    float f1 = Float.parseFloat(movie1.rating);
    float f2 = Float.parseFloat(movie2.rating);

    print(f1 == f2);
}

在这两种情况下,表达式 f1 == f2 应该得到 true。我对吗?如果它们具有相同的字面浮点或字符串值,我能安全地进行相等性比较吗?

英文:

This question is kind of language-agnostic but the code is written in Java.

We have all heard that comparing floating-point numbers for equality is generally wrong. But what if I wanted to compare two exact same literal float values (or strings representing exact same literal values converted to floats)?

I'm quite sure that the numbers will be exactly equal (well, because they must be equal in binary—how can the exact same thing result in two different binary numbers?!) but I wanted to be sure.

<!-- language: lang-java -->

Case 1:

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

Case 2:

class Movie {
    String rating; // for some reason the type is String
}
void test2() {
    movie1.rating = &quot;4.7&quot;;
    movie2.rating = &quot;4.7&quot;;

    float f1 = Float.parse(movie1.rating);
    float f2 = Float.parse(movie2.rating);

    print(f1 == f2);
}

In both situations, the expression f1 == f2 should result in true. Am I right? Can I safely compare ratings for equality if they have the same literal float or string values?

答案1

得分: 1

是的。相同的编译时常量会被一致地求值。

如果你思考一下,它们必须是相同的,因为只有一个编译器,它会将文字转换为其浮点表示,并且是确定性的。

英文:

Yes. Compile time constants that are the same are evaluated consistently.

If you think about it, they must be the same, because there’s only one compiler and it converts literals to their floating point representation deterministically.

答案2

得分: 1

有一个经验法则适用于所有编程经验法则(经验法则的经验法则):

它们过于简化,如果过于追求,将导致愚蠢的决策。如果你不完全理解经验法则背后的意图,你会搞砸的。也许经验法则仍然是净积极的(不经思考地应用它会使事情变得更好,而不是更糟),但它会造成损害,在任何情况下都不能用作辩论的论点。

所以,考虑到这一点,很明显,没有必要提出这个问题:

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

答案是极其明显的:当然不是。它不总是不好,因为经验法则基本上根据定义,如果不是根据常识,从来没有绝对的适用。

那么让我们来分析一下。

为什么有一个经验法则,不应该用==来比较浮点数?

你的问题暗示你已经知道了这一点:因为对IEEE754概念表示的浮点数进行任何数学运算,比如Java的doublefloat,是不精确的(与诸如Java的BigDecimal这样的概念相对,它是精确的*)。

当面对一个经验法则时,你应该总是做的事情是,一旦理解了经验法则的存在原因,并意识到它不适用于你的情况,就彻底忽略它。

也许你的问题归结为:我认为我理解了这个经验法则,但也许我漏掉了一些东西;除了'浮点数运算引入了小偏差,破坏了==比较'这一点不适用于这种情况之外,还有其他我不知道的关于这个经验法则的原因吗?

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

*)但是BigDecimal也有它自己的相等性问题,例如:两个表示相同数学数的BigDecimal对象精确吗,但配置为以不同的比例呈现?这取决于你是否认为它们是数值,还是表示精确小数点数的对象,以及包括如何呈现它以及在明确要求这样做时如何舍入的一些元属性。值得注意的是,BigDecimalequals实现必须做出一个选择,在两个同样有效的解释中选择何为相等,它选择了'我代表一个数',而不是'我代表一个带有一些元数据的数'。在所有的JPA/Hibernate堆栈中都存在着同样的选择困境:一个JPA对象是代表‘数据库中的一行’(因此,相等性仅由主键值定义,如果尚未保存,两个对象不能相等,甚至不能相等于其自身,除非是相同的引用标识),还是代表行代表的东西,例如一个学生,而不是‘代表学生的数据库中的一行’,在这种情况下,unid是唯一一个不重要的身份字段,其他所有字段(姓名、出生日期、社会安全号码等)都重要。相等性很难。

英文:

There's a rule of thumb that you should apply to all programming rules of thumb (rule of thumbs?):

They are oversimplified, and will result in boneheaded decision making if pushed too far. IF you do not fully -grok- the intent behind the rule of thumb, you will mess up. Perhaps the rule of thumb remains a net positive (applying it without thought will improve things more than it will make them worse), but it will cause damage, and in any case it cannot be used as an argument in a debate.

So, with that in mind, clearly, there is no point in asking the question:

"Giving that the rule of thumb 'do not use == to compare floats' exists, is it ALWAYS bad?".

The answer is the extremely obvious: Duh, no. It's not ALWAYS bad, because rules of thumb pretty much by definition, if not by common sense, never ALWAYS apply.

So let's break it down then.

WHY is there a rule of thumb that you shouldn't == compare floats?

Your question suggests you already know this: It's because doing any math on floating points as represented by IEEE754 concepts such as java's double or float are inexact (vs. concepts like java's BigDecimal, which is exact *).

Do what you should always do when faced with a rule of thumb that, upon grokking why the rule of thumb exists and realizing it does not apply to your scenario: Completely ignore it.

Perhaps your question boils down to: I THINK I grok the rule of thumb, but perhaps I'm missing something; aside from the 'floating point math introduces small deviations which mess up == comparison', which does not apply to this case, are there any other reasons for this rule of thumb that I am not aware of?

In which case, my answer is: As far as I know, no.

*) But BigDecimal has its own equality problems, such as: Are two BigDecimal objects that represent the same mathematical number precisely, but which are configured to render at a different scale 'equal'? That depends on whether your viewpoint is that they are numbers or objects representing an exact decimal point number along with some meta properties including how to render it and how to round things if explicitly asked to do so. For what it is worth, the equals implementation of BD, which has to make a sophie's choice and choose between 2 equally valid interpretations of what equality means, chooses 'I represent a number', not 'I represent a number along with a bunch of metadata'. The same sophie's choice exists in all JPA/Hibernate stacks: Does a JPA object represent 'a row in the database' (thus equality being defined solely by the primary key value, and if not saved yet, two objects cannot be equal, not even to itself, unless the same reference identity), or does it represent the thing that the row represents, e.g. a student, and not 'a row in the DB that represents a student', in which case unid is the one field that does NOT matter for identity, and all the others (name, birthdate, social security number, etc) do. equality is hard.

答案3

得分: 0

可以这样比较浮点数。问题在于,即使将 4.7 转换为浮点数时它不是准确的 4.7,但它会被始终转换为相同的值。

一般来说,像这样比较浮点数并没有绝对的错误。但对于更复杂的数学运算,你可能想要使用 Math.round() 函数或者设置一个“相似性”差距范围,只有在这个范围内的两个浮点数才被视为“相同”。

固定小数点数也存在一定的任意性。例如:

1,000,000,001

1.000,000,000

要大。

这两个数字是否不同?这取决于你所需的精度。但对于大多数情况而言,这些数字在功能上是相同的。

英文:

Yes, you can compare floats like this. The thing is that even if 4.7 isn't 4.7 when converted to a float, it will be converted consistently to the same value.

In general it is not wrong per se to compare floats like this. But for more complex math, you might want to use Math.round() or set a "sameness" difference span that the two should be within to be counted as "the same".

There is also an arbitrariness to fixed point numbers. For instance

1,000,000,001

is bigger than

1.000,000,000

Are these two numbers different? It depends on the precision you need. But for most purposes, these numbers are functionally the same

答案4

得分: 0

这个问题与编程语言无关...

实际上,在这里不存在浮点数问题,答案完全取决于编程语言。

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

存在编程语言问题,因为文字常量如何映射到浮点数以及源代码文本如何映射到操作在不同编程语言中有所不同。例如,C语言2018标准6.4.4.2的第5节规定:

相同源代码形式的所有浮点常量<sup>77)</sup>应转换为具有相同值的相同内部格式。

脚注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进行比较,并得到不相等的结果。

虽然我所知道的实现中不会出现这种问题,但是在提出类似问题时,不管编程语言如何,询问某个规则是否始终成立都为可能出现异常情况留下了余地。

英文:

> This question is kind of language-agnostic…

Actually, there is no floating-point issue here, and the answer depends entirely on the language.

There is no floating-point issue because IEEE-754 is clear: Two floating-point datums (finite numbers, infinities, and/or NaNs) compare as equal if and only if they correspond to the same real number.

There are language issues because how literals are mapped to floating-point numbers and how source text is mapped to operations differs from language to language. For example, C 2018 6.4.4.2 5 says:

> All floating constants of the same source form<sup>77)</sup> shall convert to the same internal format with the same value.

And footnote 77 says:

> 1.23, 1.230, 123e-2, 123e-02, and 1.23L are all different source forms and thus need not convert to the same internal format and value.

Thus the C standard permits 1.23 == 1.230 to evaluate to false. (There are historical reasons this was permitted, leaving it as a quality-of-implementation issue.) If by “same” literal float value, you mean the exact same source text, then this problem does not occur in C; the exact same source text must produce the same floating-point value each time in a particular C implementation. However, this example teaches us to be cautious.

C also allows implementations flexibility in how floating-point operations are performed: It allows an implementation to use more than the nominal precision in evaluating expressions, and it allows using different precisions in different parts of the same expression. So 1./3. == 1./3. could evaluate to false.
Some languages, like Python, do not have a good formal specification and are largely silent about how floating-point operations are performed. It is conceivable a Python implementation could use excess precision available in processor registers to convert the source text 1.3 to a long double or similar type, then save it somewhere as a double, then convert the source text 1.3 to a long double, then retrieve the double to compare it to the long double still in registers and get a result indicating inequality.

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.

huangapple
  • 本文由 发表于 2020年8月29日 00:03:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/63637322.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定