本篇博文完整收录在《C++自学笔记(下册)通用编程》附录B.3。这本书暂时还未出版,因此与该书有关的任何图片、表格、代码、文章(包括本篇)都谢绝转载。
每一个计算机初学者都听过这样一句话:浮点数通常被存储成近似值。我猜,很多初学者可能都曾经有过这样一个疑问:浮点数究竟有多近似呢?
在经历多年自学之后,我发现一个技巧,用于揭示浮点数的近似程度。下面仅以单精度浮点数为例,请读者自行推广到其它精度浮点数。
国际标准IEEE754采用了科学记数法格式(而不是定点格式)来存储二进制浮点数。即,每个二进制浮点数都表示为“±尾数×2的指数”形式。用最高二进制位存储符号(0代表非负、1代表负),用固定位数来存储加权指数和除权尾数,并依据固定规则利用加权指数算出指数、利用除权尾数算出尾数。依据加权指数的不同,可以将浮点数分为以下三类:
规格化:加权指数既不全为0也不全为1。用于表示最普通的浮点数。例如,14.137f。 |
非规格化:加权指数全为0。用于表示非常接近于0(即绝对值非常小)的浮点数。例如,2.9E-40f。 |
特殊值:加权指数全为1。用于表示无穷大或者非数。例如,表达式6.08f/0.0f的值、0.0f/0.0f的值。 |
对于前两类浮点数,在利用加权指数和除权尾数去计算指数和尾数时所用到的固定规则存在区别:
本篇暂时只讨论规格化(即,第一类)浮点数之中的单精度类型。
单精度浮点数占据32个二进制位,含有1位符号、8位加权指数和23位除权尾数。
通俗地说:绝对值不是特别大也不是特别小的单精度浮点数通常都属于规格化。
准确地说:绝对值位于取值范围[2的-126次方, 2的128次方)之内的单精度浮点数都属于规格化。注意,该范围大约是[1.17549435082228750797×10的-38次方, 3.402823669209384634633×10的38次方)。
下面的示例只展示了一个规格化的单精度浮点数的近似程度。其它两种,需要其它示例来展示,此处暂时省略。
1、将单精度浮点数14.137f转换为二进制位序列。
第(1)步,将14.137f拆分成整数部分14和小数部分0.137f。
第(2)步,利用“除以2取余法”将14转换为二进制位序列1110(2),从最左1算起共有4位。
对于14来说,“除以2取余法”的步骤是:用2作为除数去循环地除以被除数14或者除以前一轮次所产生的商,直到商变为0为止。如果商不为0,则将本论次的商变为下一轮次的被除数。所有轮次产生的余数(0或1)“颠倒顺序”书写在一起,就能得到所需的二进制结果。完整计算过程如下:
轮次序号 | 被除数 | 除数 | 商 | 余数 |
---|---|---|---|---|
第1轮 | 14 | 2 | 7 | 0 |
第2轮 | 7 | 2 | 3 | 1 |
第3轮 | 3 | 2 | 1 | 1 |
第4轮 | 1 | 2 | 0 | 1 |
因此,14转换为1110(2)。
第(3)步,利用“乘以2取整法”将0.137f转换为二进制,确保从最右位算起共有24-4=20位小数。
对于0.137f来说,“乘以2取整法”的步骤是:用2作为乘数去循环地乘以被乘数0.137或者乘以前一轮次所产生的剩余小数,直到积变为0为止。如果某个轮次的积大于1,则从中将整数1提取出来,否则只能将整数0提取出来;剩余小数则等于本轮次的积减去本轮次提取到的整数;然后将本轮次的剩余小数作为下一轮次的被乘数。最后将所有轮次提取的整数(0或1)按照正常顺序(无需颠倒顺序)书写在一起,就能得到所需的二进制结果之中的小数部分。
对于0.137f来说,为了确保所得结果含有20位,进行“乘以2取整法”时