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

    最近因工程问题发现了浮点数在使用过程中的误区,在此与大家分享之

请看代码如下:  

//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

 

    由此可见,浮点数可能引发的问题在此得到了解决。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值