我是这样解决JavaScrip 加减乘除精度问题

开发 前端
众所周知的 JavaScript 二进制精度问题,浮点数的计算精度会存在缺失问题。最经典的例子就是为什么0.1+0.2 !== 0.3

 前言

[[432194]]

众所周知的 JavaScript 二进制精度问题,浮点数的计算精度会存在缺失问题。最经典的例子就是为什么0.1+0.2 !== 0.3

一句话概括就是:ECMAScript规范定义Number的类型遵循了IEEE754-2008中的64位浮点数规则定义的小数后的有效位数至多为52位导致计算出现精度丢失问题!

不过网上已经有很多专门的类库可以解决这个问题。

原生封装

 

  1. /** 
  2.  ** 加法函数,用来得到精确的加法结果 
  3.  ** 说明:javascript的加法结果会有误差,在两个浮点数相加的时候会比较明显。这个函数返回较为精确的加法结果。 
  4.  ** 调用:accAdd(arg1,arg2) 
  5.  ** 返回值:arg1加上arg2的精确结果 
  6.  **/ 
  7. function accAdd(arg1, arg2) { 
  8.     let r1, r2, m 
  9.     try { 
  10.         r1 = arg1.toString().split('.')[1].length 
  11.     } catch (e) { 
  12.         r1 = 0 
  13.     } 
  14.     try { 
  15.         r2 = arg2.toString().split('.')[1].length 
  16.     } catch (e) { 
  17.         r2 = 0 
  18.     } 
  19.     m = Math.pow(10, Math.max(r1, r2)) 
  20.     return (arg1 * m + arg2 * m) / m 

 

  1. /** 
  2.  ** 减法函数,用来得到精确的减法结果 
  3.  ** 说明:javascript的减法结果会有误差,在两个浮点数相减的时候会比较明显。这个函数返回较为精确的减法结果。 
  4.  ** 调用:accSub(arg1,arg2) 
  5.  ** 返回值:arg1加上arg2的精确结果 
  6.  **/ 
  7. function accSub(arg1, arg2) { 
  8.   var r1, r2, m, n; 
  9.   try { 
  10.     r1 = arg1.toString().split(".")[1].length; 
  11.   } catch (e) { 
  12.     r1 = 0; 
  13.   } 
  14.   try { 
  15.     r2 = arg2.toString().split(".")[1].length; 
  16.   } catch (e) { 
  17.     r2 = 0; 
  18.   } 
  19.   m = Math.pow(10, Math.max(r1, r2)); //last modify by deeka //动态控制精度长度 
  20.   n = r1 >= r2 ? r1 : r2; 
  21.   return ((arg1 * m - arg2 * m) / m).toFixed(n); 

 

  1. /** 
  2.  ** 乘法函数,用来得到精确的乘法结果 
  3.  ** 说明:javascript的乘法结果会有误差,在两个浮点数相乘的时候会比较明显。这个函数返回较为精确的乘法结果。 
  4.  ** 调用:accMul(arg1,arg2) 
  5.  ** 返回值:arg1乘以 arg2的精确结果 
  6.  **/ 
  7.   
  8. function accMul(arg1, arg2) { 
  9.     let m = 0 
  10.     let s1 = arg1.toString() 
  11.     let s2 = arg2.toString() 
  12.     try { 
  13.         m += s1.split('.')[1] ? s1.split('.')[1].length : '' 
  14.     } catch (e) {} 
  15.     try { 
  16.         m += s2.split('.')[1] ? s2.split('.')[1].length : '' 
  17.     } catch (e) {} 
  18.     return (Number(s1.replace('.''')) * Number(s2.replace('.'''))) / Math.pow(10, m) 

 

  1. /** 
  2.  ** 除法函数,用来得到精确的除法结果 
  3.  ** 说明:javascript的除法结果会有误差,在两个浮点数相除的时候会比较明显。这个函数返回较为精确的除法结果。 
  4.  ** 调用:accDiv(arg1,arg2) 
  5.  ** 返回值:arg1除以arg2的精确结果 
  6.  **/ 
  7. function accDiv(arg1, arg2) { 
  8.     let t1 = 0 
  9.     let t2 = 0 
  10.     let r1 
  11.     let r2 
  12.     try { 
  13.         t1 = arg1.toString().split('.')[1].length 
  14.     } catch (e) {} 
  15.     try { 
  16.         t2 = arg2.toString().split('.')[1].length 
  17.     } catch (e) {} 
  18.     r1 = Number(arg1.toString().replace('.''')) 
  19.     r2 = Number(arg2.toString().replace('.''')) 
  20.     return (r1 / r2) * Math.pow(10, t2 - t1) 

封装

定义一个函数来调用加减乘除方法,这样做有个好处,用到地方调用加减乘除方法一致,假设某个方法后面发现那个库更好用或者某个平台不兼容、算法不太严谨、扩展新的功能等等,我们只要维护这个函数就行,不用在考虑项目中某个组件单独引用,没有按照这个规范因为这次维护引发的新问题。

 

  1. export const calcFn = { 
  2.     add() { 
  3.         const arg = Array.from(arguments) 
  4.         return arg.reduce((total, num) => { 
  5.             return accAdd(total, num) 
  6.         }) 
  7.     }, 
  8.     sub() { 
  9.         const arg = Array.from(arguments) 
  10.         return arg.reduce((total, num) => { 
  11.             return accSub(total, num) 
  12.         }) 
  13.     }, 
  14.     mul() { 
  15.         const arg = Array.from(arguments) 
  16.         return arg.reduce((total, num) => { 
  17.             return accMul(total, num) 
  18.         }) 
  19.     }, 
  20.     divide() { 
  21.         const arg = Array.from(arguments) 
  22.         return arg.reduce((total, num) => { 
  23.             return accDiv(total, num) 
  24.         }) 
  25.     } 

big.js

  • 介绍:任意精度十进制算术的小型、快速、易于使用的库。
  • 特性:目前同类型最小包、无依赖、包大小3 KB、兼容ECMAScript 3+可以说适用于所有浏览器。
  • 官网:GitHub

 

 

 

 

https://github.com/MikeMcl/big.js/

安装使用

浏览器

 

 

  1. <script src='https://cdn.jsdelivr.net/npm/big.js@6.1.1/big.min.js'></script> 

 

Node.js

  1. npm install big.js 

使用

 

  1. x = new Big(0.1) 
  2. y = new Big(0.2)                  
  3. z = new Big(0.3) 
  4. x.plus(y).eq(z)     // true 

运算符操作函数

以下big.js目前支持运算符操作函数。

  • abs,取绝对值。
  • cmp,compare的缩写,即比较函数。
  • div,除法。
  • eq,equal的缩写,即相等比较。
  • gt,大于。
  • gte,小于等于,e表示equal。
  • lt,小于。
  • lte,小于等于,e表示equal。
  • minus,减法。
  • mod,取余。
  • plus,加法。
  • pow,次方。
  • prec,按精度舍入,参数表示整体位数。
  • round,按精度舍入,参数表示小数点后位数。
  • sqrt,开方。
  • times,乘法。
  • toExponential,转化为科学计数法,参数代表精度位数。
  • toFied,补全位数,参数代表小数点后位数。
  • toJSON和toString,转化为字符串。
  • toPrecision,按指定有效位数展示,参数为有效位数。
  • toNumber,转化为JavaScript中number类型。
  • valueOf,包含负号(如果为负数或者-0)的字符串。

封装

 

  1. import Big from 'big.js' 
  2.  
  3. export const calcFn = { 
  4.     add() { 
  5.         const arg = Array.from(arguments) 
  6.         return arg.reduce((total, num) => { 
  7.             return new Big(total).plus(new Big(num)) 
  8.         }).toString() * 1 
  9.     }, 
  10.     sub() { 
  11.         const arg = Array.from(arguments) 
  12.         return arg.reduce((total, num) => { 
  13.             return new Big(total).minus(new Big(num)) 
  14.         }).toString() * 1 
  15.     }, 
  16.     mul() { 
  17.         const arg = Array.from(arguments) 
  18.         return arg.reduce((total, num) => { 
  19.             return new Big(total).times(new Big(num)) 
  20.         }).toString() * 1 
  21.     }, 
  22.     divide() { 
  23.         const arg = Array.from(arguments) 
  24.         return arg.reduce((total, num) => { 
  25.             return new Big(total).div(new Big(num)) 
  26.         }).toString() * 1 
  27.     } 

使用

 

  1. calcFn.add(0.1, 0.2) !== 0.3 // false 

bignumber.js

  • 介绍:用于任意精度十进制和非十进制算术的 JavaScript 库。
  • 特性:无依赖、包大小8 KB、兼容ECMAScript 3+可以说适用于所有浏览器。
  • 官网:GitHub

 

 

 

 

https://github.com/MikeMcl/bignumber.js

使用方法类似,同上。

decimal.js

  • 介绍:为 JavaScript 提供十进制类型的任意精度数值。
  • 特性:无依赖、包大小12.6 KB、兼容ECMAScript 3+可以说适用于所有浏览器。
  • 官网:GitHub

 

 

 

 

https://github.com/MikeMcl/decimal.js

使用方法类似,同上。

Math.js

  • 介绍:用 Javascript 编写的简单数学库,可能不维护了。
  • 特性:是一个广泛的 JavaScript 和 Node.js 数学库。它具有灵活的表达式解析器,支持符号计算,带有大量内置函数和常量,并提供了一个集成的解决方案来处理不同的数据类型,如数字、大数、复数、分数、单位和矩阵。功能强大且易于使用。
  • 官网:GitHub

 

 

 

 

总结

big.js适用于大部分十进制算术应用程序,因为不接受NaN或Infinity作为合法值。而且不支持其他基数的值。如果项目中没有非十进制算术这非常适合用,而且关键是包足过小,哈哈自己造的轮子后面还是觉得库比较香哈。

bignumber.js可能更适合金融应用,因为除非使用涉及除法的运算,否则用户无需担心会丢失精度。

decimal.js可能更适合更科学的应用程序,因为它可以更有效地处理非常小的或大的值。例如,它没有bignumber.js的限制,当将一个小指数的值与一个大指数的值相加时,bignumber.js会尝试执行全精度运算,这可能会导致操作不可行。

如上所述,decimal.js还支持非整数幂,并增加了三角函数和exp,ln和log方法。这些添加使decimal.js明显大于bignumber.js。

 

责任编辑:华轩 来源: 前端有道
相关推荐

2021-04-19 17:32:34

Java内存模型

2021-04-02 06:17:10

大数加减乘除数据结构算法

2021-09-15 13:31:44

语言项目技术栈scala

2015-06-29 09:56:08

2010-07-06 09:45:10

OracleJavaJames Gosli

2013-01-10 12:57:23

产品经理App产品设计

2013-06-17 11:21:27

2021-01-12 09:22:18

Synchronize线程开发技术

2009-03-06 09:55:48

职场晋升助理

2016-12-28 14:51:46

大数据应用

2015-07-30 09:48:38

自学编程3遍读书法

2022-07-14 14:27:34

Javascript数字精度二进制

2023-02-13 10:02:31

ChatGPT人工智能

2017-07-13 17:33:18

生成对抗网络GANIan Goodfel

2015-08-28 09:19:53

Web开发移动开发

2017-09-13 10:58:51

JavaScript转换规则

2020-12-10 08:43:17

垃圾回收JVM

2023-04-27 07:41:21

Java开发技巧

2009-12-17 10:39:01

Ruby数学表达式

2024-03-05 07:31:59

CASvalue原子性
点赞
收藏

51CTO技术栈公众号