简体   繁体   English

算术比较以避免浮点错误

[英]Arithmetic comparison to avoid floating point errors

I have the following check in one of my Java functions...我在我的 Java 函数之一中进行了以下检查...

return floor(value * 100.) % 5 == 0;

I've been told that there is a potential for floating point errors because I am comparing raw values using == but I cannot see why.有人告诉我,可能会出现浮点错误,因为我正在使用==比较原始值,但我不明白为什么。 I thought that using floor would avoid that.我认为使用floor可以避免这种情况。 Why might there be a floating point error and what is the best practice in this case to avoid such errors?为什么会出现浮点错误?在这种情况下,避免此类错误的最佳做法是什么?

Why might there be a floating point error…为什么会出现浮点错误...

There are three potential places for floating-point error in the expression you show:您显示的表达式中存在三个潜在的浮点错误位置:

  • value is presumably the result of prior computations, and those might have had errors. value大概是先前计算的结果,而那些可能有错误。 We have no information about those, since you have not shown them.我们没有关于这些的信息,因为你没有展示它们。
  • The code suggests there is some interest in value as some number of hundredths: .01, .02, .03, and so on.该代码表明,对于某个百分数的value有一些兴趣:0.01、0.02、0.03 等等。 Except for .00, .25, .50, and .75, no hundredths are representable in the binary floating-point format Java uses.除了 .00、.25、.50 和 .75,在 Java 使用的二进制浮点格式中不能表示百分之几。 So, even if prior computations were done as accurately as possible, the final result cannot possibly equal any number of hundredths other than .00, .25, .50, or .75.因此,即使先前的计算尽可能准确,最终结果也不可能等于 0.00、0.25、0.50 或 0.75 以外的任何百分数。 They will be at least slightly higher or lower.它们至少会略高或略低。 If they are lower, then multiplying by 100 and taking the floor may produce a value one lower than you desire.如果它们较低,那么乘以 100 并进行floor可能会产生比您想要的低 1 的值。
  • When value is multiplied by 100, if the result is not exactly representable (which it may not be because value may have 53 significant bits and 100 has 5 significant bits, so the product may have 58 significant bits, but only 53 can be represented in Java's double format), then it is rounded to the nearest representable value.value乘以 100 时,如果结果不能精确表示(可能不是因为value可能有 53 个有效位,100 有 5 个有效位,因此乘积可能有 58 个有效位,但在Java 的double格式),然后四舍五入为最接近的可表示值。

Floating-point arithmetic is largely designed to approximate real arithmetic.浮点算术主要用于逼近实数算术。 For this purpose, it is generally good with continuous functions, such as multiplication, sine, and so on.为此,它通常适用于连续函数,例如乘法、正弦等。 Often, with continuous functions small errors in calculation produce small errors in results.通常,对于连续函数,计算中的小错误会在结果中产生小错误。 However, floor , % , and == are discontinuous functions: They have places where some changes in the inputs cause jumps in the outputs.然而, floor%==是不连续的函数:它们有一些地方,输入的一些变化会导致输出的跳跃。 For floor, a small change from 3.99 to 4 causes a jump of 1 in the output.对于 floor,从 3.99 到 4 的微小变化会导致输出跳变 1。 For % , a small change from 3.99 % 4 to 4 % 4 causes a jump of nearly 4. For == , a small change from 3.99 == 4 to 4 == 4 causes a jump from false to true .对于% ,从3.99 % 44 % 4的小变化会导致接近 4 的跳跃。对于== ,从3.99 == 44 == 4的小变化会导致从false跳跃到true

… what is the best practice in this case to avoid such errors? ……在这种情况下,避免此类错误的最佳做法是什么?

By and large, the best practice when you need exact results for computations involving % is to use integer arithmetic or to use floating-point with care to ensure all operations are exact (as by doing integer arithmetic within the bounds where floating-point represents all integers, −2 53 to +2 53 for Java double ).总的来说,当您需要涉及%计算的精确结果时,最佳实践是使用整数算术或小心使用浮点以确保所有运算都是精确的(例如通过在浮点表示所有运算的范围内进行整数算术)整数,-2 53到 +2 53对于 Java double )。 If you need to work around that, then there is no single best practice;如果您需要解决这个问题,那么没有单一的最佳实践。 good solutions would depend on what you are doing to obtain value and what results you are trying to achieve.好的解决方案将取决于您为获得value正在做什么以及您试图实现什么结果。

I think that's a mistake we (Java developers) often make, but in general it can be observed in almost all programming languages — at least the ones I use these days.我认为这是我们(Java 开发人员)经常犯的一个错误,但总的来说,它几乎可以在所有编程语言中观察到——至少是我最近使用的那些。

The entire thing boils down to how those values are stored in memory — if I'm not mistaken, in Java, float s and double s are stored using the IEEE 754 standard format.整个事情归结为这些值如何存储在内存中——如果我没记错的话,在 Java 中, float s 和double s 是使用IEEE 754标准格式存储的。

Anyway, instead of using the equality operator ( == ), we should rely on relational operators: less than ( < ) or greater than ( > ) to compare float and double values, because in the end, floating point numbers are just an approximation , they are not exact.无论如何,我们应该依靠关系运算符而不是使用相等运算符 ( == ):小于 ( < ) 或大于 ( > ) 来比较floatdouble值,因为最终浮点数只是一个近似值,它们并不准确。

Another approach (I personally like) is to use Float.compare / Double.compare , if you are more familiar with the semantics of compareTo -kind of comparisons.另一种方法(我个人喜欢)是使用Float.compare / Double.compare ,如果您更熟悉compareTo类比较的语义。

Alternatively, I sometimes use also BigDecimal for exact calculations.或者,我有时也使用BigDecimal进行精确计算。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM