• 学习笔记—二进制和精度问题


    日常的学习笔记,包括 ES6、Promise、Node.js、Webpack、http 原理、Vue全家桶,后续可能还会继续更新 Typescript、Vue3 和 常见的面试题 等等。


    二进制

    所谓 二进制,就是只用 01 两个数字,在计数与计算时必须是 逢2进1 (如十进制的 10 就是二进制的 1010)

    计算机中所有的内容全都是以 二进制 的形式进行存储的,而数据也都是以 二进制 的形式来表现的。

    精度问题

    参考文献 Floating Point Math

    我们首先来思考一个问题,0.1 + 0.2 === 0.3 的输出结果是什么?

    来看一下这道最经典的面试题。

    console.log(0.1 + 0.2 === 0.3);
    

    这道题的结果是 false ,我们通过控制台打印可以发现, 0.1 + 0.2 的结果并不是 0.3

    console.log(0.1 + 0.2); // 0.30000000000000004
    

    导致这个问题的原因就和计算机的 二进制存储 有关。

    首先我们要先明确计算机数据存储的两个概念。

    • 计算机将所有数据以 二进制 的形式存储。
    • 计算机用 有限的大小 来存储数据。(因为现实生活中不存在无限大的内存或硬盘)

    计算机中的 **最小单位是 位(bit,比特) **。

    - 8 bit => 1 byte;
    
    - 1024 byte => 1 Kb;
    
    - 1024 Kb => 1 Mb;
    
    - 1024 Mb => 1 Gb;
    
    - 1024 Gb => 1 T;
    
    ...
    

    我们在进行数据存储时,一般以 字节 来进行存储。

    字节(byte) 的最大值和最小值是 0~255 ,也就是说取值范围是 255

    取值范围的 255 是因为计算机是通过 二进制来进行数据存储

    进制的转换

    二进制顾名思义就是 逢2进1,十进制就是 逢10进1

    又因为 8bit = 1byte,所以他们之间相差 255。

    二进制 转 十进制 其实就是 当前位的值乘以进制(2),次幂是当前所在位,也就是 1*2^n

    具体计算代码如下。

    let sum = 0;
    for (let i = 0; i < 8; i++) {
        sum += Math.pow(2, i)
    }
    console.log(sum); // 255
    

    计算公式如下。
    $$
    ∵ 12^n 且 n<8
    ∴ s = 1
    2^0 + 12^2 + ... + 12^7
    $$

    从 十进制 转 二进制,就是 当前数除以进制(2),并取余。然后将余数倒退,就是最终的结果。比如,5 的二进制就是 101

    5除以2取余数

    在代码中,我们一般使用 parseInt() 把 任意进制 转成 十进制。使用 toString() 把 十进制 转成 任意进制

    • 使用 0x 前缀表示 十六进制0o 前缀表示 八进制0b 前缀表示 二进制。如 0x64 表示 十进制的1000o555 表示 十进制的3650b1111 表示 十进制的15。
    • toString() 如果不填入参数,则默认转换成 十进制。
    // 任意进制 转成 十进制
    console.log(parseInt('20',10)); // 20
    console.log(parseInt('11',2)); // 3
    console.log(parseInt('20',16)); // 32
    // 十进制 转成 任意进制
    console.log((3).toString(2)); // 11
    console.log(3..toString(2)); // 11
    console.log((77).toString(8)); // 115 
    console.log((77).toString(16)); // 4d 
    console.log((17).toString(8)); // 21
    // 十六进制 转成 任意进制
    console.log((0x64).toString(2));  // 1100100
    console.log((0x32).toString(8)); // 62
    

    小数转换

    再回到我们最上面的题目。

    我们现在需要算的是 小数(浮点数),那么我们将小数转换成二进制进行存储,我们就需要用到 乘二取整法

    将 小数(浮点数)乘以2,若计算结果包含整数,则取出整数,记为1。反之则记为0。

    0.5 * 2 = 1 …… 0 ,则 0.5 的二进制为 0.1

    现在我们来看一下, 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
    // ... 以此类推
    

    我们可以发现,结果是一个无限循环的二进制数 0.0001100110011001100110011001100110011001100110011001101

    同理我们也可以算出,0.2 的二进制是 0.001100110011001100110011001100110011001100110011001101

    我们可以发现,0.10.2 的二进制差了一位,无线数在计算机内部存储时会有长度限制,所以最终会存在错位的情况。

    所以其十进制结果,最后会有几个 二进制单位的1 无法被计算。

    这样才会导致 0.1 + 0.2 === 0.30000000000000004

    精度问题的解决方案

    推荐使用 Math.js 进行处理,他支持 BigNumber 的数据类型。

    const math = require('mathjs');
    let big1 = math.bignumber(0.1);
    let big2 = math.bignumber(0.2);
    console.log(math.add(big1, big2)); // 0.3
    

    本篇文章由 莫小尚 创作,文章中如有任何问题和纰漏,欢迎您的指正与交流。
    您也可以关注我的 个人站点博客园掘金,我会在文章产出后同步上传到这些平台上。
    最后感谢您的支持!

  • 相关阅读:
    HTML5小游戏-绵羊快跑
    JavaScript模拟下拉菜单代码
    作业6
    作业5
    作业4
    作业三
    作业2(第二遍)
    sap jco3安装
    jmeter脚本文件(jmx)关联
    正则替换:删除空行、格式化Json/xml
  • 原文地址:https://www.cnblogs.com/moxiaoshang/p/15742397.html
Copyright © 2020-2023  润新知