js小数运算会出现精度问题


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都是十进制数

number进制的转换

parseInttoString

  • toString() 方法接受一个值为 2~36 之间的整数参数指定进制,默认为十进制,将Number转为String
  • parseInt() 第二个参数接受一个值为 2~36 之间的整数参数指定进制,默认为十进制,将String转为Number
// toString转换,输入为Number,返回为String
var n = 120;
n.toString(); // "120"
n.toString(2); // "1111000"
n.toString(8); // "170"
n.toString(16); // "78"
n.toString(20); // "60"
0x11.toString(); // "17"
0b111.toString(); // "7"
0x11.toString(12);// "15"
// parseInt转换,输入为String,返回为Number
parseInt('110'); // 110
parseInt('110', 2); // 6
parseInt('110', 8); // 72
parseInt('110', 16); // 272
parseInt('110', 26); // 702
// toString和parseInt结合使用可以在两两进制之间转换
// 将 a 从36进制转为12进制
var a = 'ra'; // 36进制表示的数
parseInt(a, 36).toString(12); // "960"

OK,扯远了,小数,浮点数,及小数运算

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

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

其中 64位 = 1位符号位 + 11位指数位 + 52位小数位

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

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

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

大概可以理解为这张图

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

  • 整数转二进制

    好理解,除二取余法,7表示为 111 = 1x2^3 + 1x2^2 + 1x2^1

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

    由于也需要转化为指数形式,例如 1/2 = 1 * 2^-1, 1/4 = 1 * 2^-2,所以小数的转化二进制过程是通过判断小数是不是满 1/2,1/4,8/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.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…(无限循环)

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…(无限循环)

而存储结构中的尾数部分最多只能表示 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

用标准计数法表示如下:

0.1 => (−1)0 × 2^4 × (1.1001100110011001100110011001100110011001100110011010)2

0.2 => (−1)0 × 2^3 × (1.1001100110011001100110011001100110011001100110011010)2

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

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

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

(−1)0 × 2−2 × (1.0011001100110011001100110011001100110011001100110100)2=>.0.30000000000000004

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

(-1)0 * 2-2 * (0b10011001100110011001100110011001100110011001100110100 * 2**-52); //0.30000000000000004

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

这是一个典型的精度丢失案例,从上面的计算过程可以看出,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
      • 注意:toFixed之后会转换为字符串格式,可以再使用parseFloat转换为小数 parseFloat((a+b).toFixed(2))
  2. 第三方封装类库

    • math库

      • 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)
    • bignumber,big,decimal等

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

