您的位置:首页 > 其它

由一个浮点数问题引发的致命问题

2013-09-05 20:47 204 查看
最近因工程问题发现了浮点数在使用过程中的误区,在此与大家分享之

请看代码如下:

//FileName: floatCompare.c
#include <stdio.h>

int main(int argc, char *argv[])
{
float fVal1, fVal2;
float Excent = 0.03;

fVal1 = 1592.15;
fVal2 = 1592.17;

if ((fVal2 - fVal1) <= (Excent - 0.01))
{
printf("0.02 <= 0.03-0.01\n");
}
else
{
printf("0.02 > 0.03-0.01\n");
}
}


编译执行如下:

$ gcc floatCompare.c

$ ./a.out

发现其输出结果为:0.02 > 0.03-0.01

很明显,这样的结果是我们不可接受的。因为代码按我们所想就应打印0.02 <= 0.03 - 0.01才对。问题出在哪里呢?存储在内存中的float类型的变量并非就是0.02000000或者0.0300000。由此可参考如下代码:

//FileName: FloatToUShort.c
#include <stdio.h>

#define NUM 24
const float gfWaveLen[];

unsigned short FloatToShort(float fWavelen);

int main()
{
int i = 0;
unsigned short usVal = 0x0000;
while (i < NUM)
{
usVal = FloatToShort(gfWaveLen[i]);
printf("%.2f : %.4f : 0x%04x : %d\n",gfWaveLen[i], gfWaveLen[i], usVal, usVal);
i ++;
}
}

unsigned short FloatToShort(float fWavelen)
{
unsigned short usWaveLen = 0x0000;
int iWaveLen = 0;
float fWaveTmp = 0;

#if defined(METHOD)    //方法A
fWaveTmp = fWavelen - 1500;
iWaveLen = fWaveTmp * 1000;
usWaveLen = ((iWaveLen %10) > 5) ? (fWaveTmp*100 + 1) : (fWaveTmp*100);
#else
usWaveLen = (fWavelen - 1500)*100;    //方法B-这个地方有问题
#endif

return usWaveLen;
}

const float gfWaveLen[NUM]=
{
1529.16, 1529.55, 1529.94, 1530.33, 1530.72, 1531.12,
1531.51, 1531.90, 1532.29, 1532.68, 1533.07, 1533.47,
1533.86, 1534.25, 1534.64, 1535.04, 1535.43, 1535.82,
1536.22, 1536.61, 1537.00, 1537.40, 1537.79, 1538.19
};


为了方便讨论,我们把传递给FloatToShort函数的浮点数变量假定都为1529.16-1538.19之间的数。

我们直接取(浮点数-1500)*100的方法来看看会出现什么样的结果:

$ gcc FloatToUShort.c
$ ./a.out
1529.16 : 1529.1600 : 0x0b64 : 2916
1529.55 : 1529.5500 : 0x0b8b : 2955
1529.94 : 1529.9399 : 0x0bb1 : 2993  @-1
1530.33 : 1530.3300 : 0x0bd8 : 3032  @-2
1530.72 : 1530.7200 : 0x0bff : 3071  @-3
1531.12 : 1531.1200 : 0x0c27 : 3111  @-4
1531.51 : 1531.5100 : 0x0c4f : 3151
1531.90 : 1531.9000 : 0x0c76 : 3190
1532.29 : 1532.2900 : 0x0c9d : 3229
1532.68 : 1532.6801 : 0x0cc4 : 3268
1533.07 : 1533.0699 : 0x0cea : 3306  @-5
1533.47 : 1533.4700 : 0x0d12 : 3346  @-6
1533.86 : 1533.8600 : 0x0d39 : 3385  @-7
1534.25 : 1534.2500 : 0x0d61 : 3425
1534.64 : 1534.6400 : 0x0d88 : 3464
1535.04 : 1535.0400 : 0x0db0 : 3504
1535.43 : 1535.4301 : 0x0dd7 : 3543
1535.82 : 1535.8199 : 0x0dfd : 3581  @-8
1536.22 : 1536.2200 : 0x0e25 : 3621  @-9
1536.61 : 1536.6100 : 0x0e4c : 3660  @-10
1537.00 : 1537.0000 : 0x0e74 : 3700
1537.40 : 1537.4000 : 0x0e9c : 3740
1537.79 : 1537.7900 : 0x0ec3 : 3779
1538.19 : 1538.1899 : 0x0eea : 3818  @11


我们的本意是取浮点数去掉1500后扩大100倍的值,但令我们惊奇的是区区24个浮点数中,使用方法B时(即usWaveLen = (fWavelen - 1500)*100;),我们得到的数值有11个在意料之外。观察其到小数点后4位精度的数值发现有的浮点数本来为1529.94,其对应的4位精度表示居然是1529.9399,代码并未进行“四舍五入”从而得到想要的2994的结果。还有一种情况,浮点数本来为1530.33,其4位精度表示也是1530.3300,这样标准的数值在使用方法B后也没有得到想要的3033,却得到了3032。

由此可见,程序并未按照我们预期的想法进行。

有一个好的方法就是扩展其精度,我们只需小数点后两位的精度即可,那么使用方法A,我们先将浮点数扩大至原来的1000倍,然后看其个位数是否为大于5,小于5则按照正常的浮点数扩大100倍进行处理,大于5则说明在此基础上还需加1,不然仍会丢失一位小数。

按照这个方法我们测试如下:

$ gcc FloatToUShort.c -DMETHOD
$ ./a.out
1529.16 : 1529.1600 : 0x0b64 : 2916
1529.55 : 1529.5500 : 0x0b8b : 2955
1529.94 : 1529.9399 : 0x0bb2 : 2994
1530.33 : 1530.3300 : 0x0bd9 : 3033
1530.72 : 1530.7200 : 0x0c00 : 3072
1531.12 : 1531.1200 : 0x0c28 : 3112
1531.51 : 1531.5100 : 0x0c4f : 3151
1531.90 : 1531.9000 : 0x0c76 : 3190
1532.29 : 1532.2900 : 0x0c9d : 3229
1532.68 : 1532.6801 : 0x0cc4 : 3268
1533.07 : 1533.0699 : 0x0ceb : 3307
1533.47 : 1533.4700 : 0x0d13 : 3347
1533.86 : 1533.8600 : 0x0d3a : 3386
1534.25 : 1534.2500 : 0x0d61 : 3425
1534.64 : 1534.6400 : 0x0d88 : 3464
1535.04 : 1535.0400 : 0x0db0 : 3504
1535.43 : 1535.4301 : 0x0dd7 : 3543
1535.82 : 1535.8199 : 0x0dfe : 3582
1536.22 : 1536.2200 : 0x0e26 : 3622
1536.61 : 1536.6100 : 0x0e4d : 3661
1537.00 : 1537.0000 : 0x0e74 : 3700
1537.40 : 1537.4000 : 0x0e9c : 3740
1537.79 : 1537.7900 : 0x0ec3 : 3779
1538.19 : 1538.1899 : 0x0eeb : 3819


由此可见,浮点数可能引发的问题在此得到了解决。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: