计算机系统是程序员的知识体系中最基础的理论知识,你越早掌握这些知识,你就能越早享受知识带来的"复利效应"。
你会发现前者的结果是0.30000000000000004,而后者的结果是0.3(当然了!)。那么,为什么计算机的准确度,连普通的电子计算器的都比不上?关键在于计算机与计算器使用了不同的数据表示方法。
对于整数来说,大家都知道8位有符号整数可以表示[-128,127],8位无符号整数可以表示[0,255],不管怎么样,8位二进制无论如何也只能表示256个整数。当需要表示257这个数,有且只有两个办法:
同理,我们把问题域扩展到全体实数,8位二进制同样也只能表示256个实数。假如约定这样一种8位编码:最低两位为小数区域,其余是整数区域,这样就有:
实数有两种表示格式,分别是定点数和浮点数。像上面说的这种约定整数部分和小数部分为固定位置的格式,就是定点数表示。
一个虚数上相当于两个实数,所以我们只需要关心实数在计算机中的表示即可,将一个实数装载入计算机需要分为三个步骤:
2.5.1转换为二进制数格式
这个步骤可能损失精度,换句话说,有些数会损失精度,而有些数不会,这取决于表示这个数需要的信息量和浮点数的存储格式
事实上,在0.1到0.9的9个小数中,只有0.5可以用二进制精确的表示。怎么理解呢?我们把1想象成一个圆,在十进制里,它可以划分为10等分;但在二进制里,它只能划分为2等分。也就是说二进制里一位,要么表示0,要么表示一半,它没有办法像十进制那样表示3/10、4/10、6/10......1的一半在十进制里是什么?0.5,所以二进制可以精确表示0.5,任何包含因子5的数都可以用二进制精确表示。无法精确表示的数字,存储值只能是真实值的近似表示。
提示:类似地,思考下十进制数格式可以精确表示1/3吗?
2.5.2转换为二进制科学计数法表示
2.5.3转换为IEEE754标准格式
IEEE754严格规定了尾数域和指数域可表示的大小,位数有限,意味着信息量是有限的。有些数需要的二进制数据量巨大,在这个步骤自然会损失精度,具体如下:
IEEE二进制浮点数算术标准(IEEE754)是广泛使用的浮点数运算标准,是大多数高级语言的现行浮点运算标准,例如C/C++、Java、JavaScript等。
对于一个科学计数法表示,当尾数a的整数部分有且仅有一位有效数字时,我们称它是规格化的。由于0在数字的最左边是无效的,而在二进制的世界里只有0和1,因此,二进制数使用规格化的科学计数法时,整数部分固定为1。
既然整数部分1是固定的,那么就没有必要存储整数部分的信息了。正因如此,IEEE754标准的浮点数采用隐藏位的策略,整数部分的1是隐含的,不需要占用一位比特,这样是使得尾数可以多一位有效数。
现在,我们已经知道浮点数划分的三个区域,现在我们来看这三个区域是如何求值的:
前面讲的是IEEE754浮点数的一般格式,其中最常用的是32位单精度浮点数和64位双精度浮点数,在高级语言中通常代表float和double两种数据类型(例如C/C++、Java),在有些语言中只有一种数字格式number(例如JavaScript/TypeScript)。
在IEEE754标准规定指数区域全0或全1为特殊值,具体如下:
为什么0.1+0.2!=0.3呢?首先,0.1和0.2这两个实数无法用二进制精确表示。在二进制的世界里,只有包含因子5的十进制数才有可能精确表示,而0.1和0.2转换为二进制后是无限循环小数,在计算机中存储的值只能是真实值的近似表示,这里是第一次精度丢失;其次,计算机浮点数采用了IEEE754标准格式,IEEE754严格规定了尾数域和指数域可表示的大小,位数有限,意味着可表示的信息量是有限的,换句话说就会存在三种误差:上溢、下溢和舍入误差。而0.1+0.2的结果的尾数域部分刚好超过了尾数域位数,超过位数的部分舍去,存在舍入误差,这里是第二次精度丢失。
计算机系统系列完整目录如下(2023/07/11更新):
永远相信美好的事情即将发生,欢迎加入小彭的Android交流社群~