搞懂js中小数运算精度问题原因及解决办法的更多相关文章

  1. 让你彻底搞懂JS中复杂运算符==

    让你彻底搞懂JS中复杂运算符== 大家知道,==是JavaScript中比较复杂的一个运算符.它的运算规则奇怪,容易让人犯错,从而成为JavaScript中“最糟糕的特性”之一. 在仔细阅读了ECMA ...

  2. 帮你彻底搞懂JS中的prototype、__proto__与constructor(图解)

    作为一名前端工程师,必须搞懂JS中的prototype.__proto__与constructor属性,相信很多初学者对这些属性存在许多困惑,容易把它们混淆,本文旨在帮助大家理清它们之间的关系并彻底搞 ...

  3. 彻底搞懂 JS 中 this 机制

    彻底搞懂 JS 中 this 机制 摘要:本文属于原创,欢迎转载,转载请保留出处:https://github.com/jasonGeng88/blog 目录 this 是什么 this 的四种绑定规 ...

  4. 一文搞懂 js 中的各种 for 循环的不同之处

    一文搞懂 js 中的各种 for 循环的不同之处 See the Pen for...in vs for...of by xgqfrms (@xgqfrms) on CodePen. for &quo ...

  5. js做小数运算精度问题

    当js做小数运算时存在bug,大概是因为二进制和十进制转换之间的关系. bug如图 解决方案 1.运算结果后,乘以100再除以100.网上推荐这种方法但是乘以1000再除以1000依然存在精度问题 2 ...

  6. 晨叔技术晨报: 你真的搞懂JS中的“值传递”和“引用传递”吗?

    晨叔周刊,每周一话题,技术天天涨. 本周的话题是JS的内存问题(加入本周话题,请点击传送门). 图 话题入口 今天的技术晨报来,就来谈谈JS中变量的,值传递和引用传递的问题.现在,对于很多的JSer来 ...

  7. Python浮点数(小数)运算误差的原因和解决办法

    原因解释:浮点数(小数)在计算机中实际是以二进制存储的,并不精确.比如0.1是十进制,转换为二进制后就是一个无限循环的数:0.0001100110011001100110011001100110011 ...

  8. bootstrap4中bootstrap_treeview不显示图标原因以及解决办法

    1.bootstrap4中bootstrap_treeview不显示图标原因 查看过大神的博客,经过自己试验,插件依赖: bootstrap/3.3.7 jquery/3.3.1 <link h ...

  9. 一文搞懂js中的typeof用法

    基础 typeof 运算符是 javascript 的基础知识点,尽管它存在一定的局限性(见下文),但在前端js的实际编码过程中,仍然是使用比较多的类型判断方式. 因此,掌握该运算符的特点,对于写出好 ...

随机推荐

  1. 3.k均值的算法

    一.课堂练习 # 课堂练习 from sklearn.datasets import load_iris # 导入鸢尾花数据 iris=load_iris() iris iris.keys() dat ...

  2. 在IBM Cloud中运行Fabric

    文章目录 打包智能合约 创建IBM Cloud services 创建fabric网络 创建org和相应的节点 创建order org和相应节点 创建和加入channel 导入智能合约 上篇文章我们讲 ...

  3. mac OS 配置 svn服务器端

    在Windows环境下 一般使用Tortoise SVN来搭建svn环境 操作系统 mac OS High Sierra 10.13.6 在Mac环境下 由于Mac自带了svn的服务器端和客户端功能 ...

  4. 为物联网而生:高性能时间序列数据库HiTSDB商业化首发!

    为什么80%的码农都做不了架构师?>>>   摘要: 近日,阿里云宣布高性能时间序列数据库 (High-Performance Time Series Database , 简称 H ...

  5. puppet报告系统Dashboard部署及配置详解

    Puppet Dasshboard是由支持Puppet开发的公司Puppetlabs创建的,是Ruby on Rails程序.可以作为一个ENC(外部节点分类器)以及一个报告工具,并且正在逐渐成为一个 ...

  6. 啃算法:归并排序及JavaScript实现

    在学习归并排序之前,有必要了解分治法,因为归并排序正是应用了分治模式.(基本定义摘自<算法导论>) 一.分治法 1.思想: 将原问题分解为几个规模较小但类似于原问题的子问题,递归地求解这些 ...

  7. Linux 下如何产生core文件(core dump设置)

    转自:https://blog.csdn.net/star_xiong/article/details/43529637 今天在Linux下调试C程序时,出现段错误,习惯性的ls下当前目录,发现没有生 ...

  8. codeforce 270B Multithreading

    B. Multithreading Emuskald is addicted to Codeforces, and keeps refreshing the main page not to miss ...

  9. Appium-desktop 元素定位

    1.打开 appium-desktop ,点击 start session 2.打开后,点击屏幕右上角的搜索按钮 3.然后会打开配置页面,在本地服务配置信息同上面写的代码链接配置.填入正确的信息后,点 ...

  10. Jenkins 源代码管理(SVN)

    Subversion 安装插件 1.首先将本地的自动化用例打包上传 svn 2.配置 jenkins 源代码管理(每次执行 jenkins 时,会自动 check-ou t配置地址中的代码到 Jenk ...