C浮点数精度丢失问题.doc
文本预览下载声明
C#浮点数精度丢失问题
C#中的浮点数,分单精度(float)和双精度(double):
float 是 System.Single 的别名,介于?-3.402823e38 和 +3.402823e38 之间的32位数字,符合二进制浮点算法的 IEC 60559:1989 IEEE 754 标准;
double 是 System.Double 的别名,介于?-1.座机电话号码486232e308 和 +1.座机电话号码486232e308?之间的64位数字,符合二进制浮点算法的 IEC 60559:1989 IEEE 754 标准;?
我们知道,计算机只认识 0 和 1,所以数值都是以二进制的方式储存在内存中的。
(对于人脑和计算机哪个聪明,个人更倾向于选择人脑,计算机只是计算得快,而且不厌其烦而已!)
所以要知道数值在内存中是如何储存的,需先将数值转为二进制(这里指在范围内的数值)。
根据 IEEE 754 标准,任意一个二进制浮点数 V 均可表示为:V -1 ^ s * M * 2 ^ e 。
其中 s 0, 1 ;M? [1, 2 ;e 表示偏移指数。
以 198903.19 10 为例,先转成二进制的数值为:1XXXXXXXXXX1110111.001XXXXXXXXXX011 2 (截取 16 位小数),采用科学记数法等于?1.1XXXXXXXXXX1XXXXXXXXXX00010100011 * 2 ^ 17 (整数位是 1),即 198903.19 10 -1 ^ 0 *?1.1XXXXXXXXXX1XXXXXXXXXX00010100011 * 2 ^ 17 。
整数部分可采用 除2取余法,小数部分可采用 乘2取整法。
从结果可以看出,小数部分 0.19 转为二进制后,小数位数超过 16 位(我已经手算到小数点后 32 位都还没算完,其实这个位数是无穷尽的)。
由于无法得到完全正确的数值,这里就引申出浮点数精度丢失的问题:
/* 程序段1 */
float num_a 198903.19f;
float num_b num_a / 2;
Console.WriteLine num_a ;
Console.WriteLine num_b ;
这段程序代码,我们预想中正确的结果应该是:198903.19 和 99451.595。
但结果居然是!!!原因下面将讲到 ...?
这里介绍另一种转小数部分的方法,有兴趣可以看下:
假如结果要求精确到 N 位小数,那么只需要将小数部分乘以 2 的 N 次方(例如 N 16,0.19 * 2 ^ 16 ,得到 12451.84)。
取整数部分(12451),按整数的方法转为二进制,得到 1XXXXXXXXXX011,不足 N 位在高位用 0 补足。
结果 0.19 精确到 16 位后,用二进制表示为 0.001XXXXXXXXXX011。
可以看出,若是小数部分乘以 2 的 N 次方后,可以得到一个整数,那么这个小数可以用二进制精确表示,否则则不可以。
(原理很简单,根据二进制小数位转十进制的方法,反推回去就可以得到这个结果)?
在内存中,float 和 double 的储存格式是一致的,只是占用的空间大小不同。
float 总共占用 32 位:
从左往右,第 1 位是符号位,占 1 位;第 2-9 位是指数位,占 8 位;第 10-32 位是尾数位,占 23 位。
double 总共占用 64 位,从左往右第 1 位也是符号位,占 1 位;第 2-12 位是指数位,占 11 位;第 13-64 位是尾数位,占 52 位。?
其中,符号位(即上文的 s,下同),0 代表正数,1 代表负数。
对于 float,8位指数位的值范围为 0-255 10 ,由于指数(即上文的 e,下同)可正可负,而指数位的值是一个无符号整数。根据标准规定,储存时采用偏移值(偏移值为127)的方法,储存值为指数 + 127。例如 0111 0011 2 表示指数 -12 10 ( -12 +?127? 115),1000 1011 2 表示指数 12 10 (12+ 127 139)。 另外,IEEE 754 规定(同样适用于 double):
当指数全为 0 时,如果尾数全为 0,表示?±0(正负取决于符号位),如果尾数不全为 0,计算时指数等于 -126,尾数不加上第一位的1,而是还原为 0.xxxxxx 的小数,表示更接近 0 的小数;
当指数全为 1 时,如果尾数全为 0,表示 ±无穷大(正负取决于符号位),如果尾数不全为 0,表示这不是一个数(NaN)。 同样的,对于 double,11位指数位,储存时采用的偏移值为 1023。
显示全部