测试浮点相等性。 (FE_FLOATING_POINT_EQUALITY)
Posted
技术标签:
【中文标题】测试浮点相等性。 (FE_FLOATING_POINT_EQUALITY)【英文标题】:Test for floating point equality. (FE_FLOATING_POINT_EQUALITY) 【发布时间】:2011-04-19 10:57:07 【问题描述】:我在 ANT 脚本中使用了 findbugs,但我不知道如何修复我的两个错误。我已阅读文档,但不明白。以下是我的错误以及与之相关的代码:
错误 1:
private boolean equals(final Quantity other)
return this.mAmount == convertedAmount(other);
错误 2:EQ_COMPARETO_USE_OBJECT_EQUALS
public final int compareTo(final Object other)
return this.description().compareTo(((Decision) other).description());
我已阅读有关 ComparesTo 问题的文档,其中指出
强烈建议但不严格要求 (x.compareTo(y)==0) == (x.equals(y))。一般来说,任何实现了 Comparable 接口并违反此条件的类都应该清楚地表明这一事实。推荐的语言是“注意:这个类有一个与equals不一致的自然顺序。”
还有关于浮点相等的文档
此操作比较两个浮点值是否相等。因为浮点计算可能涉及四舍五入,所以计算出的 float 和 double 值可能不准确。对于必须精确的值,例如货币值,请考虑使用固定精度类型,例如 BigDecimal。对于不需要精确的值,请考虑比较某个范围内的相等性,例如: if ( Math.abs(x - y)
虽然我不明白。有人可以帮忙吗?
【问题讨论】:
【参考方案1】:对于浮点警告,您应该记住浮点数是一种不精确类型。对此经常给出的标准参考(也许值得一读)是:
What Every Computer Scientist Should Know About Floating-Point Arithmetic David Goldberg。
因为浮点数不是精确值 - 即使它们在四舍五入后看起来相同 - 它们可能会略有不同,并且无法匹配。
Comparable interface 期望其实现者有某种行为;该警告是在告诉您您没有遵守该规定,并提供建议的操作。
【讨论】:
【参考方案2】:问题一:
对于 FE_FLOATING_POINT_EQUALITY 问题,您不应该将两个浮点值直接与 ==
运算符进行比较,因为由于微小的舍入误差,即使条件 value1 == value2
确实存在,对于您的应用程序而言,这些值在语义上可能是“相等的”不成立。
为了解决这个问题,修改你的代码如下:
private boolean equals(final Quantity other)
return (Math.abs(this.mAmount - convertedAmount(other)) < EPSILON);
其中 EPSILON 是您应该在代码中定义的常量,表示您的应用程序可以接受的微小差异,例如.0000001.
问题 2:
对于 EQ_COMPARETO_USE_OBJECT_EQUALS 问题:强烈建议无论x.compareTo(y)
返回零,x.equals(y)
应为true
。在你的代码中你已经实现了compareTo
,但是你没有覆盖equals
,所以你是从Object
继承equals
的实现,上面的条件不满足。
为了解决这个问题,在你的类中覆盖equals
(可能还有hashCode
),这样当x.compareTo(y)
返回0时,x.equals(y)
将返回true
。
【讨论】:
equals
方法应该是可传递的 (javarevisited.blogspot.fr/2011/02/… ) 并且与 hashCode
方法一致。请告诉我们更多关于您作为(Math.abs(this.mAmount - convertedAmount(other)) < EPSILON)
的equals
实现以及它如何满足这些要求的信息。
@PascalCuoq 你解决了吗?我能想到的一种方法是将浮点数转换为 BigDecimal,四舍五入到您需要的精度并为此计算哈希值。但会有性能影响。
@AlexanderMalakhov 我不是提出这个问题的人。我只对定义非传递 equals
方法的适当性发表了评论。这里没有要“解决”的问题。浮点值可以进行散列处理,并且可以与 Java 已经提供的基本运算符进行比较是否相等。任何定义非传递equals
方法的人都是在为自己制造问题,而解决问题的最简单方法是一开始就不要制造它。
@PascalCuoq。通过“解决”,我的意思就是——使equals
可传递并与hashCode
同步。我的建议解决了hashCode
(仅在该方法内转换为BigDecimal),但不是equals
。作为数学家,这让我有点紧张:)(definition 供参考)
哦,等等。这也适用于equals
。在比较之前,应该四舍五入到所需的精度并比较四舍五入的值。不过,性能可能仍然是个问题。【参考方案3】:
我不同意上面的答案。 Equals 和 compareTo 是在浮点比较中引入 epsilon 的错误位置。
浮点值可以通过 equals 和 compareTo 进行精确比较,只需使用“==”运算符。 如果您的应用程序使用作为计算结果的浮点数,需要将这些值与 epsilon 方法进行比较,它应该只在需要的地方这样做。例如在数学线相交法中。 但不在 equals 和 compareTo 中。
此警告非常具有误导性。这意味着比较两个浮点数,其中至少一个是计算结果可能会产生意想不到的结果。 然而,通常这样的浮点数,比较,不是计算的结果,比如
static final double INVALID_VALUE = -99.0;
if (f == INVALID_VALUE)
其中 f 使用 INVALID_VALUE 初始化,在 java 中将始终完美运行。 但是 findbugs 和 sonarcube 仍然会抱怨。
所以只需在 findbugs 中添加一个忽略过滤器,假设您有两个类 MyPoint2D 和 Myrectangle2D
<Match>
<OR>
<Class name="~.*\.MyPoint2D" />
<Class name="~.*\.MyRectangle2D" />
</OR>
<Bug code="FE" />
<Justification author="My Name" />
<Justification
text="Floating point equals works (here)." />
</Match>
【讨论】:
首先使用“无效值”并不是一个干净的方法。但是优化(通常过早地完成)可能会导致这样的黑客攻击,甚至最终可能是最好的解决方案。从清洁代码的角度来看,您应该使用一个单独的字段来保存浮点数是否有效的信息。那么你就不需要考虑检查浮点数是否相等了。 我同意,在许多情况下,最好有一个单独的字段来显示有效性。在许多其他情况下不是。 (内部代码不对任何其他类公开)这里的主要主题是与浮点常量的比较有效 内部代码不那么干净的想法是危险的。此外,内部代码可能需要由必须了解代码的继任开发人员进行维护。但在某些情况下,我会接受“高度优化”的说法。 与内部 INVALID_VALUE 相比同样干净。发布该字段时的问题是 INVAID_VALUE 是未知的,有时甚至是未记录的。在“if (fieldIsValid()”和“if (field != INVALID)”之间的理解没有任何优势,即使相反是正确的:,第二种情况的代码将紧跟在 if 之后。第一种情况需要一个if (!isFieldsIsValid() 这是一个双重否定,已知更糟。或者一个不干净的 else 路径,因为大部分时间运行的主要代码应该在 if() 块中。 它仍然不干净,因为数据类型从未声明像-99.0
这样的值表示“无效”。您必须首先找出这一点,以了解为什么您的数据突然大幅下降到零以下(例如,它是某物的计数器,因此只会出现正值)。将值与常量进行比较的代码点不是问题。在那里它变得清晰。在没有任何文档的情况下可能会传递这种奇怪值的所有其他地方都是。以上是关于测试浮点相等性。 (FE_FLOATING_POINT_EQUALITY)的主要内容,如果未能解决你的问题,请参考以下文章