• js中小数四则运算精度问题原因及解决办法


    先带着问题看如下2张截图(一脸懵逼)

    在这里插入图片描述
    在这里插入图片描述
    本篇文章主要参考自该博主:https://www.cnblogs.com/zm-blogs/p/12909096.html

    js number类型

    JS 数字类型只有number类型,number类型相当于其他强类型语言中的double类型(双精度浮点型),不区分浮点型和整数型。

    number类型不同进制

    number 有四种进制表示方法,十进制,二进制,八进制和十六进制

    二进制: 0B或者0b (数字0和字母B或者小写字母b) ,后接1或者0表示二进制数
    八进制: es5下禁止表示八进制数会自动转化为十进制数,es6用0o ,后接小于8的数字表示八进制
    十六进制: 以0x或者0X开头,后接0-9数字和a-e五个英文字母
    十进制:默认直接输入0-9都是十进制数

    二进制和十进制互转请参考https://zhuanlan.zhihu.com/p/75291280

    小数,浮点数,及小数运算

    由于Js的所有数字类型都是双精度浮点型(64位)采用 IEEE754 标准

    64位二进制数表示一个number数字

    其中 64位 = 1位符号位 + 11位指数位 + 52位小数位
    在这里插入图片描述

    符号位:用来表示数字的正负,-1^符号位数值,0为正数,1为负数

    指数位:一般都用科学计数法表示数值大小,但是这里一般都是2进制的科学计数法,表示2的多少次方

    小数位:科学计数法前面的数值,IEEE745标准,默认所有的该数值都转为1.xxxxx这种格式,优点是可以省略一位小数位,可以存储更多的数字内容,缺点是丢失精度
    在这里插入图片描述

    浮点数的运算精度丢失问题就是因为,浮点数转化为该标准的二进制的过程中出现的丢失

    整数转二进制

    好理解,除二取余法,3(10进制)转二进制

    3 / 2 = 1 // 余1
    1 / 2 = 0 // 余1
    倒着从下往上看 3(十进制) = 11(二进制),二进制转10进制,则是 11 = 1 * 2^0 + 1 * 2^1 = 1 + 2 = 3
    
    • 1
    • 2
    • 3

    整数10转2的口诀是除2取余,从下往上
    小数10转2的口诀是乘2取整数,小数继续乘2,继续取整数,直到小数部分为0,最后的结果为整数补0.从上往下看

    问题来了,小数如何转二进制!!

    由于也需要转化为指数形式,例如 1/2 = 1 * 2^-1, 1/4 = 1 * 2^-2,所以小数的转化二进制过程是通过判断小数是不是满 1/2,1/4,1/8(因为这些数才能乘2后小数部位取余为0)以此类推,换成数学公式就是,乘二取整法

    0.125的二进制

    0.125 * 2 = 0.25 ====== 取出整数部分0
    0.25 * 2 = 0.5 ====== 取出整数部分0
    0.5 * 2 = 1.0 ====== 取出整数部分1
    所以0.125转化成二进制是:0.001

    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.4*2=0.8======取出整数部分0
    
    0.8*2=1.6======取出整数部分1
    
    0.6*2=1.2======取出整数部分1
    
    接下来会无限循环
    
    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.1转化成二进制是:0.0001 1001 1001 1001…(无限循环)
    
    0.1 => 0.0001 1001 1001 1001…(无限循环)
    
    同理0.2的二进制是0.0011 0011 0011 0011…(无限循环)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    OK ,转化为二进制之后,开始准备运算
    计算机中的数字都是以二进制存储的,二进制浮点数表示法并不能精确的表示类似0.1这样 的简单的数字

    如果要计算 0.1 + 0.2 的结果,计算机会先把 0.1 和 0.2 分别转化成二进制,然后相加,最后再把相加得到的结果转为十进制

    但有一些浮点数在转化为二进制时,会出现无限循环 。比如, 十进制的 0.1 转化为二进制,会得到如下结果:

    0.1 => 0.0001 1001 1001 1001…(无限循环)
    
    0.2 => 0.0011 0011 0011 0011…(无限循环)
    
    • 1
    • 2
    • 3

    而存储结构中的尾数部分最多只能表示 53 位。为了能表示 0.1,只能模仿十进制进行四舍五入了,但二进制只有 0 和 1 , 于是变为 0 舍 1 入 。 因此,0.1 在计算机里的二进制表示形式如下:

    0.1 => 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 101
    
    0.2 => 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 001
    
    • 1
    • 2
    • 3

    用标准计数法表示如下:

    0.1 => (1)0 × 2^4 × (1.1001100110011001100110011001100110011001100110011010)2
    
    0.2 => (1)0 × 2^3 × (1.1001100110011001100110011001100110011001100110011010)2
    
    • 1
    • 2
    • 3

    在计算浮点数相加时,需要先进行 “对位”,将较小的指数化为较大的指数,并将小数部分相应右移:

    最终,“0.1 + 0.2” 在计算机里的计算过程如下:

    在这里插入图片描述

    经过上面的计算过程,0.1 + 0.2 得到的结果也可以表示为:

    (1)0 × 22 × (1.0011001100110011001100110011001100110011001100110100)2=>.0.30000000000000004
    
    • 1

    通过 JS 将这个二进制结果转化为十进制表示:

    (-1)0 * 2-2 * (0b10011001100110011001100110011001100110011001100110100 * 2**-52); //0.30000000000000004
    
    • 1
    console.log(0.1 + 0.2) ; // 0.30000000000000004
    
    • 1

    这是一个典型的精度丢失案例,从上面的计算过程可以看出,0.1 和 0.2 在转换为二进制时就发生了一次精度丢失,而对于计算后的二进制又有一次精度丢失 。因此,得到的结果是不准确的。

    但是问题是:几乎所有的编程语言浮点数都是都采用IEEE浮点数算术标准~

    JAVASCRIPT中的解决办法

    (1)原生方法类

    因为浮点数转换的时候小数乘二取整会有无限循环的情况,但是整数除二取余是不会的,所以整数部分不会出现精度丢失问题
    方案1:将小数转化为整数进行运算
    实现思想:先将小数转化为字符串,判断小数部分位数,并且将运算两边小数同时乘以10最大小数位数,再将最后结果除以10最大小数位数

    因为小数精度过高的情况下可能出现无限循环,出现截断或者进位等情况
    方案2:限制精度,只保留小数部分位数,减小精度出现的误差问题
    方法:Number.toFixed()
    代码:

    		console.log((0.1 + 0.2).toFixed(12) == 0.3)
            // true
            console.log((0.1 + 0.2).toFixed(12))
            // 0.300000000000
            console.log((2.4/0.8).toFixed(12))
            // 3.000000000000
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    注意:toFixed之后会转换为字符串格式,可以再使用parseFloat转换为小数 parseFloat((a+b).toFixed(2))

    (2)第三方封装类库math库

    统一配置math.js

        math.config({
            number: 'BigNumber',  
            // 'number' (default), 
            precision: 20         
        });
        // 转换数字类型
        var temp = math.bignumber(a) * math.bignumber(b)
        // 提取数字类型,不然会是一个math对象
        var result = math.number(temp)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    bignumberbigdecimal

    将js原生number类型转为bignumber,big,decimal等封装类型,(decimal是8421 BCD编码,bignumber是支持高精度的数据类型,实现原理则大概是用类数组存储数据位,保持精度的可靠性)

  • 相关阅读:
    Java电子招投标采购系统源码-适合于招标代理、政府采购、企业采购、等业务的企业
    jvm深入研究文档--jvm分区以及职责
    Python 中的变量Variable
    前端开发面试-css篇
    linux自定义命令-通过关键字批量杀死进程
    Jmeter——结合Allure展示测试报告
    TinyOs操作系统---第3章 任务中断间的共享资源保护
    insightface实战:画出嘴巴和眼睛的mask
    太绝了!这份Python爬虫入门『最强教程』当之无愧
    客户端负载均衡_什么是负载均衡
  • 原文地址:https://blog.csdn.net/hzxOnlineOk/article/details/126153332