首页 > 其他分享 >记录--前端金额运算精度丢失问题及解决方案

记录--前端金额运算精度丢失问题及解决方案

时间:2024-02-01 17:44:40浏览次数:32  
标签:运算 -- 解决方案 浮点数 JavaScript js 有效数字 丢失 精度

这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助

前言

前端开发中难免会遇到价格和金额计算的需求,这类需求所要计算的数值大多数情况下是要求精确到小数点后的多少位。但是因为JS语言本身的缺陷,在处理浮点数的运算时会出现一些奇怪的问题,导致计算不精确。

本文尝试从现象入手,分析造成这一问题原因,并总结和整合一些通用的解决方案,以供大家参考。

现象回顾

下面的是JS进行数值运算过程中常见的问题,这个问题有个专业的名称叫精度丢失

在 JavaScript 中整数和浮点数都属于 Number 数据类型,所有的数字都是以 64 位浮点数形式存储,整数也是如此。所以我们在打印 1.00 这样的浮点数的结果是 1 而非 1.00 。在一些特殊的数值表示中,例如金额,这样看上去有点别扭,但是至少值是正确了。然而要命的是,当浮点数做数学运算的时候,你经常会发现一些问题,举几个例子:

// 加法
0.1 + 0.2 = 0.30000000000000004
0.7 + 0.1 = 0.7999999999999999
0.2 + 0.4 = 0.6000000000000001
2.22 + 0.1 = 2.3200000000000003

// 减法
1.5 - 1.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998

// 乘法
19.9 * 100 = 1989.9999999999998
19.9 * 10 * 10 = 1990
1306377.64 * 100 = 130637763.99999999
1306377.64 * 10 * 10 = 130637763.99999999
0.7 * 180 = 125.99999999999999
9.7 * 100 = 969.9999999999999
39.7 * 100 = 3970.0000000000005

// 除法
0.3 / 0.1 = 2.9999999999999996
0.69 / 10 = 0.06899999999999999

问题分析

JavaScript 里的数字是采用 IEEE 754 标准的 64 位双精度浮点数。

该规范定义了浮点数的格式,对于 64 位的浮点数在内存中的表示,最高的 1 位是符号位,接着的 11 位是指数,剩下的 52 位为有效数字,具体对应如下:

  • 第 0 位:符号位, s 表示 ,0 表示正数,1 表示负数;
  • 第 1 位到第 11 位:储存指数部分, e 表示 ;
  • 第 12 位到第 63 位:储存小数部分(即有效数字),f 表示,

符号位决定了一个数的正负,指数部分决定了数值的大小,小数部分决定了数值的精度。

IEEE 754 规定,有效数字第一位默认总是 1,不保存在 64 位浮点数之中。也就是说,有效数字总是 1.xx…xx 的形式,其中 xx..xx 的部分保存在 64 位浮点数之中,最长可能为 52 位。

因此,JavaScript 提供的有效数字最长为 53 个二进制位(64 位浮点的后 52 位 + 有效数字第一位的 1)。

那么,JavaScript 在计算 0.1 + 0.2 时,到底发生了什么呢?

首先,十进制的 0.10.2 都会被转换成二进制,但由于浮点数用二进制表达时是无穷的,就成了下面的样子。

0.1 -> 0.0001100110011001...(无限)
0.2 -> 0.0011001100110011...(无限)
IEEE 754 标准的 64 位双精度浮点数的小数部分最多支持 53 位二进制位,所以两者相加之后得到的二进制就是下面的样子。
0.0100110011001100110011001100110011001100110011001100

最终,因浮点数小数位的限制而截断的二进制数字,再转换为十进制,就成了 0.30000000000000004,所以在进行算术计算时就产生了误差。

解决方案

通常情况下对计算精度要求高的业务场景都应该交给后端去计算和存储,因为后端有成熟的方案和工具来解决这种计算问题。

这里是对网上常见的几个解决方案的汇总和整理,但是方案一和方案二都存在一定的局限性,我会在对应的方案里进行说明。通常我们前端做这类运算通常只用于表现层,所以这两个方案基本是够用的。

方案一

