zoukankan      html  css  js  c++  java
  • 为什么0.1+0.2=0.30000000000000004

    ​阅读本文大约需要8分钟...

    问题

    在计算机的世界里,可能有很多常人无法理解的事情。比如 0.1 + 0.2 = ?。来,告诉我你的答案。

    有的朋友看到这就迫不及待的说,这么简单的问题,很明显等于 0.3 啊,小学生都会算的好伐。你这是在侮辱我的智商?

    好吧,我来告诉你一个打脸的事实,0.1 + 0.2 还真不等于 0.3 。先别急着反驳我。

    打开你的任意一个浏览器(我用chrome做演示),F12打开console控制台,输入 console.log(0.1 + 0.2) 。如果你操作正确的话,你会看到以下的结果。

    file

    是不是感觉匪夷所思,what?为什么结果是0.30000000000000004,这是神魔鬼? 难道,我这么多年学习的数学知识,老师教的都是错的?

    别着急。其实,你的老师教的没错,在我们的世界中,0.1 + 0.2 确实是等于 0.3 的。但是,在计算机中,可就不是这么一回事了。待我娓娓道来。

    因为,我们在计算数学问题的时候,用的是十进制,计算出来结果是0.3没问题。但是,在计算机中用的是二进制,都是由0和1来组成。这就不得不提一下,十进制转换二进制了。

    二进制转换

    十进制小数转换二进制的步骤:(以10.25为例)

    1.先转换整数部分,除2直到商为0,倒数取余。

    10/2 ... 商5...余数0 
    5/2   ...商2...余数1
    2/2   ...商1...余数0
    1/2   ...商0...余数1
    

    倒数取余,就是1010

    2.再转换小数部分,乘2取整,直到小数部分为0.

    0.25*2 ... 0.50 ...整数0
    
    0.50*2 ... 1.0   ...整数1
    

    小数部分为0,结束,即为01

    因此10.25(10)转换成二进制,结果就是 1010.01(2)

    聪明的你,类比以上方法,应该可以动手去算一下十进制0.1转成二进制是多少了。

    0.1*2 ... 0.2 ...整数0
    0.2*2 ... 0.4   ...整数0
    0.4*2 ... 0.8 ...整数0
    0.8*2 ... 1.6   ...整数1
    0.6*2 ... 1.2 ...整数1
    0.2*2 ... 0.4   ...整数0
    

    等等,怎么感觉进入死循环了,小数部分乘以2,一直乘不到小数部分为0

    就像十进制中1/3,结果是0.3(3...)这样的问题一样,0.1转成二进制时也会存在精度问题,我们需要进行取舍。

    我们看一下0.1在计算机中是怎么存储的。对此,需要了解一下浮点数的概念。

    浮点数

    浮点数,顾名思义,小数点是浮动的数。千万不要以为浮点数就是小数。因为,在js中是没有整数和小数的概念的,其实整数也是以浮点数的形式表示的,只是小数部分为0而已。

    浮点数简单理解,就是类似于我们十进制中的科学计数法。在计算机中一般遵循IEEE 754标准。格式如下:

    (-1)^S * M * 2^E
    1. S表示符号位,当S=0时,为正数;当S=1时,为负数。
    2. M表示有效数字(尾数),大于等于1,小于2。
    3. E为指数(也叫阶码)。
    

    因此,上边的10.25(二进制1010.01)按照此格式表示即为 1.01001 * 2^3

    对于32位浮点数来说,符号位占一位,指数位占8位,尾数占23位
    对于64位浮点数来说,符号位占一位,指数位占11位,尾数占52位

    IEEE 754标准

    注意:IEEE 754标准规定,在保存尾数M时,第一位默认是1,因此可以被舍去,只存储后边的部分。例如,1.01001保存的时候,只保存01001,等到用的时候再把1加上去。这样,就可以节省一个位的有效数字。

    指数E在存储的时候也有些特殊。若为32位,指数占8位,则可表示的大小范围为0-255 。如为64位,指数占11位,范围为0-2047 。但是,指数是有正有负的,因此实际值需要在此基础上减去一个中间数。对于32位,中间数为127,对于64位,中间数为1023 。

    还是以1.01001 * 2^3 为例,若为32位浮点数,则需要保存成 3+ 127 = 130,即二进制的10000010,若为64位浮点数,则保存成 3+ 1023 = 1026 ,即二进制的10000000010。

    计算步骤

    好了。巴拉巴拉了这么多。终于,要进入我们今天的正题了。

    我们看一下 0.1 在计算机中是怎么用 IEEE 754标准存储的。

    十进制0.1转为二进制为0.0001100110011(0011循环),即 1.100110011(0011)*2^-4,因此符号位为0,尾数1.100110011(0011),阶码为 -4,实际存储为 -4+1023 = 1019 的二进制 1111111011

    0  01111111011  1001100110011001100110011001100110011001100110011010
    S    E指数             M尾数
    

    十进制0.2转为二进制为0.001100110011(0011循环),即 1.100110011(0011)*2^-3 ,存储时,符号位为0,尾数 1.100110011(0011),阶码为-3,实际存储为 -3+1023 = 1020 的二进制 1111111100。

    0  01111111100  1001100110011001100110011001100110011001100110011010
    S     E指数          M尾数
    

    接下来,计算 0.1 + 0.2 。

    浮点数进行计算时,需要对阶。即把两个数的阶码设置为一样的值,然后再计算尾数部分。其实对阶很好理解,就和我们十进制科学记数法加法一个道理,先把指数部分化成一样,再计算尾数。

    另外,需要注意一下,对阶时需要小阶对大阶。因为,这样相当于,小阶指数乘以倍数,尾数部分相对应的除以倍数,在二进制中即右移倍数位。这样,不会影响到尾数的高位,只会移出低位,损失相对较少的精度。

    因此,0.1的阶码为 -4 , 需要对阶为 0.2的阶码 -3 。尾数部分整体右移一位。

    原来的0.1
    0  01111111011  1001100110011001100110011001100110011001100110011010
    对阶后的0.1
    0  01111111100  1100110011001100110011001100110011001100110011001101
    

    然后进行尾数部分相加

       0  01111111100   1100110011001100110011001100110011001100110011001101
    +  0  01111111100   1001100110011001100110011001100110011001100110011010
    =  0  01111111100  10110011001100110011001100110011001100110011001100111
    

    可以看到,产生了进位。因此,阶码需要 +1,即为 -2,尾数部分进行低位四舍五入处理。因尾数最低位为1,需要进位。所以存储为:

    0  1111111101  1011001100110011001100110011001100110011001100110100
    

    最后把二进制转换为十进制,

    二进制为:
    1.1011001100110011001100110011001100110011001100110100 * 2^-2
    转为十进制为:
    2^-2 * (1*2^0 + 1*2^-1 + 0 + 1*2^-3 + 1*2^-4 + ...)
    最终结果为:
    0.3000000000000000444089209850062616169452667236328125
    因为精度问题,只取到:
    0.30000000000000004
    

    问题总结

    1.在十进制转换为二进制的过程中,会产生精度的损失。

    2.二进制浮点数进行对阶运算时,也会产生精度的损失。

    因此,最终结果才产生了偏差。

    看完的小伙伴,现在应该能理解,为什么0.1 + 0.2 ≠ 0.3 这个问题了吧。

  • 相关阅读:
    网站安全编程 黑客入侵 脚本黑客 高级语法入侵 C/C++ C# PHP JSP 编程
    【算法导论】贪心算法,递归算法,动态规划算法总结
    cocoa2dx tiled map添加tile翻转功能
    8月30日上海ORACLE大会演讲PPT下载
    【算法导论】双调欧几里得旅行商问题
    Codeforces Round #501 (Div. 3) B. Obtaining the String (思维,字符串)
    Codeforces Round #498 (Div. 3) D. Two Strings Swaps (思维)
    Educational Codeforces Round 89 (Rated for Div. 2) B. Shuffle (数学,区间)
    洛谷 P1379 八数码难题 (BFS)
    Educational Codeforces Round 89 (Rated for Div. 2) A. Shovels and Swords (贪心)
  • 原文地址:https://www.cnblogs.com/starry-skys/p/11824852.html
Copyright © 2011-2022 走看看