这篇文章是作者自己总结的。由于作者水平所限,难免有错误。欢迎大家指正,在此感激不尽。说到浮点数,人人又爱又恨。喜欢它,因为只有它能方便地使用小数;讨厌它,因为它不能准确地表示小数。以PHP为例:对于floor((0.1+0.7)*10)这样的函数调用,按照数学老师晚死的原则,每个人都可以得到8的结果。但实际上?它将返回7.数学老师的棺材板。..(╯‵′)╯︵┻━┻可是为什么会这样呢?这要从浮点数的特性说起。我们都知道一切都是二元的。在计算机中,一切都是二进制表示的。假设一个4字节整数十进制数8在大端机器中表示为00000000000000000000000000001000(0x0000008)。将十进制整数转换为二进制数非常容易。但是小数呢?比如我们要表示1.75,在计算机中应该怎么存储呢?显然,它不能像整数那样存储。让我们回忆一下小数是如何用十进制计算的。这就是我们计算上述1.75的方法:1×10^0+7×10^-1+5×10^-2。然后我们按照同样的规则计算二进制的小数部分:0.75=1/2+1/4,即1×2^-1+1×2^-2,加上前面的整数部分,则整个公式变成1×2^0+1×2^-1+1×2^-2,写成二进制形式就是1.11。因此,1.75的二进制表示为1.11。小数转二进制,与除以二取整数部分四舍五入相反。0.75*2=1.5->10.5*2=1->1,所以我们也可以得到1.11。科学计数法很好,我们已经知道如何表示十进制二进制了。热,问题来了。学过C语言的同学都知道,一个float只有4个字节,一个Double也只有8个字节。所以,似乎表示小数的范围很有限。在数学老师晕倒在厕所之前,我们应该记得小数中有这么一个东西——科学记数法,我们可以很方便地用它来表示大的小数。那么,同理,我们也可以用它来表示浮点数。让我们首先回顾一下科学记数法的表示。假设我们有一个数字17500,我们可以用科学计数法将其表示为1.75×10^4。让我们跟随葫芦。在二进制数中,假设有一个数字是11010。让我们将其映射为十进制。十进制乘以10,然后二进制乘以2,我们对应的可以写成1.101×2^100。是的,其实就是这么简单。那么有人会问,为什么不写成0.1101×2^101呢?我们再回忆一下,在十进制的科学计数法中,有没有规定整数部分的取值范围是[1,10)。那么对应到我们的二进制数,这个规则就可以变成[1,2),没错,对应关系就这么简单。浮点数还好,我们现在知道用二进制表示小数,用科学记数法表示二进制小数。那么,我们离在计算机内存中存储数字只有一步之遥了。如果我们要把所有的东西都存储在内存中,那么我们就需要合理分配内存空间。浮点数有两种,一种是单精度浮点数(float),占用4字节内存。其中,1位为符号位,8位为指数(幂),23位为尾数(小数部分)。细心的人可能会发现好像没有整数部分?但别担心,这就是上面的规则派上用场的地方。当整数部分在[1,2)之间时,只能取1的值。那么,对于这个值,我们是否可以将其作为默认值,不记录在浮点数的表示中呢?而这样一来,我们的浮点数的精度就多了一位(小数部分的位数决定了精度)。这种表示称为隐式从1开始的表示。归一化和非归一化的偏移值此时,我们发现第一位是浮点数的符号,所以,对于科学计数法来说,序号也需要有符号。单精度时,指数只有8位;在双精度中,指数只有11位。如果我们把指数码表示成补码,那么我们可以表示的数的范围就会缩小,显然不划算。于是,偏移值就诞生了。内存中归一化浮点数表示中的归一化值(指数代码不全为0或1),指数代码不是2的幂,而是计算的结果。这个计算公式是e-Bias,其中Bias是偏置值,e是指数在浮点数中的二进制表示。Bias的取值为2^k-1-1(单精度为127,双精度为1023),所以e-Bias的取值范围为[-126,127](单精度)和[-1022,1023](双精度)。事实上,如果你对补码有很好的理解,你应该能看出来这实际上是省略了符号位的补码表示)。从上面隐含的1-startedrepresentation的尾数,我们可以计算出底数M=1+f。那么我们整个浮点数就可以写成这样一个表达式:M×(e-Bias)。非规范化值(序号全为0)我们可以用同一个公式来表示规范化值和非规范化值。然而,出于一些更方便的原因(我不会在这里深入),它们之间是有区别的。根据归一化计算,指数的值为0-Bias,但在这里,我们让指数的值等于1-Bias。同样,由于我们在指数上加1,整个浮点数将向左移动一位。那么,我们需要保持浮点数的值不变,M不再需要上面整数部分的1,所以M=f。同时我们会发现一个问题,就是+0.0和-0.0在浮点数的二进制表示上是不一样的。SpecialValues(所有指数都为1)最后还剩下一类数,就是所有指数都为1的情况。当小数为0时,浮点数为∞。当小数不为0时,浮点数的值为NaN,即不是数字(NotaNumber)。计算浮点数好了说了这么多,还是回到原来的问题,floor((0.1+0.7)*10)=7,我们先来看0.1的二进制表示。首先,我们将小数转换为二进制,得到0.000[1100]...让我们转换为浮点数的二进制表示。根据上面的规则,可以表示为科学计数法1.10011001100110011001100×2^-4,使得指数编码为-4+127=123,二进制表示为01111011。因此,整个浮点数的二进制表示号码是00111101110011001100110011001100(0x3dcccccc)。同样,0.7将表示为00111101001100110011001100110011(0x3d333333)。首先,我们要把数字的顺序用小序号对齐,然后加上尾数,这样我们得到的值为00111101111001100110011001100101,我们把它转成十进制,发现小于0.8。所以当我们做乘法和向下舍入时,它等于7。最后,其实浮点数有很多陷阱。因此,我们在使用浮点数时一定要小心。另外,在进行金额计算时,一定不能使用浮点数。本文是作者自己阅读的总结。由于作者水平所限,难免有错误。欢迎大家指正,在此感激不尽。参考文献《深入理解计算机系统(第 3 版)》第2.4.2节
