一、例子
首先我们去编译器试试 double a=1.9;通过添加监视查看a的值
会发现a的值是1.8999999…
二、开始今天的学习
在最开始学c++的时候并没有对浮点数进行很深入的学习,认为浮点不就是小数嘛,首先在C++的宏里面有
FLT_MAX 和FLT_MIN的定义,float是四字节的浮点数,后面我么会发现,四字节的浮点数有效数字为6-7位,能保证的是6位,与浮点数的个数相比,四字节能表示的浮点数简直微不足道,也就是说存在小数根本就不能按照浮点数的这一套标准来进行“完美”储存,用有限的字节数去储存无限的浮点数,因此就会发生精度丢失。
C/C++采用的是IEEE浮点标准,它以“二进制的科学表示法”表示一个小数:
其中M是一个整数部分仅有一位的二进制小数,例如1.011,表示十进制下的1.375。E表示该小数以2为底时的阶数。基于以上的表示方式,小数需要对三部分进行编码:表示符号的s,及阶码E、尾数码M。C++中的double类型三种编码所占的位数如图所示。
现在让我们按照IEEE浮点数表示法,一步步的将float型浮点数12345.0f转换为十六进制代码。在处理这种不带小数的浮点数时,直接将整数部转化为二进制表示:
1 11100010 01000000
也可以这样表示:
11110001001000000.0
然后将小数点向左移,一直移到离最高位只有1位,就是最高位的
1:1.11100010010000000
一共移动了16位,在布耳运算中小数点每向左移一位就等于在以2为底的科学计算法表示中指数+1,所以原数就等于这样:1.11100010010000000 * ( 2 ^ 16)
好了,现在我们要的尾数和指数都出来了。显而易见,最高位永远是1,这样尾数的二进制就变成了:11100010010000000最后在尾数的后面补0,一直到补够23位:
11100010010000000000000
12345.0f这个数是正的,所以符号位是0,那么我们按照前面讲的格式把它拼起来:
按照IEEE浮点数表示法,将float型浮点数123.456f转换为十六进制代码。对于这种带小数的就需要把整数部和小数部分开处理。整数部直接化二进制:100100011。小数部的处理比较麻烦一些,比如有一个十进制纯小数0.57826,
5是十分位,位阶是1/10;
7是百分位,位阶是1/100;
8是千分位,位阶是1/1000……,
这些位阶分母的关系是10^1、10^2、10^3……,现假设每一位的序列是{S1、S2、S3、……、Sn},在这里就是5、7、8、2、6,而这个纯小数就可以这样表示:n = S1 * ( 1 / ( 10 ^ 1 ) ) + S2 * ( 1 / ( 10 ^ 2 ) ) + S3 * ( 1 / (10 ^ 3 ) ) + …… + Sn * ( 1 / ( 10 ^ n ) )。把这个公式推广到b进制纯小数中就是这样:
现在一个二进制纯小数比如0.100101011就应该比较好理解了,这个数的位阶序列就因该是1/(2^1)、1/(2^2)、1/(2^3)、1/(2^4),即0.5、0.25、0.125、0.0625……。乘以S序列中的1或着0算出每一项再相加就可以得出原数了。现在你的基础知识因该足够了,再回过头来看0.45这个十进制纯小数,化为该如何表示呢?现在你动手算一下,最好不要先看到答案,这样对你理解有好处。
来看一下步骤:1 / 2^1位(为了方便,下面仅用2的指数来表示位),0.456小于位阶值0.5故为0;2位,0.456大于位阶值0.25,该位为1,并将0.45减去0.25得0.206进下一位;3位,0.206大于位阶值0.125,该位为1,并将0.206减去0.125得0.081进下一位;4位,0.081大于0.0625,为1,并将0.081减去0.0625得0.0185进下一位;5位0.0185小于0.03125,为0……问题出来了,即使超过尾数的最大长度23位也除不尽!这就是著名的浮点数精度问题了。