当前位置: 首页 > 科技观察

随笔:如何向女朋友解释为什么0.2+0.1在电脑上不等于0.3?

时间:2023-03-21 21:13:58 科技观察

为什么我们用电脑浏览器计算0.2+0.1,结果是0.30000000000000004,而0.1+0.6的结果却是0.7?这个问题一直是一个经典问题,甚至还有一个域名为https://0.30000000000000004.com/的网站,主要是解释这个问题。在这个网站中,列出了各种编程语言计算0.2+0.1的结果,部分摘录如下:可以看出,在各种语言中,计算0.2+0.1的结果惊人的一致,即魔法0.30000000000000004。实际上,当我们使用浏览器的控制台(F12)进行计算时,我们使用的是JavaScript语言进行计算。因此,前面的现象归根结底与具体的编程语言无关。主要问题是如何在计算机中表示小数,如何进行小数运算。我们知道计算机只知道0和1【为什么计算机只知道0和1】。如果现实世界中的内容要被计算机存储、计算或显示,就需要将其转换成二进制。在现实世界中,有两种主要类型的数字,整数和小数。在之前的文章【为什么计算机使用补码来存储数据】中,我们介绍了整数在计算机中的表示方式有很多种,比如原码、反码、补码等。整数包括正整数、负整数和零。计算机中存储的整数分为有符号数和无符号数。对于无符号数,使用哪种编码方法并不重要。对于有符号数的编码方式,常用补码。那么,一个十进制数要想得到它的二进制补码,就需要先通过一定的算法得到它对应的原码。十进制转二进制首先,我们来看看如何将十进制整数转换为二进制整数?十进制整数转二进制整数采用“除以2取余,倒序排列”的方法。具体方法是:将十进制整数除以2,得到商和余数;然后将商除以2,得到商和余数,依此类推,直到商小于1,然后将第一个余数作为二进制数二进制数的低位有效位,余数后面得到的,依次排列为二进制数的高位有效位。比如我们要把127转换成二进制,方法如下:那么,十进制小数转换成二进制小数是怎么计算的呢?十进制小数化为二进制小数采用“乘以2进位,按顺序排列”的方法。具体方法是:*用2乘以小数得到乘积*取出乘积的整数部分,再将剩余的小数部分乘以2得到另一个乘积*再取出乘积的整数部分,依此类推,直到乘积在的小数部分为零,此时二进制的最后一位为0或1。或者直到达到所需的精度。因此,十进制的0.625对应二进制的0.101。并非所有数字都可以用二进制表示。既然知道了如何将十进制数转化为二进制数,那我们是不是可以直接用二进制来表示小数呢?在我们前面的例子中,0.625是一个特殊的列,所以我们还是用同样的算法,请计算0.1对应的二进制值?我们发现0.1的二进制表示存在死循环,即(0.1)10=(0.000110011001100…)2这种情况下计算机就不能用二进制精度表示0.1了。也就是说,对于0.1这样的数,我们没有办法将它转换成一个确定的二进制数。为了解决一些小数不能用二进制准确表示的问题,IEEE754有了IEEE754规范。二进制浮点运算的IEEE标准(IEEE754)是自1980年代以来使用最广泛的浮点运算标准,并被许多CPU和浮点运算单元使用。浮点数和小数并不完全相同。在计算机中实际上有两种小数表示形式:定点数和浮点数。因为在位数相同的情况下,定点数的表示范围比浮点数小。因此在计算机科学中,浮点数用于表示实数的近似值。IEEE754规定了四种表示浮点值的方式:单精度(32位)、双精度(64位)、扩展单精度(超过43位,很少使用)和扩展双精度(79位以上,通常在80位)。最常用的是32位单精度浮点数和64位双精度浮点数。IEEE并没有解决小数不能精确表示的问题,只是提出了一种用近似值表示小数的方法,并引入了精度的概念。浮点数是由一串0和1组成的位序列(bitsequence)。逻辑上用三元组{S,E,M}来表示一个数N,如下图所示:S(sign)表示N的符号位。对应的值s满足:当n>0时,s=0;当n≤0时,s=1。E(exponent)表示N的指数,位于S和M之间。对应的e值也可以是正数或负数。M(mantissa)代表N的尾数,正好在N的末尾。M也称为有效数字(significand)、系数数字(coefficient),甚至称为“小数”。那么浮点数N的实际值n用下面的公式表示:上面的公式看起来很复杂,符号位和尾数位比较好理解,但是指数位就没那么好理解了。其实大家不用太纠结这个公式。你只需要知道,对于单精度浮点数,最多只能用32个字符来表示一个数,而双精度浮点数最多只能用64位来表示一个数。对于那些无限循环的二进制数,计算机使用浮点数来保留一定的有效数字,所以这个值只能是一个近似值,不能是实数。至于如何计算一个数对应的IEEE754浮点数,不是本文的重点,这里不再赘述。过程还是比较复杂的,需要进行排序、尾数和、归一化、舍入、溢出判断。但是这些其实不需要了解的太详细。我们只需要知道,小数在计算机中的表示是一个近似数,而不是一个真实的数值。根据精度,近似程度会有所不同。例如十进制数0.1对应双精度浮点数的二进制值:0.00011001100110011001100110011001100110011001100110011001。0.2是小数0.00110011001100110011001100110011001100110011001100110011。所以将两者相加:转换为十进制后,您将得到:0.30000000000000004!避免精度损失在Java中,用float表示单精度浮点数,用double表示双精度浮点数,表示近似值。因此,在Java代码中,不要使用float或double进行高精度计算,尤其是金额计算,否则容易出现资金流失问题。为了解决这样的精度问题,Java提供了BigDecimal进行精确计算。参考资料:https://0.30000000000000004.com/https://zh.wikipedia.org/zh-hans/IEEE_754https://www.h-schmidt.net/FloatConverter/IEEE754.html作者简介:谈编程,是一个公众号以漫画+音频的形式讲解枯燥的编程知识。致力于让编程变得更有趣。本文转载自微信公众号“漫花编程”,可通过以下二维码关注。转载本文请联系漫花编程公众号。