JS小数运算精度丢失的问题

工作中会不会经常会碰到一些数据指标的计算,比如百分比转化,保留几位小数等,就会出现计算不准确,数据精度丢失的情况。通过这篇分享借助第三方库能够轻松解决数据精度丢失的问题。


一、场景复现

JS数字精度丢失的一些常见问题
// 加法 =====================
0.1 + 0.2 === 0.3 // false
0.1 + 0.2 = 0.30000000000000004
0.7 + 0.1 = 0.7999999999999999
0.2 + 0.4 = 0.6000000000000001// 减法 =====================
1.5 - 1.2 = 0.30000000000000004
0.3 - 0.2 = 0.09999999999999998// 乘法 =====================
19.9 * 100 = 1989.9999999999998
0.8 * 3 = 2.4000000000000004
35.41 * 100 = 3540.9999999999995// 除法 =====================
0.3 / 0.1 = 2.9999999999999996
0.69 / 10 = 0.06899999999999999

为什么0.1 + 0.2 === 0.3是false呢?

先看下面这个比喻

比如一个数 1÷3=0.33333333......

3会一直无限循环,数学可以表示,但是计算机要存储,方便下次取出来再使用,但0.333333...... 这个数无限循环,再大的内存它也存不下,所以不能存储一个相对于数学来说的值,只能存储一个近似值,当计算机存储后再取出时就会出现精度丢失问题。

再看js里保留小数位tofixed()对于小数最后一位为5时进位不正确的问题
1.35.toFixed(1) // 1.4 正确
1.335.toFixed(2) // 1.33  错误
1.3335.toFixed(3) // 1.333 错误
1.33335.toFixed(4) // 1.3334 正确
1.333335.toFixed(5)  // 1.33333 错误
1.3333335.toFixed(6) // 1.333333 错误

可以看到,小数点位数为2,5时四舍五入是正确的,其它是错误。

根本原因还是计算机里浮点数精度丢失的问题

如:1.005.toFixed(2) 返回的是 1.00 而不是 1.01。

原因: 1.005 实际对应的数字是 1.00499999999999989,在四舍五入时全部被舍去。

1.005.toPrecision(21) //1.00499999999999989342

二、浮点数

“浮点数”是一种表示数字的标准,整数也可以用浮点数的格式来存储,我们也可以理解成,浮点数就是小数,

在JavaScript中,现在主流的数值类型是Number,而Number采用的是IEEE754规范中64位双精度浮点数编码,

这样的存储结构优点是可以归一化处理整数和小数,节省存储空间。

对于一个整数,可以很轻易转化成十进制或者二进制。但是对于一个浮点数来说,因为小数点的存在,小数点的位置不是固定的。解决思路就是使用科学计数法,这样小数点位置就固定了。

而计算机只能用二进制(0或1)表示,二进制转换为科学记数法的公式如下:

其中,a的值为0或者1,e为小数点移动的位置。

举个例子:

27.0转化成二进制为11011.0 ,科学计数法表示为:

其中,a的值为0或者1,e为小数点移动的位置。

举个例子:

27.0转化成二进制为11011.0 ,科学计数法表示为:

前面讲到,javaScript存储方式是双精度浮点数,其长度为8个字节,即64位比特,

64位比特又可分为三个部分:

符号位S:第 1 位是正负数符号位(sign),0代表正数,1代表负数;
指数位E:中间的 11 位存储指数(exponent),用来表示次方数,可以为正负数。在双精度浮点数中,指数的固定偏移量为1023;
尾数位M:最后的 52 位是尾数(mantissa),超出的部分自动进一舍零;
如下图所示:

举个例子:

27.5 转换为二进制11011.1

11011.1转换为科学记数法 [公式]

符号位为1(正数),指数位为4+,1023+4,即1027

因为它是十进制的需要转换为二进制,即 10000000011,小数部分为10111,补够52位即: 1011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`

所以27.5存储为计算机的二进制标准形式(符号位+指数位+小数部分 (阶数)),既下面所示:

0+10000000011+011 1000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000`

二、问题分析

再回到问题上

0.1 + 0.2 === 0.3 // false
0.1 + 0.2 = 0.30000000000000004

通过上面的学习,我们知道,在javascript语言中,0.1 和 0.2 都需要先将十进制转化成二进制后再进行运算。

// 0.1 和 0.2 都转化成二进制后再进行运算
0.00011001100110011001100110011001100110011001100110011010 +
0.0011001100110011001100110011001100110011001100110011010 =
0.0100110011001100110011001100110011001100110011001100111// 转成十进制正好是 0.30000000000000004

所以输出false

再来一个问题,那么为什么x=0.1得到0.1?

