计算机组成原理——揭秘浮点数

1.问题引入

看一下这段代码。

for(double x = 0;x!=10;x+=0.1)
           System.out.println(x);

你是否会觉得程序最终会得到你想要的结果?很遗憾,运行后这段代码会一直循环下去,而不是停在了10的位置,想要了解问题的所在,我们就需要清楚浮点数在计算机中究竟是如何表示的。

2.早期计算机中如何表示浮点数?

类似于科学计数法,在计算机中任何一个二进制数N可以写成如下形式:
在这里插入图片描述
其中M为浮点数的尾数,是一个纯小数。
e为浮点数的指数,是一个整数。
那么在表示浮点数的时候需要给出确定精度的尾数,以及指明小数点位置的指数,这里习惯上称为阶码。最后还有有一个符号位来表示浮点数的符号,于是乎可以这样存储:
计算机组成原理——揭秘浮点数_第1张图片

3.IEEE 754标准下浮点数格式

后来为了便于软件移植,统一按IEEE 754标准,将32位和64位的浮点数分别按照如下格式表示:
计算机组成原理——揭秘浮点数_第2张图片
说明:
一:
S在这里指的是符号位,占用1位,S=0表示正数,S=1表示负数
二:
E为阶码,32位浮点数占8位,64位浮点数占11位。而且这个阶码通常采用移码的方式表示,这样做的好处是方便了两个指数的大小比较和对阶操作(浮点数间的运算会用到),所以将指数e变成阶码E时,需要加一个偏移量:
32位浮点数中E=e+127
64位浮点数中E=e+1023
三:
为了统一化,我们还需要对尾数进行一定的约束,否则同一个浮点数就可能出现许多不同的表示,比如0.1x21 ,0.01x22
所以我们规定当尾数不为0时,尾数域的最高有效位为1。

那么我们规格化浮点数为:
32位浮点数:
在这里插入图片描述
64位浮点数:
在这里插入图片描述

四:
针对一些特殊情况,我们认为当阶码和尾数都为0时,x的值为零,又因为有符号位的约束,所以有正0和负0;当阶码为全1,尾数为全0,x的值为无穷,同样,结合符号位有正无穷和负无穷之分。当然这些在IEEE 754标准中认为是非规格化。而对于像阶码全1尾数非0的情况,754标准规定这是无效的!用NaN符号表示。
所以在32位浮点数规格化时E表示的范围由28 去掉全0全1,只有1到~254,真正的指数e的值为-127 ~128;64位浮点数的指数e的值为-1023 ~1024

4.浮点数精度

通过刚才的了解我们知道了尾数确定精度,所以32位浮点数,尾数23位换算成十进制223 = 8388608,最多可以表示7位;同样的64位浮点数,尾数52位换算成十进制最多可以表示16位。

5.实例

说了一大堆,还是老样子,下面我们就
计算机组成原理——揭秘浮点数_第3张图片
例:一个二进制数0.11,转化为754标准32位浮点数二进制存储格式
这里是正数,所以S=0,那么表示为1.1x2-1 ,然后阶码E=e+127=126
转换为二进制就是01111110,尾数这里是1,然后补齐剩下的22位就是
10 000 000 000 000 000 000 000
所以最终结果就是
0 01111110 10 000 000 000 000 000 000 000

6.回看问题

现在我们回到开头的问题,将0.1转换为2进制存储:
0.1x2=0.2……取出整数部分0
0.2x2=0.4……取出整数部分0
0.4x2=0.8……取出整数部分0
0.8x2=1.6……取出整数部分1
0.6x2=1.2……取出整数部分1
0.2x2=0.4……取出整数部分0
0.4x2=0.8……取出整数部分0
0.8x2=1.6……取出整数部分1
0.6x2=1.2……取出整数部分1

可以看出它循环了,存储到计算机里就是1.10011…x2-4 ,这尾数32位浮点数就要23位,64位浮点数要52位,可不就精度损失了。
所以,x将会从9.999 999 999 999 98直接跳到10.099 999 999 999 98,完美的错过了。
计算机组成原理——揭秘浮点数_第4张图片

7.解决方案

解决的思路还是很多的,这里我举两个例子。
1.转化为可以计算的整形
咱们就变通一下,比如这段代码

double x = 2.0;
double y = 1.1;
System.out.println(x-y);

这结果输出妥妥的0.8999999999999999,气人不。
但是我们给它转个整形

System.out.println((x*10-y*10)/10);

这结果就对了。
2.Bigdecimal类
这是java里的一个很有用的大数类,可以处理任意精度的浮点数。不过很遗憾,它不能使用常用的算术运算符(+ - )之类的,而是需要使用特定的方法(add,substract)

BigDecimal x = new BigDecimal("2.0");
BigDecimal y = new BigDecimal("1.1");
BigDecimal z = x.subtract(y);
System.out.println("差是"+z);

结果也是0.9


给自己个鸡腿吧!
计算机组成原理——揭秘浮点数_第5张图片
参考资料:
[1]计算机组成原理/白中英,戴志涛主编.-5版.-北京: 科学出版社,2013.3

你可能感兴趣的:(计算机组成原理)