转载请注明作者:phylips@bmy 出处:http://duanple.blog.163.com/blog/static/70971767200932185613881/
源自于以下问题:
float只能保证6位有效数字,那么下面的程序输出应该是多少呢?
int main()
{
float f = 0.1234567;
cout << f << endl;
cout << setprecision(7) << f << endl;
return 0;
}
我觉得应该是:
0.123457
0.1234570
但是在Dev C++ 4.9.9.2(g++3.4.2)下的运行结果是:
0.123457
0.1234567
setprecision(7)仅改变了cout输出的位数,为什么连float的精度也变了呢??
解答:
0.1234567在内存中的二进制模式如下0x3dfcd6de,ieee754标准
0
01111011
11111001101011011011110
1.11111001101011011011110*(2^(123-127))=0.000111111001101011011011110
实际上内存中存的是这样的一个二进制小数0.000111111001101011011011110,化成十进制是0.12345670163631439208984375 。可以看到前面7位都是精确的,所以有效数字是6位的说法,应该这样理解:这并非是一个绝对的说法,也就是说这里6位有效数字是指float可以保证前6位肯定有效(实际上是由尾数域的长度计算出来的),但这个的意思不是意味着第7位就一定就是无效的了。
以前好像说过,并非所有的十进制数都可以用二进制来表示,有些有限的十进制小数,表示成二进制实际上会变成了无限的了。至于这个二进制在不同的精度设置下,打印出是什么样子,就跟setprecision有关了,我就不无聊了
———————————————————————————–
首先之所以有以上开始的那个疑问,在于用十进制的思维去考虑计算机的二进制计算。也就是说所谓的保证6位有效数字,并不是说0.1234567变成ieee浮点数表示后,就成了0.123457了,而是指在向ieee浮点数格式转换的时候,保证6位有效数字,并不是说转换过程就是把十进制表示的第7位四舍五入就好了。
实际上编译器处理的过程是这样的,首先读取程序中的0.1234567,然后将其转换成ieee754的浮点数格式,转换的过程可以看成一个十进制向二进制的表示过程,根据ieee标准,可以确定应该转换到第几位,然后保留这些位就可以了,剩下位的处理就是一个舍入问题了。同时要注意到以下两点:
1.并非所有的有限十进制小数都可以转换为有限的二进制表示,能否转成有限位的二进制表示,取决于小数转换成最简分数时,其分母的质因子,如果质因子只有2则可以完成有限的二进制位转换。否则无法用一个有限的二进制小数表示原来的十进制小数。比如1/10就是无法用有限二进制小数表示的。这就意味着很多十进制看似位数简单的小数,无论如何也是无法用有限二进制表示的,也就意味着转换成二进制后,与原数必然存在误差。
2.实数是无限的,而ieee浮点数格式代表的二进制模式则是有限的,这也同样意味着一个二进制模式可能需要表示多个实数,也就意味着误差出现的必然。
比如上述的0.1234567由编译器转换为ieee格式的时候,根据ieee浮点格式,需要转换到从第一个非0位开始后的第24个位置,然后往后的那些位数就要舍掉了,如何舍掉呢?ieee同样规定了几种舍入方法,通常采用的是舍入到偶数。
根据标准,无法精确保存的值向最接近的可保存的值进行舍入。再看上面的0.1234567,的表示0.000111111001101011011011110 =0.12345670163631439208984375,我们来看它是不是最接近的呢,实际上是的,首先上面的是比原数大,所以我们看如果把原来的表示减1,则变成了0.000111111001101011011011101=0.123456694185733795166015625,显然这个不如上面那个接近,而且它小于了原数,也就说明了,其他的表示将比它们差的更多。
可见这样的转换,并不是随机的,而是一个确定的过程,当无法精确表示时,要保证一个最接近的表示,所以产生了上面看似很复杂的0.12345670163631439208984375 ,让人感到很疑惑,但是的确它就是可以保存的最接近原数0.1234567的那个。
另外,setprecision,只是决定了输出0.12345670163631439208984375的多少位?实际上这里面有暗含了一个二进制到十进制的转换。
——————————————————————————————————–
浮点数的舍入
任何有效数上的运算结果,通常都存放在较长的暂存器中,当结果被放回浮点格式时,必须将多出来的位元丢弃。 有多种方法可以用来执行舍入作业,实际上IEEE标准列出4种不同的方法:
- 舍入到最接近:会将结果舍入为最接近且可以表示的值。
- 朝+∞方向舍入:会将结果朝正无限大的方向舍入。
- 朝-∞方向舍入: 会将结果朝负无限大的方向舍入。
- 朝0方向舍入: 会将结果朝0的方向舍入。