主要是存储二进制时小数点的偏移量最大为52位,最多可以表达的位数是2^53=9007199254740992,对应科学计数尾数是 9.007199254740992,这也是 JS 最多能表示的精度。

它的长度是 16,所以可以使用 toPrecision(16) 来做精度运算,超过的精度会自动做凑整处理。

.10000000000000000555.toPrecision(16)
// 返回 0.1000000000000000,去掉末尾的零后正好为 0.1

但看到的 0.1 实际上并不是 0.1。不信你可用更高的精度试试:

0.1.toPrecision(21) = 0.100000000000000005551

小结

计算机存储双精度浮点数需要先把十进制数转换为二进制的科学记数法的形式,然后计算机以自己的规则{符号位+(指数位+指数偏移量的二进制)+小数部分}存储二进制的科学记数法。

因为存储时有位数限制(64位),并且某些十进制的浮点数在转换为二进制数时会出现无限循环,会造成二进制的舍入操作(0舍1入),当再转换为十进制时就造成了计算误差。

三、解决方案

理论上用有限的空间来存储无限的小数是不可能保证精确的,但我们可以处理一下得到我们期望的结果。

当你拿到 1.4000000000000001 这样的数据要展示时,建议使用 toPrecision 凑整并 parseFloat 转成数字后再显示,如下:

parseFloat(1.4000000000000001.toPrecision(12)) === 1.4  // True

封装成方法就是:

function strip(num, precision = 12) {return +parseFloat(num.toPrecision(precision));
}

对于运算类操作,如 +-*/,就不能使用 toPrecision 了。正确的做法是把小数转成整数后再运算(先扩大再缩小法)。

以加法为例:

/*** 精确加法*/
function add(num1, num2) {const num1Digits = (num1.toString().split('.')[1] || '').length;const num2Digits = (num2.toString().split('.')[1] || '').length;const baseNum = Math.pow(10, Math.max(num1Digits, num2Digits));return (num1 * baseNum + num2 * baseNum) / baseNum;
}

以toFixed()为例

//先扩大再缩小法 
function toFixed(num, s) {var times = Math.pow(10, s)// 0.5 为了舍入var des = num * times + 0.5// 去除小数des = parseInt(des, 10) / timesreturn des + ''
}
console.log(toFixed(1.333332, 5))

最后还可以使用第三方库,如Math.js、BigDecimal.js

参考文献

  • 数值-阮一峰
  • BigInt - JavaScript | MDN

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/117601.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

SpringBoot+SpringMVC+MybatisPlus

文章目录 SpringBootSpringMVCMybatisPlus怎样在SpringBoot中引入SpringMVC?首先看下引入的依赖创建数据库表创建DO类创建MyBatisPlus动态代理接口创建controller控制器接收http请求创建SpringBoot配置文件application.yml最后创建启动类 SpringBootSpringMVCMybatisPlus 怎样…

cuda卸载

