菠菜

敏感而豁达

浮点数的误差是怎么形成的

在这里我用下面的简单的程序一步一步说明,程序很简单,应该很容易看懂。

#include <stdio.h>

// 浮点数的误差是怎么形成的
void main()
{
	double d =	1099511627775.9998779296875;
	printf("d = %.14f\n", d);
	// 问题描述:下面的数输出时并不是在编码时所给的数,有误差,是怎么形成的呢?
	float f =	1099511627775.9998779296875F;
	printf("d = %.14f\n", f);

	// 这是我的机子的情况
	printf("sizeof char is %d\n", sizeof(char)); // 1
	printf("sizeof int is %d\n", sizeof(int)); // 4
	printf("sizeof long is %d\n", sizeof(long)); // 4
	printf("sizeof float is %d\n", sizeof(float)); // 4
	printf("sizeof double is %d\n", sizeof(double)); // 8

	// 先理论学习一下

	/*
	参考自http://en.wikipedia.org/wiki/IEEE_754
	IEEE 754-1985标准规定,float用32位二进制编码表示,其中
	最高位31位为符号位,0表示正,1表示负
	30位-23位,共8位,为阶码,通过移码的方式保存指数部分,即指数值加上偏移量127(01111111)
	22位-0位,共23位,为尾数,存储的是规范化二进制数(1.b1b2...bn),其中整数位前导1不保存,只保存小数位b1b2...b23
	*/
	// 所以定义以下联合体
	union 
	{
		int i;
		float f;
		unsigned char ca[4]; // 高索引表示数的高位
	}dt;
	dt.i = 0x01020304;
	printf("%x %x %x %x\n", dt.ca[0], dt.ca[1], dt.ca[2], dt.ca[3]);
	// 这里可以看出,数组从小到大依次表示数位从小到大,所以后面从大到小输出,这个是大小端的问题,有兴趣的请谷歌

	dt.f = -1;
	printf("%x %x %x %x\n", dt.ca[3], dt.ca[2], dt.ca[1], dt.ca[0]);
	/*
	当浮点数为-1的时候,十六进制输出为 BF 80 0 0
	转换为二进制数为 10111111 10000000 00000000 00000000
	重新将比特位分组 1 01111111 00000000000000000000000
	得
	符号位1,表示负数
	阶数01111111b,即127,减去127,得0
	尾数全为0,表示1.0b
	所以整体表示 -1*1.0*2^0,即-1
	*/

	dt.f = 33;
	printf("%x %x %x %x\n", dt.ca[3], dt.ca[2], dt.ca[1], dt.ca[0]);
	/*
	当浮点数为33的时候,十六进制输出为 0x 42 4 0 0
	转换为二进制数为 01000010 00000100 00000000 00000000 b
	重新将比特位分组 0 10000100 00001000000000000000000 b
	得
	符号位0,表示正数
	阶数10000100b,即132,减去127,得5
	尾数为1.00001b
	所以整体表示1.00001b左移5位,即100001b,即33
	*/

	dt.i = 1024;
	printf("%x %x %x %x\n", dt.ca[3], dt.ca[2], dt.ca[1], dt.ca[0]);
	printf("%f\n", dt.f);
	printf("%E\n", dt.f);
	/*
	当整数为33的时候,十六进制输出为 0x 0 0 4 0
	转换为二进制数为 00000000 00000000 00000100 00000000 b
	重新将比特位分组 0 00000000 00000000000010000000000 b
	得
	符号位0,表示正数
	阶数00000000b,即0,减去127,得-127
	尾数为1.0000000000001b
	所以整体表示1.0000000000001b右移127位,即0.(126个0)10000000000001b,这是个很小的数,科学表示法为1.434930E-042。
	*/

	// 真正探索啦

	dt.f = 1099511627775.9998779296875F;
	printf("%x %x %x %x\n", dt.ca[3], dt.ca[2], dt.ca[1], dt.ca[0]);
	printf("%.30E\n", dt.f); // 原数位小于30位,所以这里的表示是精确的
	/*
	当浮点数为1099511627775.9998779296875的时候,十六进制输出为 0x 53 80 0 0
	转换为二进制数为 01010011 10000000 00000000 00000000 b
	重新将比特位分组 0 10100111 00000000000000000000000 b
	得
	符号位0,表示正数
	阶数10100111b,即167,减去127,得40
	尾数为1.0b
	所以整体表示2的40次方,即1099511627776,跟输出1.099511627776000000000000000000+012是相符的。
	所以这里的误差根源为:1,减少幂次到39,置所有尾数位为1,则数只能表示到(2-2^(-23))*2^39,为1099511562240,比原数小了65535.9998779296875。
	这就是浮点数在表示大数时的误差,随着数越大,误差越大。
	同理可得出double类型的数在表示时,虽然精度比float小,但随着数的增大,误差也会越来越明显。
	*/

	getchar();
}

posted on 2012-09-20 17:53  ~菠菜~  阅读(670)  评论(0编辑  收藏  举报

导航