在应对确定精度浮点数运算时,可以把金额转换成整数进行运算,最后再将结果转换成真实金额。

// 定义金额(保留两位小数)
const amount1 = 0.10;
const amount2 = 0.20;

// 转整数运算,并还原成真实金额
const result = ((amount1 * 100) + (amount2 * 100)) / 100;

// 结果
console.log(result); // 0.3

// 直接运算
console.log(amount1 + amount2); // 0.30000000000000004

需要注意的是上面的例子只能处理最多两位小数的运算场景,如果小数位不确定这个方法是行不通的。

方案二

JavaScript 内置的 toFixed() 方法可以将数字转换成保留指定小数位的字符串。这个方法适用于简单的金额计算。但需要注意舍入误差,因为转换后是字符串,失去了浮点数的特性,最后的结果坑你存在微小的误差。

// 定义金额
const amount1 = 0.1;
const amount2 = 0.2;

// 加法运算
const result = (amount1 + amount2).toFixed(2); // 保留两位小数

// 注意这里运算的结果应该是:0.30000000000000004
console.log(result); // 输出 "0.30"

toFixed 它是一个四舍六入五成双的诡异的方法(也叫银行家算法)。"四舍六入五成双"含义:对于位数很多的近似数,当有效位数确定后,其后面多余的数字应该舍去,只保留有效数字最末一位,这种修约(舍入)规则是“四舍六入五成双”,也即“4舍6入5凑偶”这里“四”是指≤4 时舍去,"六"是指≥6时进上,"五"指的是根据5后面的数字来定,当5后有数时,舍5入1;当5后无有效数字时,需要分两种情况来讲:5前为奇数,舍5入1;5前为偶数,舍5不进(0是偶数)。

第三方库

现代前端发展至今,已经有很多成熟的类库来帮助我们解决此类问题,这类类库通常有很好的通用性和兼容性。

下面我将推荐几个人气较高的数字计算类库。

Math.js

Math.js 是专门为 JavaScript 和 Node.js 提供的一个广泛的数学库。它具有灵活的表达式解析器,支持符号计算,配有大量内置函数和常量,并提供集成解决方案来处理不同的数据类型像数字,大数字 (超出安全数的数字),复数,分数,单位和矩阵,功能强大,易于使用。

官网:mathjs.org/

GitHub:GitHub - josdejong/mathjs: An extensive math library for JavaScript and Node.js

decimal.js

为 JavaScript 提供十进制类型的任意精度数值。

官网:decimal.js API

GitHub:GitHub - MikeMcl/decimal.js: An arbitrary-precision Decimal type for JavaScript

big.js

Big.js 是一个用于处理任意精度的大数运算的 JavaScript 库。它解决了 JavaScript 中处理大数运算时精度丢失的问题,提供了更高精度的计算能力。

Big.js 库的特点包括:

  • 任意精度:Big.js 允许您处理任意精度的数字,而不受 JavaScript 内置数字类型的限制。
  • 高精度计算:Big.js 提供了精确的加法、减法、乘法、除法和取余等运算,以及比较和舍入等功能。
  • 可配置的精度和舍入规则:您可以自定义 Big.js 运算的精度和舍入规则,以满足特定的需求。
  • 支持链式操作:您可以使用链式调用来执行多个运算,使代码更简洁易读。
  • 适用于浏览器和 Node.js:Big.js 可以在浏览器和 Node.js 环境中使用,兼容性良好。

Big.js 库非常适用于需要高精度计算的场景,如金融、密码学、科学计算和大数据处理等。它允许开发人员在 JavaScript 中进行准确的数字计算,避免了精度损失带来的问题。

官网:big.js API

GitHub:GitHub - MikeMcl/big.js: A small, fast JavaScript library for arbitrary-precision decimal arithmetic.

总结

本文对 Javascript 中浮点数运算出现的精度丢失问题进行了还原,分析了问题产生的原因在于二进制本身。同时给出了三个网络上比较成熟的解决方案,其中第一和第二方案基本可以满足大部分开发场景,如果不能满足就使用类库。

本文转载于:

https://juejin.cn/post/7325627704782307337