去查看你的电脑显卡对应的cuda版本,不然还是一整个用不到gpu的情况嘿嘿. 啊啊啊啊打开控制面板看一下,驱动不要乱卸载: 这些东西不能全部卸载了哦,只能卸载含有“CUDA”的那几个(其实其他的可能也没有用 但是不懂的哇 …

关于路由转发

路由表的作用 路由表的作用:目标网络匹配路由表,从相应网络转发;不匹配路由表,丢弃或转发至默认路由器。 路由转发的原理 根据IP地址找到目标网络,由应路由器解封装查看目标网络是否可达,重新封装进行转…

Axure 9 使用 font awesome 字体发布原型

我使用的版本为Font awesome 6.1.1,安装后在axure中新增3个字体 如果直接发布,在没有安装Font awesome的电脑上无法正常显示字体图标,需要在发布前进行设置。 设置方法: 点击共享 点开设置 在字体设置页,填入图中所示…

FreeRTOS 消息队列 详解

目录 什么是队列? 消息队列特点 1. 数据入队出队方式 2. 数据传递方式 3. 多任务访问 4. 出队、入队阻塞 消息队列相关 API 函数 1. 创建队列 2. 写队列 3. 读队列 消息队列实操 什么是队列? 队列又称消息队列,是一种常用于任务间…

创建 Edge 浏览器扩展教程(下)

创建 Edge 浏览器扩展教程(下) 创建扩展教程,第 2 部分1:更新弹出窗口.html以包含按钮2:更新弹出窗口.html在浏览器选项卡顶部显示图像3:创建弹出式 JavaScript 以发送消息4:从任何浏览器选项卡…

全国三维数字化创新设计大赛湖北赛区省赛成功举办

须弥芥子,数字如海。10月14日—15日,2023 年数字科技文化节——第16届全国三维数字化创新设计大赛湖北赛区省赛暨产教联合体大会在武汉软件工程职业学院成功举行。 (大赛全体专家领导合影) 全国三维数字化创新设计大赛组委会副秘…

Windows 安装 jmeter

注:在安装Jmeter之前,请先检查下电脑有没有装JDK:开始->运行->然后输入cmd->进入命令行界面,输入java -version , 出现以下信息就是此电脑已安装了JDK: 下载地址 http://jmeter.apache.org/downlo…

C/C++程序设计和预处理

个人主页:仍有未知等待探索_C语言疑难,数据结构,小项目-CSDN博客 专题分栏:C语言疑难_仍有未知等待探索的博客-CSDN博客 目录 一、引言 二、程序的翻译环境和执行环境 1、什么是程序 2、程序的翻译环境 3、程序的执行环境 三、预处理 1、预定义符…

python爬虫分析基于python图书馆书目推荐数据分析与可视化

收藏关注不迷路 文章目录 前言一、项目介绍二、开发环境三、功能介绍四、核心代码五、效果图六、文章目录 前言 随着电子技术的普及和快速发展,线上管理系统被广泛的使用,有很多商业机构都在实现电子信息化管理,图书推荐也不例外&#xff0c…

windows协议详解之-RPC/SMB/LDAP/LSA/SAM域控协议关系

如果你在windows域控环境中,例如企业的网络中开启wireshark抓包,你一定会遇到一大堆各种各样的协议。不同于互联网服务(大多基于HTTP),为了实现域控中各种各样的服务,windows的域控环境中采用了非常多的协议…

程桌面管理软件Apple Remote Desktop mac中文介绍说明

Apple Remote Desktop mac是一款远程桌面管理软件。它可以让用户通过局域网或互联网连接到其他远程计算机,并实时监控和管理这些计算机。 使用Apple Remote Desktop,用户可以轻松远程操作和控制其他计算机的桌面。用户可以在远程计算机上查看、操控和键入…

风力发电功率预测(CEEMDAN-LSTM-CNN-CBAM模型,Python代码)

1.前言 1.1.运行效果:风力发电功率预测(CEEMDAN-LSTM-CNN-CBAM模型,Python代码)_哔哩哔哩_bilibili 1.2.环境库: 如果库版本不一样, 一般也可以运行,这里展示我运行时候的库版本,是…

J2EE的N层体系结构

J2EE平台采用了多层分布式应用程序模型,实现不同逻辑功能的应用程序被封装到不同的构件中,处于不同层次的构件可被分别部署到不同的机器中。 RMI/IIOP:RMI(Remote Method Invocation,远程方法调用)是Java的…

C语言每日一题(18)数组匹配

牛客网 BC156 牛牛的数组匹配 题目描述 描述 牛牛刚学会数组不久,他拿到两个数组 a 和 b,询问 b 的哪一段连续子数组之和与数组 a 之和最接近。 如果有多个子数组之和同样接近,输出起始点最靠左的数组。 输入描述: 第一行输…

网络安全https

http是明文的,相当于在网上裸奔,引出了https,大多数网站都转为了https,连非法的赌博网站有的都是https的。 1.https的网站是不是必须让用户装数字证书? 答:分两种,一种是单向认证,像…

【STM32】HAL库ADC多通道精准测量(采用VREFINT内部参考电压)

【STM32】HAL库ADC多通道精准测量(采用VREFINT内部参考电压) 文章目录 多通道测量VREFINTADC采样周期多通道配置 附录:Cortex-M架构的SysTick系统定时器精准延时和MCU位带操作SysTick系统定时器精准延时延时函数阻塞延时非阻塞延时 位带操作…

GCE的安装和使用

GCE的安装和使用 GCE的安装使用1. GCE的安装2. GCE的使用补充:一个简单的R脚本——kmerpdf.R,用于绘制kmer的种类和数量分布图 GCE的安装使用 一个基因组评估软件。其他同类型软件Genomescope 1. GCE的安装 Github官网:https://github.com…

冒泡排序:了解原理与实现

目录 原理 实现 性能分析 结论 冒泡排序(Bubble Sort)是一种简单但效率较低的排序算法。它重复地比较相邻的元素并交换位置,直到整个序列有序为止。虽然冒泡排序的时间复杂度较高,但在小规模数据集上仍然具有一定的实际应用价…

【JavaEE】CAS -- 多线程篇(7)

CAS 1. 什么是 CAS2. CAS 伪代码3. CAS 是怎么实现的4. CAS的应用4.1 实现原子类4.2 实现自旋锁 5. CAS 的 ABA 问题 1. 什么是 CAS CAS: 全称Compare and swap,字面意思:”比较并交换“能够比较和交换 某个寄存器中的值和内存中的值, 看是否相等, 如果相等, 则把另…