ios 比较浮点值有多危险?
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/10334688/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
How dangerous is it to compare floating point values?
提问by Proud Member
I know UIKit
uses CGFloat
because of the resolution independent coordinate system.
我知道UIKit
用途CGFloat
是因为分辨率独立的坐标系。
But every time I want to check if for example frame.origin.x
is 0
it makes me feel sick:
但每次我要检查是否例如frame.origin.x
被0
这让我感到恶心:
if (theView.frame.origin.x == 0) {
// do important operation
}
Isn't CGFloat
vulnerable to false positives when comparing with ==
, <=
, >=
, <
, >
?
It is a floating point and they have unprecision problems: 0.0000000000041
for example.
CGFloat
与==
, <=
, >=
, <
,比较时不容易误报>
吗?它是一个浮点数,它们有不精确的问题:0.0000000000041
例如。
Is Objective-C
handling this internally when comparing or can it happen that a origin.x
which reads as zero does not compare to 0
as true?
Objective-C
比较时是在内部处理这个问题,还是会发生origin.x
读取为零的a0
与 true不比较?
回答by R.. GitHub STOP HELPING ICE
First of all, floating point values are not "random" in their behavior. Exact comparison can and does make sense in plenty of real-world usages. But if you're going to use floating point you need to be aware of how it works. Erring on the side of assuming floating point works like real numbers will get you code that quickly breaks. Erring on the side of assuming floating point results have large random fuzz associated with them (like most of the answers here suggest) will get you code that appears to work at first but ends up having large-magnitude errors and broken corner cases.
首先,浮点值的行为不是“随机的”。精确的比较可以而且确实在大量现实世界的使用中有意义。但是如果你打算使用浮点数,你需要了解它是如何工作的。错误地假设浮点数像实数一样工作会让你的代码很快崩溃。假设浮点结果具有与之相关的大量随机模糊(就像这里的大多数答案所建议的那样),会让您的代码一开始看起来可以工作,但最终会出现大量错误和破碎的极端情况。
First of all, if you want to program with floating point, you should read this:
首先,如果你想用浮点编程,你应该阅读这个:
What Every Computer Scientist Should Know About Floating-Point Arithmetic
Yes, read all of it. If that's too much of a burden, you should use integers/fixed point for your calculations until you have time to read it. :-)
是的,阅读所有内容。如果负担太大,您应该使用整数/定点进行计算,直到您有时间阅读它。:-)
Now, with that said, the biggest issues with exact floating point comparisons come down to:
现在,话虽如此,精确浮点比较的最大问题归结为:
The fact that lots of values you may write in the source, or read in with
scanf
orstrtod
, do not existas floating point values and get silently converted to the nearest approximation. This is what demon9733's answer was talking about.The fact that many results get rounded due to not having enough precision to represent the actual result. An easy example where you can see this is adding
x = 0x1fffffe
andy = 1
as floats. Here,x
has 24 bits of precision in the mantissa (ok) andy
has just 1 bit, but when you add them, their bits are not in overlapping places, and the result would need 25 bits of precision. Instead, it gets rounded (to0x2000000
in the default rounding mode).The fact that many results get rounded due to needing infinitely many places for the correct value. This includes both rational results like 1/3 (which you're familiar with from decimal where it takes infinitely many places) but also 1/10 (which also takes infinitely many places in binary, since 5 is not a power of 2), as well as irrational results like the square root of anything that's not a perfect square.
Double rounding. On some systems (particularly x86), floating point expressions are evaluated in higher precision than their nominal types. This means that when one of the above types of rounding happens, you'll get two rounding steps, first a rounding of the result to the higher-precision type, then a rounding to the final type. As an example, consider what happens in decimal if you round 1.49 to an integer (1), versus what happens if you first round it to one decimal place (1.5) then round that result to an integer (2). This is actually one of the nastiest areas to deal with in floating point, since the behaviour of the compiler (especially for buggy, non-conforming compilers like GCC) is unpredictable.
Transcendental functions (
trig
,exp
,log
, etc.) are not specified to have correctly rounded results; the result is just specified to be correct within one unit in the last place of precision (usually referred to as 1ulp).
该地段价值的,你可以在源写,或与读取的事实
scanf
或者strtod
,不存在浮动点值并获得静悄悄地转换为最接近的近似。这就是demon9733's answer所说的。许多成果获得不具有足够的精度代表实际结果因圆润的事实。一个简单的例子,你可以看到它是添加
x = 0x1fffffe
和y = 1
作为浮动。在这里,x
尾数中有 24 位精度(好的)并且y
只有 1 位,但是当您添加它们时,它们的位不在重叠的位置,结果将需要 25 位精度。相反,它会四舍五入(以0x2000000
默认舍入模式)。由于需要无限多个位置才能获得正确值,因此许多结果会四舍五入。这包括有理结果,例如 1/3(您在十进制中熟悉它,它需要无限多的位置)和 1/10(在二进制中也需要无限多的位置,因为 5 不是 2 的幂),以及不合理的结果,例如任何不是完美平方的东西的平方根。
双舍入。在某些系统(尤其是 x86)上,浮点表达式的计算精度高于其标称类型。这意味着当发生上述类型的舍入之一时,您将获得两个舍入步骤,首先将结果舍入到更高精度的类型,然后舍入到最终类型。例如,考虑将 1.49 舍入为整数 (1) 时会发生什么情况,而如果先将其舍入到一位小数 (1.5) 然后将结果舍入为整数 (2) 时会发生什么情况。这实际上是浮点运算中最难处理的领域之一,因为编译器的行为(特别是对于像 GCC 这样有缺陷、不符合标准的编译器)是不可预测的。
超越函数(
trig
、exp
、log
等)未指定为具有正确舍入的结果;结果只是指定为在精度的最后一位(通常称为1ulp)的一个单位内是正确的。
When you're writing floating point code, you need to keep in mind what you're doing with the numbers that could cause the results to be inexact, and make comparisons accordingly. Often times it will make sense to compare with an "epsilon", but that epsilon should be based on the magnitude of the numbers you are comparing, not an absolute constant. (In cases where an absolute constant epsilon would work, that's strongly indicative that fixed point, not floating point, is the right tool for the job!)
当您编写浮点代码时,您需要记住您对可能导致结果不准确的数字所做的事情,并进行相应的比较。通常,与“epsilon”进行比较是有意义的,但该 epsilon 应基于您要比较的数字的大小,而不是绝对常数。(在绝对常数 epsilon 可以工作的情况下,这强烈表明定点,而不是浮点,是适合这项工作的工具!)
Edit:In particular, a magnitude-relative epsilon check should look something like:
编辑:特别是,相对于幅度的 epsilon 检查应该类似于:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
Where FLT_EPSILON
is the constant from float.h
(replace it with DBL_EPSILON
fordouble
s or LDBL_EPSILON
for long double
s) and K
is a constant you choose such that the accumulated error of your computations is definitely bounded by K
units in the last place (and if you're not sure you got the error bound calculation right, make K
a few times bigger than what your calculations say it should be).
FLT_EPSILON
常数来自哪里float.h
(用DBL_EPSILON
for double
s 或LDBL_EPSILON
for long double
s替换)并且K
是您选择的常数,这样计算的累积误差肯定受K
最后一个单位的限制(如果您不确定是否得到了错误)限制计算正确,K
比您的计算所说的大几倍)。
Finally, note that if you use this, some special care may be needed near zero, since FLT_EPSILON
does not make sense for denormals. A quick fix would be to make it:
最后,请注意,如果您使用它,在零附近可能需要一些特殊的注意,因为FLT_EPSILON
对非正规数没有意义。一个快速的解决方法是让它:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
and likewise substitute DBL_MIN
if using doubles.
DBL_MIN
如果使用双打,同样替换。
回答by High Performance Mark
Since 0 is exactly representable as an IEEE754 floating-point number (or using any other implementation of f-p numbers I've ever worked with) comparison with 0 is probably safe. You might get bitten, however, if your program computes a value (such as theView.frame.origin.x
) which you have reason to believe ought to be 0 but which your computation cannot guarantee to be 0.
由于 0 完全可以表示为 IEEE754 浮点数(或使用我曾经使用过的任何其他 fp 数实现),因此与 0 进行比较可能是安全的。但是,如果您的程序计算一个值(例如theView.frame.origin.x
),您有理由相信该值应该为 0,但您的计算不能保证为 0 ,则您可能会被咬。
To clarify a little, a computation such as :
为了澄清一点,计算如下:
areal = 0.0
will (unless your language or system is broken) create a value such that (areal==0.0) returns true but another computation such as
将(除非您的语言或系统损坏)创建一个值,使得 (area==0.0) 返回 true 但另一个计算,例如
areal = 1.386 - 2.1*(0.66)
may not.
不得。
If you can assure yourself that your computations produce values which are 0 (and not just that they produce values which ought to be 0) then you can go ahead and compare f-p values with 0. If you can't assure yourself to the required degree, best stick to the usual approach of 'toleranced equality'.
如果您可以确保您的计算产生的值为 0(而不仅仅是它们产生的值应该为 0),那么您可以继续将 fp 值与 0 进行比较。如果您不能保证达到所需的程度,最好坚持“容忍平等”的通常方法。
In the worst cases the careless comparison of f-p values can be extremely dangerous: think avionics, weapons-guidance, power-plant operations, vehicle navigation, almost any application in which computation meets the real world.
在最坏的情况下,粗心比较 fp 值可能是极其危险的:想想航空电子、武器制导、动力装置操作、车辆导航,几乎所有计算与现实世界相结合的应用程序。
For Angry Birds, not so dangerous.
对于 Angry Birds 来说,没有那么危险。
回答by starmole
I want to give a bit of a different answer than the others. They are great for answering your question as stated but probably not for what you need to know or what your real problem is.
我想给出一个与其他人不同的答案。它们非常适合按照所述回答您的问题,但可能不适用于您需要了解的内容或您真正的问题是什么。
Floating point in graphics is fine! But there is almost no need to ever compare floats directly. Why would you need to do that? Graphics uses floats to define intervals. And comparing if a float is within an interval also defined by floats is always well defined and merely needs to be consistent, not accurate or precise! As long as a pixel (which is also an interval!) can be assigned that's all graphics needs.
图形中的浮点很好!但是几乎没有必要直接比较浮点数。为什么你需要这样做?图形使用浮点数来定义间隔。并且比较浮点数是否在也由浮点数定义的区间内始终是明确定义的,只需要保持一致,而不是准确或精确!只要可以分配一个像素(这也是一个间隔!),这就是所有图形需求。
So if you want to test if your point is outside a [0..width[ range this is just fine. Just make sure you define inclusion consistently. For example always define inside is (x>=0 && x < width). The same goes for intersection or hit tests.
所以如果你想测试你的点是否在 [0..width[ 范围之外,这很好。只要确保你一致地定义包容。例如总是在里面定义是 (x>=0 && x < width)。交叉或命中测试也是如此。
However, if you are abusing a graphics coordinate as some kind of flag, like for example to see if a window is docked or not, you should not do this. Use a boolean flag that is separate from the graphics presentation layer instead.
但是,如果您滥用图形坐标作为某种标志,例如查看窗口是否停靠,则不应这样做。改为使用与图形表示层分开的布尔标志。
回答by JHumphrey
Comparing to zero canbe a safe operation, as long as the zero wasn't a calculated value (as noted in an above answer). The reason for this is that zero is a perfectly representable number in floating point.
只要零不是计算值(如上述答案中所述),与零进行比较就可以是安全的操作。这样做的原因是零在浮点数中是一个完全可表示的数字。
Talking perfectly representable values, you get 24 bits of range in a power-of-two notion (single precision). So 1, 2, 4 are perfectly representable, as are .5, .25, and .125. As long as all your important bits are in 24-bits, you are golden. So 10.625 can be repsented precisely.
谈论完美可表示的值,您将获得 24 位范围的 2 次幂概念(单精度)。所以 1、2、4 是完全可表示的,0.5、0.25 和 0.125 也是如此。只要您所有重要的位都是 24 位,您就是黄金。因此可以精确表示 10.625。
This is great, but will quickly fall apart under pressure. Two scenarios spring to mind: 1) When a calculation is involved. Don't trust that sqrt(3)*sqrt(3) == 3. It just won't be that way. And it probably won't be within an epsilon, as some of the other answers suggest. 2) When any non-power-of-2 (NPOT) is involved. So it may sound odd, but 0.1 is an infinite series in binary and therefore any calculation involving a number like this will be imprecise from the start.
这很好,但会在压力下迅速崩溃。我想到了两种情况:1) 涉及计算时。不要相信 sqrt(3)*sqrt(3) == 3。它不会是那样的。正如其他一些答案所暗示的那样,它可能不会在 epsilon 内。2) 当涉及任何非 2 次幂 (NPOT) 时。所以这听起来可能很奇怪,但 0.1 是二进制的无限级数,因此任何涉及这样的数字的计算从一开始就会不精确。
(Oh and the original question mentioned comparisons to zero. Don't forget that -0.0 is also a perfectly valid floating-point value.)
(哦,原来的问题提到了与零的比较。不要忘记 -0.0 也是一个完全有效的浮点值。)
回答by GoZoner
[The 'right answer' glosses over selecting K
. Selecting K
ends up being just as ad-hoc as selecting VISIBLE_SHIFT
but selecting K
is less obvious because unlike VISIBLE_SHIFT
it is not grounded on any display property. Thus pick your poison - select K
or select VISIBLE_SHIFT
. This answer advocates selecting VISIBLE_SHIFT
and then demonstrates the difficulty in selecting K
]
[“正确答案”掩盖了选择K
. 选择K
最终与选择一样临时,VISIBLE_SHIFT
但选择K
不那么明显,因为VISIBLE_SHIFT
它不基于任何显示属性。因此选择你的毒药-选择K
或选择VISIBLE_SHIFT
。这个答案提倡选择VISIBLE_SHIFT
,然后展示选择的难度K
]
Precisely because of round errors, you should not use comparison of 'exact' values for logical operations. In your specific case of a position on a visual display, it can't possibly matter if the position is 0.0 or 0.0000000003 - the difference is invisible to the eye. So your logic should be something like:
正是由于舍入错误,您不应该对逻辑运算使用“精确”值的比较。在视觉显示上的位置的特定情况下,位置是 0.0 还是 0.0000000003 可能无关紧要 - 肉眼看不到差异。所以你的逻辑应该是这样的:
#define VISIBLE_SHIFT 0.0001 // for example
if (fabs(theView.frame.origin.x) < VISIBLE_SHIFT) { /* ... */ }
However, in the end, 'invisible to the eye' will depend on your display properties. If you can upper bound the display (you should be able to); then choose VISIBLE_SHIFT
to be a fraction of that upper bound.
但是,最终,“肉眼不可见”将取决于您的显示属性。如果您可以上限显示(您应该可以);然后选择VISIBLE_SHIFT
成为该上限的一小部分。
Now, the 'right answer' rests upon K
so let's explore picking K
. The 'right answer' above says:
现在,“正确答案”取决于,K
让我们探索采摘K
。上面的“正确答案”说:
K is a constant you choose such that the accumulated error of your computations is definitely bounded by K units in the last place (and if you're not sure you got the error bound calculation right, make K a few times bigger than what your calculations say it should be)
K 是你选择的一个常数,这样你的计算的累积误差在最后一个地方肯定受 K 单位的限制(如果你不确定你的误差界限计算是否正确,让 K 比你的计算大几倍说应该是)
So we need K
. If getting K
is more difficult, less intuitive than selecting my VISIBLE_SHIFT
then you'll decide what works for you. To find K
we are going to write a test program that looks at a bunch of K
values so we can see how it behaves. Ought to be obvious how to choose K
, if the 'right answer' is usable. No?
所以我们需要K
. 如果获取K
比选择我的更困难,更不直观,VISIBLE_SHIFT
那么您将决定什么适合您。为了找到K
我们将编写一个测试程序来查看一堆K
值,以便我们可以看到它的行为。K
如果“正确答案”可用,如何选择应该很明显。不?
We are going to use, as the 'right answer' details:
我们将使用,作为“正确答案”的细节:
if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)
Let's just try all values of K:
让我们尝试 K 的所有值:
#include <math.h>
#include <float.h>
#include <stdio.h>
void main (void)
{
double x = 1e-13;
double y = 0.0;
double K = 1e22;
int i = 0;
for (; i < 32; i++, K = K/10.0)
{
printf ("K:%40.16lf -> ", K);
if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)
printf ("YES\n");
else
printf ("NO\n");
}
}
ebg@ebg$ gcc -o test test.c
ebg@ebg$ ./test
K:10000000000000000000000.0000000000000000 -> YES
K: 1000000000000000000000.0000000000000000 -> YES
K: 100000000000000000000.0000000000000000 -> YES
K: 10000000000000000000.0000000000000000 -> YES
K: 1000000000000000000.0000000000000000 -> YES
K: 100000000000000000.0000000000000000 -> YES
K: 10000000000000000.0000000000000000 -> YES
K: 1000000000000000.0000000000000000 -> NO
K: 100000000000000.0000000000000000 -> NO
K: 10000000000000.0000000000000000 -> NO
K: 1000000000000.0000000000000000 -> NO
K: 100000000000.0000000000000000 -> NO
K: 10000000000.0000000000000000 -> NO
K: 1000000000.0000000000000000 -> NO
K: 100000000.0000000000000000 -> NO
K: 10000000.0000000000000000 -> NO
K: 1000000.0000000000000000 -> NO
K: 100000.0000000000000000 -> NO
K: 10000.0000000000000000 -> NO
K: 1000.0000000000000000 -> NO
K: 100.0000000000000000 -> NO
K: 10.0000000000000000 -> NO
K: 1.0000000000000000 -> NO
K: 0.1000000000000000 -> NO
K: 0.0100000000000000 -> NO
K: 0.0010000000000000 -> NO
K: 0.0001000000000000 -> NO
K: 0.0000100000000000 -> NO
K: 0.0000010000000000 -> NO
K: 0.0000001000000000 -> NO
K: 0.0000000100000000 -> NO
K: 0.0000000010000000 -> NO
Ah, so K should be 1e16 or larger if I want 1e-13 to be 'zero'.
啊,所以如果我想让 1e-13 为“零”,那么 K 应该是 1e16 或更大。
So, I'd say you have two options:
所以,我想说你有两个选择:
- Do a simple epsilon computation using your engineering judgementfor the value of 'epsilon', as I've suggested. If you are doing graphics and 'zero' is meant to be a 'visible change' than examine your visual assets (images, etc) and judge what epsilon can be.
- Don't attempt any floating point computations until you've read the non-cargo-cult answer's reference (and gotten your Ph.D in the process) and then use your non-intuitive judgement to select
K
.
- 正如我所建议的,使用您对“epsilon”值的工程判断进行简单的 epsilon 计算。如果您正在制作图形并且“零”意味着“可见变化”,那么请检查您的视觉资产(图像等)并判断 epsilon 可以是什么。
- 不要尝试任何浮点计算,直到您阅读了 non-cargo-cult 答案的参考资料(并在此过程中获得了博士学位),然后使用您的非直觉判断选择
K
.
回答by Michael T.
The correct question: how does one compare points in Cocoa Touch?
正确的问题:如何比较 Cocoa Touch 中的点数?
The correct answer: CGPointEqualToPoint().
正确答案:CGPointEqualToPoint()。
A different question: Are two calculated values are the same?
一个不同的问题:两个计算值是否相同?
The answer posted here: They are not.
答案张贴在这里:他们不是。
How to check if they are close? If you want to check if they are close, then don't use CGPointEqualToPoint(). But, don't check to see if they are close. Do something that makes sense in the real world, like checking to see if a point is beyond a line or if a point is inside a sphere.
如何检查它们是否接近?如果您想检查它们是否接近,则不要使用 CGPointEqualToPoint()。但是,不要检查它们是否接近。做一些在现实世界中有意义的事情,比如检查一个点是否在一条线之外,或者一个点是否在一个球体内。
回答by Lucas Membrane
The last time I checked the C standard, there was no requirement for floating point operations on doubles (64 bits total, 53 bit mantissa) to be accurate to more than that precision. However, some hardware might do the operations in registers of greater precision, and the requirement was interpreted to mean no requirement to clear lower order bits (beyond the precision of the numbers being loaded into the registers). So you could get unexpected results of comparisons like this depending on what was left over in the registers from whoever slept there last.
上次我检查 C 标准时,没有要求双精度浮点运算(总共 64 位,53 位尾数)精确到超过该精度。但是,某些硬件可能会在更高精度的寄存器中执行操作,并且该要求被解释为不需要清除低位位(超出加载到寄存器中的数字的精度)。所以你可能会得到意想不到的比较结果,这取决于最后睡在那里的人在寄存器中留下了什么。
That said, and despite my efforts to expunge it whenever I see it, the outfit where I work has lots of C code that is compiled using gcc and run on linux, and we have not noticed any of these unexpected results in a very long time. I have no idea whether this is because gcc is clearing the low-order bits for us, the 80-bit registers are not used for these operations on modern computers, the standard has been changed, or what. I'd like to know if anyone can quote chapter and verse.
也就是说,尽管我每次看到它都努力将其删除,但我工作的装备中有大量使用 gcc 编译并在 linux 上运行的 C 代码,并且我们已经很长时间没有注意到任何这些意外结果. 不知道是不是gcc在为我们清低低位,现代计算机上这些操作没有使用80位的寄存器,标准变了,还是什么。我想知道是否有人可以引用章节和诗句。
回答by Igor
You can use such code for compare float with zero:
您可以使用此类代码将浮点数与零进行比较:
if ((int)(theView.frame.origin.x * 100) == 0) {
// do important operation
}
This will compare with 0.1 accuracy, that enough for CGFloat in this case.
这将与 0.1 精度进行比较,在这种情况下,对于 CGFloat 来说已经足够了。
回答by Abbas Mulani
-(BOOL)isFloatEqual:(CGFloat)firstValue secondValue:(CGFloat)secondValue{
BOOL isEqual = NO;
NSNumber *firstValueNumber = [NSNumber numberWithDouble:firstValue];
NSNumber *secondValueNumber = [NSNumber numberWithDouble:secondValue];
isEqual = [firstValueNumber isEqualToNumber:secondValueNumber];
return isEqual;
}
}
回答by denim
I am using the following comparison function to compare a number of decimal places:
我正在使用以下比较函数来比较小数位数:
bool compare(const double value1, const double value2, const int precision)
{
int64_t magnitude = static_cast<int64_t>(std::pow(10, precision));
int64_t intValue1 = static_cast<int64_t>(value1 * magnitude);
int64_t intValue2 = static_cast<int64_t>(value2 * magnitude);
return intValue1 == intValue2;
}
// Compare 9 decimal places:
if (compare(theView.frame.origin.x, 0, 9)) {
// do important operation
}