C 意外浮点算术
不久前,我在这里发布了一篇文章,我知道由于精度不同,浮点值不应该与双精度值进行比较,我们可能无法始终得到可预测的结果。但最近我偶然发现了另一个代码,其中两个或多个浮点数之间的比较也会导致非常奇怪的行为 以下是我遇到的代码:C 意外浮点算术,c,floating-point,C,Floating Point,不久前,我在这里发布了一篇文章,我知道由于精度不同,浮点值不应该与双精度值进行比较,我们可能无法始终得到可预测的结果。但最近我偶然发现了另一个代码,其中两个或多个浮点数之间的比较也会导致非常奇怪的行为 以下是我遇到的代码: #include<stdio.h> int main() { float a=0.0f; int i; for(i=0;i<10;i++) a=a+0.1f; if(a==1.0f) printf("
#include<stdio.h>
int main()
{
float a=0.0f;
int i;
for(i=0;i<10;i++)
a=a+0.1f;
if(a==1.0f) printf("True\n");
else printf("False\n");
a=0.0f;
for(i=0;i<5;i++)
a=a+0.2f;
if(a==1.0f) printf("True\n");
else printf("False\n");
}
#包括
int main()
{
浮球a=0.0f;
int i;
对于(i=0;i而言,浮点数(包括C中的浮点和双精度)由两部分表示,这两部分都有固定数量的位来保存其值:
一个称为尾数的二进制分数(在二进制点的左边没有位,在二进制点的右边没有零)。(这与数字的十进制表示法相比较。小数点左边的数字可以与二进制点左边的位相比较,小数点右边的小数位数可以与二进制点右边的小数位数相比较)
表示尾数乘以2的幂的指数。(与科学符号0.1e5相比,表示尾数乘以10的幂的指数为5)
在十进制中,我们不能用固定数量的小数位数来表示分数1/3。例如,0.333333并不完全等于1/3,因为3需要无限重复
在二进制中,我们不能用固定数量的小数位来表示分数1/10。在这种情况下,二进制数0.0001100110011并不完全等于1/10,因为0011需要无限重复。因此,当1/10转换为浮点时,这部分会被截断以适合可用位
在二进制中,分母(底部)可被10整除的任何分数都是无限重复的。这意味着许多浮点值是不精确的
当我们把无限重复的二进制分数转换成一个固定位数的二进制分数时,如果你把它们加在一起,不精确性可能会被抵消或加强,这取决于当我们把无限重复的二进制分数转换成一个固定位数的二进制分数时被截断的位的值
对于大数字、含有大量数字的分数或添加非常不同的数字时,也会出现不精确。例如,10亿+0.0000009不能用可用位数表示,因此分数会被舍入
你可以看到它变得很复杂。在任何特定的情况下,你都可以提出浮点表示法,在乘法或除法时计算由于截断位和舍入而产生的误差。在这一点上,如果你遇到麻烦,你就可以确切地看到为什么它是错误的
简化示例-不精确表示
下面是一个忽略指数并使尾数未规范化的示例,这意味着不会删除左侧的零。(当在7位后截断时,0.0001100=1/10和0.0011001=1/20)。请注意,在实际情况下,问题发生在右侧的位数更多:
0.0001100 = 1/10
0.0001100
0.0001100
0.0001100 0.0011001 = 2/10 (1/5)
0.0001100 0.0011001
0.0001100 0.0011001
--------- ---------
00 <- sum of right 2 columns 11 <- sum of right column
11000 <- sum of next column 00 <- sum of next two columns
110 <- sum of next column 11 <- sum of next column
000 <- sum of other columns 11 <- sum of next column
------- 000 <- sum of other columns
0.1001000 <- sum ---------
0.1001011 <- sum
或者,您可以通过两次比较来检查相同的差异范围:
if (f1 < 0.999999f || f1 > 1.000001f) ...
if(f1<0.99999f | | f1>1.000001f)。。。
我应该再次指出,每个问题都有自己感兴趣的小数位数
例如,如果谷歌告诉你地球上两个位置之间的距离(以公里为单位),你可能会关注最近的米,因此你会说0.001(千分之一公里)范围内的任何两个位置在功能上是相同的。将差值与0.0005进行比较。或者你可能只关注最近的块,因此将差值与0.03进行比较(300米)。因此,将差值与0.015进行比较
同样的道理也适用于你的测量工具非常精确的情况。如果你用标尺测量,不要期望结果精确到百分之一英寸。你假设a+0.2
等于a+0.1+0.1
。事实并非如此(因为舍入误差)对于a
的某些值,但对于其他值则是如此。例如,当a==0
时,两者显然相等,但如果a
是a+0.1==a
的最小数,则两者显然不同
请参阅此代码:
#include<stdio.h>
int main()
{
float a=0.0f;
int i;
for(i=0;i<10;i++){
if (a+0.1f+0.1f==a+0.2f)
printf("i=%d, equal!\n",i);
else
printf("i=%d, delta=%.10f\n",i,(a+0.1f+0.1f)-(a+0.2f));
a=a+0.1f;
}
return 0;
}
添加一个printf()
,a
在这两种情况下都显示为1.000000
,所有答案都可以在这里找到()“我们可以相信浮点arthimatic多远?”在一个非常小的增量内。也就是说不完全正确。永远不会(除非你真正理解你在做什么)使用=
来比较任何浮点值,无论是浮点值
还是双精度
@woodchips:最低有效位并不总是一个风险。有一些技术可以很好地处理浮点值,但它们需要知识和仔细的应用。高级语言通常不支持浮点运算很好,因此它还需要特定于实现的代码。@EricPostpischil-是的,我意识到有些情况下LSB没有风险。我经常利用这一点。但除非你对浮点运算的行为有足够的了解,否则最好不要假设。这个答案没有解释为什么将.1f添加十次会产生与将.2f添加五次不同的结果。建议将浮点数与公差进行比较,这是一个错误的建议,因为它会以误报换取误报(在不了解应用程序的情况下,这是不合适的),如果没有关于前面的操作和数据以及应用程序要求的信息以及其他原因,就无法确定容差。@EricPostChil如果有人正在编写代码,并且在不知道问题域指定的容差的情况下选择使用浮点或双精度,我们需要拿走他们的编程许可证。我要补充一点关于为什么使用浮点运算的一些信息
#include<stdio.h>
int main()
{
float a=0.0f;
int i;
for(i=0;i<10;i++){
if (a+0.1f+0.1f==a+0.2f)
printf("i=%d, equal!\n",i);
else
printf("i=%d, delta=%.10f\n",i,(a+0.1f+0.1f)-(a+0.2f));
a=a+0.1f;
}
return 0;
}
i=0, equal!
i=1, equal!
i=2, equal!
i=3, equal!
i=4, equal!
i=5, delta=0.0000000596
i=6, delta=0.0000000596
i=7, delta=0.0000000596
i=8, equal!
i=9, equal!