如果对您有所帮助,欢迎您点个关注,我会定时更新技术文档,大家一起讨论学习,一起进步。

 

标签:运算,--,解决方案,浮点数,JavaScript,js,有效数字,丢失,精度
From: https://www.cnblogs.com/smileZAZ/p/18001753

相关文章

  • 19蟠桃
    19蟠桃记题目描述孙悟空在大闹蟠桃园的时候,第一天吃掉了所有桃子总数一半多一个,第二天又将剩下的桃子吃掉一半多一个,以后每天吃掉前一天剩下的一半多一个,到第n天准备吃的时候只剩下一个桃子。这下可把神仙们心疼坏了,请帮忙计算一下,第一天开始吃的时候桃子一共有多少个桃子。输......
  • 外包出项北京市海淀区劳动监察举报
     劳动监察举报必须 本人 亲自去,必须需要带身份证,举报材料 这里的两个文件到那之后也有,不过需要手动填,比较麻烦,建议自己下载下来填好打印出来就行,注意,所有内容 必须在一张A4纸 上,不能分两页......
  • phpMyAdmin 未授权Getshell
    前言做渗透测试的时候偶然发现,phpmyadmin少见的打法,以下就用靶场进行演示了。0x01漏洞发现环境搭建使用metasploitable2,可在网上搜索下载,搭建很简单这里不多说了。发现phpmyadmin,如果这个时候无法登陆,且也没有前台的漏洞,可以继续在这个phpmyadmin目录下做文章。发现setup......
  • springboot接口加解密传输
    前言:写这个博客的目的也是想着后面如果需要用上的时候方便参考,这篇文章使用的也是比较简单的加解密流程,但这篇文章注重点是后端对于前端传过来的加密参数怎么接收,然后统一拦截解密后又回到原本接口上,接口返回结果时又是怎么将结果集加密后传输出去还有一个很重要的......
  • python爬取教习网试卷下载
    #!/usr/local/bin/python3#-*-encoding:utf-8-*-importrequestsfromlxmlimportetreeimportosfromPILimportImageimportshutildefget_doc_url(url):headers={"User-Agent":"Mozilla/5.0(Macintosh;IntelMacOS......
  • 证书申请说明
    ####证书申请说明查找《数字证书管理服务》服务,进行申请购买。购买后得到记录值如下:将主机记录、记录值添加到一个dns新的解析。如图选项:进入《域名》,《解析》。添加相关信息。记录类型选择TXT点击验证,会显示通过。等待证书下发。签发状态......
  • AI赋能—EasyCVR视频融合平台为春节人员流动保驾护航
    春节期间,如景区、商场、车站等公共场所的人流量激增,人员密集度大。在此情况下,监控客流量可以及时发现人群聚集、过度拥挤等安全隐患,防止发生安全事故。通过实时监测和分析客流量数据,可以及时发现安全隐患和拥堵问题,采取相应的措施进行处置,降低安全风险,确保公共场所的安全和秩序。......
  • laravel生成二维码,并添加背景图片,图标logo
    1、安装组件composerrequiresimplesoftwareio/simple-qrcode1.3.*在 config/app.php 注册服务提供者:SimpleSoftwareIO\QrCode\QrCodeServiceProvider::class同样在 config/app.php 添加 QrCode 门面:'QrCode'=>SimpleSoftwareIO\QrCode\Facades\QrCode::class2......
  • 接收机噪声系数 & 接收机带宽
    https://blog.csdn.net/weixin_45317919/article/details/131342203接收机噪声系数(ReceiverNoiseFigure, RNF)是衡量接收机内部噪声影响的一个关键指标,它反映了信号在接收机中经过放大、滤波等处理后,其信噪比降低的程度。具体来说,噪声系数是通过输入信噪比(SNRin)和输出信......
  • 2. 模拟图形绘制
    caseclassPoint(varx:Double,vary:Double)extendsDrawable{defshift(deltaX:Double,deltaY:Double):Unit={x+=deltaX;y+=deltaY}}traitDrawable{defdraw():Unit={println(this.toString)}}abstractclassShape(valposition......