Pro*_*ber 381 c floating-point objective-c floating-accuracy ios
我知道因UIKit
使用CGFloat
分辨率独立坐标系而使用.
但我想,以检查是否例如每次frame.origin.x
是0
它让我感到恶心:
if (theView.frame.origin.x == 0) {
// do important operation
}
Run Code Online (Sandbox Code Playgroud)
是不是CGFloat
有比较时容易误报==
,<=
,>=
,<
,>
?这是一个浮点,他们有不寻常的问题:0.0000000000041
例如.
Objective-C
在比较时是在内部处理这个,还是可能发生origin.x
读取为零的情况并不是0
真实的?
R..*_*R.. 457
首先,浮点值的行为不是"随机"的.在大量现实世界的用法中,精确的比较可以而且确实有意义.但是如果你要使用浮点数,你需要知道它是如何工作的.假设浮点工作就像实数一样,会让你快速破解代码.假设浮点结果与它们相关的大量随机模糊(就像这里建议的大多数答案一样)会让你看起来最初工作的代码,但最终会出现大幅度错误和破坏角落情况.
首先,如果你想用浮点编程,你应该读这个:
是的,阅读全部内容.如果这是一个太大的负担,你应该使用整数/不动点进行计算,直到你有时间阅读它.:-)
现在,有了这个说法,精确浮点比较的最大问题归结为:
该地段价值的,你可以在源写,或者用阅读的事实scanf
或者strtod
,不存在浮动点值并获得静悄悄地转换为最接近的近似.这就是demon9733的答案所说的.
事实上许多结果由于没有足够的精度来表示实际结果而被舍入.一个简单的例子,你可以看到这是添加x = 0x1fffffe
和y = 1
浮动.这里,x
尾数中有24位精度(ok)并且y
只有1位,但是当你添加它们时,它们的位不在重叠位置,结果需要25位精度.相反,它会四舍五入(到0x2000000
默认的舍入模式).
事实上许多结果由于需要无限多的位置来获得正确的值而被舍入.这包括合理的结果,如1/3(你从十进制熟悉,它需要无限多的地方),但也是1/10(这也是二进制中无限多的地方,因为5不是2的幂),以及不合理的结果,如任何不完美正方形的平方根.
双舍入.在某些系统(特别是x86)上,浮点表达式的精度高于其标称类型.这意味着当上述类型的舍入之一发生时,您将获得两个舍入步骤,首先将结果舍入到更高精度类型,然后舍入到最终类型.例如,如果将1.49舍入为整数(1),请考虑以十进制发生的情况,而如果首先将其舍入到一个小数位(1.5),然后将结果舍入为整数(2),则会发生什么.这实际上是浮点处理的最糟糕的区域之一,因为编译器的行为(特别是对于有缺陷的,不符合要求的编译器,如GCC)是不可预测的.
超越函数(trig
,exp
,log
,等)不规定为具有正确的舍入结果; 结果只是指定在最后一个精度位置(通常称为1ulp)的一个单位内是正确的.
当您编写浮点代码时,您需要记住您正在对可能导致结果不准确的数字执行的操作,并相应地进行比较.通常,与"epsilon"进行比较是有意义的,但是epsilon应该基于您所比较的数字的大小,而不是绝对常数.(在绝对常数epsilon可以工作的情况下,这强烈表明固定点,而不是浮点,是工作的正确工具!)
编辑:特别是,幅度相对的epsilon检查应该类似于:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y))
Run Code Online (Sandbox Code Playgroud)
当FLT_EPSILON
从定float.h
(含更换DBL_EPSILON
为double
S或LDBL_EPSILON
为long double
S)和K
是您选择这样的计算的累积误差绝对是以下所界定的恒定K
在最后的地方单位(如果你不知道你得到了错误绑定计算权,K
比你的计算所说的要大几倍.
最后,请注意,如果你使用它,可能需要在零附近FLT_EPSILON
做一些特别小心,因为对于非正规数没有意义.快速解决方法是:
if (fabs(x-y) < K * FLT_EPSILON * fabs(x+y) || fabs(x-y) < FLT_MIN)
Run Code Online (Sandbox Code Playgroud)
并且DBL_MIN
如果使用双打同样替代.
Hig*_*ark 35
由于0可以完全表示为IEEE754浮点数(或使用我曾经使用的任何其他fp数字实现),因此与0进行比较可能是安全的.但是,如果你的程序计算一个theView.frame.origin.x
你有理由认为应该为0但你的计算不能保证为0 的值(例如),你可能会被咬.
为了澄清一点,计算如下:
areal = 0.0
Run Code Online (Sandbox Code Playgroud)
将(除非您的语言或系统被破坏)创建一个值,使得(areal == 0.0)返回true但另一个计算如
areal = 1.386 - 2.1*(0.66)
Run Code Online (Sandbox Code Playgroud)
不得.
如果你可以向自己保证你的计算产生0的值(而不仅仅是它们产生的值应该是0)那么你可以继续将fp值与0进行比较.如果你不能保证自己达到所需的程度,最好坚持"宽容平等"的惯常做法.
在最糟糕的情况下,对fp值的粗心比较可能是非常危险的:思考航空电子设备,武器引导,发电厂运行,车辆导航,几乎任何计算符合现实世界的应用.
对于愤怒的小鸟,没有那么危险.
sta*_*ole 22
我想给出一些与其他答案不同的答案.它们非常适合回答您所述的问题,但可能不适合您需要了解的内容或您的真正问题.
图形中的浮点很好!但是几乎没有必要直接比较浮子.你为什么要这么做?Graphics使用浮点数来定义间隔.并且比较浮点是否也在浮点数定义的区间内总是很明确,只需要一致,不准确或精确!只要一个像素(也是一个间隔!)可以分配所有图形需要.
因此,如果你想测试你的点是否在[0..width [范围之外]这是正常的.只需确保一致地定义包含.例如,始终定义内部是(x> = 0 && x <width).交叉点或命中测试也是如此.
但是,如果您将图形坐标滥用为某种标记,例如,如果要查看窗口是否已停靠,则不应执行此操作.请使用与图形表示层分开的布尔标志.
小智 13
只要零不是计算值(如上面的答案中所述),与零比较可以是安全的操作.原因是零是浮点数中完全可表示的数字.
说出完全可表示的值,您可以获得24位的功率范围(单精度).因此,1,2,4是完全可表示的,如.5,.25和.125.只要你的所有重要位都是24位,你就是金色的.所以10.625可以准确地表达.
这很好,但会在压力下迅速崩溃.我想到两种情况:1)涉及计算时.不要相信sqrt(3)*sqrt(3)== 3.它不会那样.并且它可能不会在epsilon中,正如其他一些答案所暗示的那样.2)当涉及任何非幂2(NPOT)时.所以它可能听起来很奇怪,但0.1是二进制的无限级数,因此涉及这样的数字的任何计算从一开始就是不精确的.
(哦,原始问题提到比较为零.不要忘记-0.0也是一个完全有效的浮点值.)
GoZ*_*ner 11
['正确答案'掩盖了选择K
.选择K
最终就像选择VISIBLE_SHIFT
但是选择一样K
不明显,因为VISIBLE_SHIFT
它不是基于任何显示属性.因此,选择你的毒药 - 选择K
或选择VISIBLE_SHIFT
.这个答案提倡选择VISIBLE_SHIFT
然后证明选择的难度K
]
正是由于圆形错误,您不应该使用逻辑运算的"精确"值进行比较.在您在视觉显示器上的位置的特定情况下,如果位置是0.0或0.0000000003则无关紧要 - 眼睛看不到差异.所以你的逻辑应该是这样的:
#define VISIBLE_SHIFT 0.0001 // for example
if (fabs(theView.frame.origin.x) < VISIBLE_SHIFT) { /* ... */ }
Run Code Online (Sandbox Code Playgroud)
但是,最终,"眼睛看不见"将取决于您的显示属性.如果你可以上限显示(你应该能够); 然后选择VISIBLE_SHIFT
成为上限的一小部分.
现在,"正确的答案"依赖于此,K
让我们来探索挑选K
.上面的"正确答案"说:
K是你选择的一个常数,这样你的计算的累积误差肯定是由最后的K个单位限定的(如果你不确定你的误差界限计算是正确的,那么使K比你的计算大几倍)说它应该是)
所以我们需要K
.如果获取K
比选择我更困难,更不直观,VISIBLE_SHIFT
那么你将决定什么对你有用.要找到K
我们要编写一个测试程序来查看一堆K
值,以便我们可以看到它的行为方式.K
如果'正确答案'可用,应该明白如何选择.没有?
我们将使用,作为"正确答案"的详细信息:
if (fabs(x-y) < K * DBL_EPSILON * fabs(x+y) || fabs(x-y) < DBL_MIN)
Run Code Online (Sandbox Code Playgroud)
让我们试试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
Run Code Online (Sandbox Code Playgroud)
啊,如果我希望1e-13为'零',那么K应该是1e16或更大.
所以,我想说你有两个选择:
K
.正确的问题:如何在Cocoa Touch中比较积分?
正确答案:CGPointEqualToPoint().
一个不同的问题:两个计算值是否相同?
答案贴在这里:他们不是.
如何检查它们是否接近?如果要检查它们是否接近,则不要使用CGPointEqualToPoint().但是,不要检查它们是否接近.做一些在现实世界中有意义的事情,比如检查一个点是否超出一条线或一个点是否在一个球体内.
归档时间: |
|
查看次数: |
86915 次 |
最近记录: |