js中数字运算结果与预期不一致的问题和解决方案

本文主要是和大家聊聊关于js中经常出现数字运算结果与预期结果不一致的问题,与及解决该问题的的方案。

一、问题现象

如:0.1 + 0.2的预期结果是0.3,但是在js中得到的计算结果却是0.30000000000000004,如下图所示

 如:0.3 - 0.2的预期结果是0.1,但是在js中得到的计算结果为0.09999999999999998,如下图所示

 要想明白出现上述问题的原因,还需要了解在js中数字是如何编码的。

二、js中的数字编码

JavaScript 的 数字Number 类型(可表示整数和小数)是一个双精度 64 位二进制格式 IEEE 754 值,类似于 Java 或者 C# 中的 double。这意味着它可以表示小数值,但是存储数值的大小和精度有一些限制。简而言之,IEEE 754 双精度浮点数使用 64 位来表示 3 个部分:

  • 1 比特符号位(sign)(0-正数,1-负数)
  • 11 比特指数位(exponent)(-1022 到 1023)
  • 52 比特尾数位(mantissa-小数部分)(表示 0 和 1 之间的数值)

尾数(也称为有效位数)是表示实际值的数值(有效数值)部分。指数是尾数应乘以的 2 的幂。将其视为科学计数法:

尾数使用 52 比特存储,在二进制小数中解释为 1.… 之后的数值。因此,尾数的精度是 2的-52次方幂(可以通过 Number.EPSILON 获得),或者十进制小数点后大约 15 到 17 位;高于该精度级别的算术需要舍入

一个数值可以容纳的最大值是 2的1024次方减1(基于二进制的指数为 1023,尾数为 0.1111...),这可以通过 Number.MAX_VALUE 获得。大于该值的数值,被特殊数常数 Infinity 取代。

只有在 范围内的整数才能在不丢失精度的情况下被表示(可通过 Number.MIN_SAFE_INTEGER 和 Number.MAX_SAFE_INTEGER 获得),因为尾数只能容纳 53 位(包括前导 1)。

 以上内容出自:Number - JavaScript | MDN

三、0.1+0.2不等于0.3的原因

通过上述介绍可以知道在二进制科学表示法中,双精度浮点数的小数部分最多只能保留52位,再加上前面的1,其实就是保留53位有效数字,剩余的舍去,遵从“0舍1入”的原则。


// 0.1 转化为二进制 
0.0 0011 0011 0011 0011...(0011无限循环) 

// 0.2 转化为二进制 
0.0011 0011 0011 0011 0011...(0011无限循环)

// 0.3 转化为二进制 
0.01 0011 0011 0011...(0011无限循环)

如上,0.1、0.2、0.3转化为二进制会出现无限循环,0.1+0.2 不等于 0.3 ,因为在 0.1+0.2 的计算过程中发生了两次精度丢失。第一次是在 0.1 和 0.2 转成双精度二进制浮点数时,由于二进制浮点数的小数位只能存储52位,导致小数点后第53位的数要进行为1则进1为0则舍去的操作,从而造成一次精度丢失。第二次在 0.1 和 0.2 转成二进制浮点数后,二进制浮点数相加的过程中,小数位相加导致小数位多出了一位,又要让第53位的数进行为1则进1为0则舍去的操作,又造成一次精度丢失。最终导致 0.1+0.2 不等于0.3

四、解决方案

1、简单粗暴的方法是使用toFixed设置保留小数位数(注意用toFixed得到的是string类型)

(0.1+0.2).toFixed(1) == 0.3    // true

2、将其转化为整数计算后,再转化为小数

(0.1*100+0.2*100)/100 === 0.3 // true

3、使用第三方库:bignumber.js

bignumber.js APIhttps://mikemcl.github.io/bignumber.js/#bignumberbignumber.js - npmA library for arbitrary-precision decimal and non-decimal arithmetic. Latest version: 9.1.1, last published: 2 months ago. Start using bignumber.js in your project by running `npm i bignumber.js`. There are 5883 other projects in the npm registry using bignumber.js.https://www.npmjs.com/package/bignumber.js用法很简单

npm install bignumber.js // 安装依赖包bignumber.js

import BigNumber from "bignumber.js"; // 引进依赖包
let x = new BigNumber(0.1) // 使用构造函数BigNumber
let result = Number(x.plus(0.2)) // 结果为0.3,注意得到的结果为bignumber类型,需要调用通过Number转化

注意使用bignumber.js计算出来的结果为字符串。

相比较于第1、第2两种方法,第3种方法比较简单,只需要在使用的时候直接调用API即可,不需要开发者自行做转换。

五、Bignumber.js大致实现原理

Bignumber.js是一个用于处理任意精度数学运算的js库,他是基于数组来存储和操作大数,通过数学上的基本原理来实现各种基本运算,并最终将结果转换为字符串输出。主要缺点就是性能比原生差。它的大致实现原理如下所示:

1)存储:Bignumber.js 使用一个数组来存储大数的每一位,每一位上存储的是一个数字,取值范围是 0 到 9。数组的第一个元素表示该数的最高位,最后一个元素表示该数的最低位。例如,如果要表示一个 100 位的数字,Bignumber.js 就会创建一个 100 个元素的数组,每个元素的初始值都是 0。
2)转换:当用户输入一个大数时,Bignumber.js 会先将其转换成字符串,然后按照字符串的每一位进行存储。例如,如果用户输入的是字符串 "123456789",那么 Bignumber.js 就会创建一个 9 个元素的数组,分别存储数字 1 到 9。Bignumber.js 还支持从其他数据类型转换成大数,例如整数、浮点数和科学计数法。
3)运算:Bignumber.js 支持多种基本运算,包括加法、减法、乘法和除法等。这些运算都是基于存储在数组中的数位来实现的,具体实现方法也是根据数学上的基本原理来设计的。例如,加法就是从低位到高位逐位相加,并处理进位。减法就是从低位到高位逐位相减,并处理借位。乘法就是将两个数位上的每个数字相乘,并将结果累加到对应的位置上。除法则是根据除法的定义,逐步计算商和余数。
4)格式化:Bignumber.js 还支持将大数格式化为字符串,方便用户进行输出和显示。格式化过程也是基于数组中的数位来实现的,根据需要添加小数点和符号,并处理前导零和尾随零。

 六、一些有用的方法

1、判断是否为整数

1)通过位运算,数字或上0后的结果是否等于原数字,即向下取整还是等于自身。(缺点:只能处理32位以内数字)

function isInt(n) {
  var isNum = typeof n === 'number'
  // 向下取整,直接舍去小数部分
  var i = n | 0;
  return isNum && n === i;
}

2)同理可用Math.round  Math.floor  Math.ceil取整后还等于自身,则为整数

3)对1求余为0

function isInt(n) {
  var isNum = typeof n === 'number'
  // 向下取整,直接舍去小数部分
  var result = n % 1 === 0;
  return isNum && result;
}

4)ES6提供的Number.isInteger

Number.isInteger('') // false
Number.isInteger('2') // false
Number.isInteger(undefied) // false
Number.isInteger(2) // true

5)~ 是按位非 也就是按位取反 那么2个~~就是2次取反,最终得到向下取整的结果

~~'123'为123
~~'123.14'为123
~~123为123
~~123.14为123
// ~~后面如果跟Boolean类型、null和undefined 也会转换成int类型 变成数值:
~~false为0
~~true为1
~~null为0
~~undefined为0

// 因此在使用到的时候需要判断是否为数值类型
isInt(n) {
  let isNum = typeof n === 'number'
  let result = ~~n
  return isNum && result === n
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值