深入探讨JavaScript中的精度问题:原理与解决方案

深入探讨JavaScript中的精度问题:原理与解决方案

在日常的JavaScript开发中,我们经常会遇到一些令人困惑的数值计算问题,特别是涉及到小数点运算时。例如,为什么0.1 + 0.2的结果不是预期的0.3,而是0.30000000000000004?本文将详细介绍JavaScript中出现精度问题的原因,深入解析十进制小数如何存储为二进制,以及如何避免和解决精度问题。

一、JavaScript中的精度问题现象

让我们先来看几个实际的例子:

console.log(0.1 + 0.2);          // 输出:0.30000000000000004
console.log(0.1 + 0.7);          // 输出:0.7999999999999999
console.log(0.2 * 0.4);          // 输出:0.08000000000000002
console.log(0.3 / 0.1);          // 输出:2.9999999999999996

这些结果可能出乎我们的意料,但在JavaScript中却是常见的。这些精度问题主要发生在浮点数运算中。

二、精度问题的底层原因

1. IEEE 754 双精度浮点数标准

JavaScript中的数字(Number类型)遵循IEEE 754标准,使用64位双精度浮点数表示。这种表示方法在计算机中非常常见,但也带来了浮点数精度的问题。

2. 十进制小数如何存储为二进制

2.1 整数部分的二进制表示

对于整数部分,将十进制整数不断除以2,取余数,逆序排列即可得到二进制表示。

示例:将十进制数5转换为二进制。

5 ÷ 2 = 2 ...... 1
2 ÷ 2 = 1 ...... 0
1 ÷ 2 = 0 ...... 1逆序排列余数:1 0 1因此,5的二进制表示为101
2.2 小数部分的二进制表示

小数部分的转换需要将十进制小数不断乘以2,取其整数部分(0或1),直到小数部分为0或达到所需的精度。

示例:将十进制小数0.625转换为二进制。

0.625 × 2 = 1.25     整数部分:1
0.25  × 2 = 0.5      整数部分:0
0.5   × 2 = 1.0      整数部分:1因此,0.625的二进制表示为0.101
2.3 不能精确表示的十进制小数

然而,对于某些十进制小数,如0.10.2等,在二进制中是无限循环小数,无法精确表示。

示例:将十进制小数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.0001100110011001100110011001100110011001100110011...
2.4 二进制浮点数的表示

在IEEE 754标准中,浮点数表示为:

(-1)^符号位 × 1.尾数 × 2^(指数位)

64位双精度浮点数的结构

  • 1位符号位(S):0表示正数,1表示负数。
  • 11位指数位(E):存储指数的偏移值。
  • 52位尾数(M):又称为有效数字或小数部分。

由于尾数只有有限的52位,无法存储无限循环的小数,必须进行截断或舍入,导致精度损失。

3. 二进制无法精确表示某些十进制小数

因为某些十进制小数在二进制中是无限循环的,所以在存储这些小数时,只能保留有限的位数,导致精度损失。

示例

  • 十进制0.1的二进制近似值:
    0.0001100110011001100110011001100110011001100110011001101(共52位尾数)
    
  • 但实际的二进制表示只能保留52位尾数,超出的部分被截断。

4. 浮点数的舍入误差

由于截断或舍入的存在,浮点数在存储时会引入微小的误差。当对这些浮点数进行运算时,误差会被放大或累积,导致最终结果与预期不符。

5. 浮点数的计算过程

0.1 + 0.2为例:

  1. 将十进制小数转换为二进制浮点数

    • 0.1的二进制近似值:
      0.0001100110011001100110011001100110011001100110011001101
      
    • 0.2的二进制近似值:
      0.001100110011001100110011001100110011001100110011001101
      
  2. 进行二进制加法

    将上述二进制数相加,得到结果(仍然是近似值)。

  3. 将二进制结果转换回十进制

    得到的十进制结果为0.30000000000000004,出现了精度误差。

三、扩展到其他进制小数的表示

1. 八进制和十六进制

与二进制类似,八进制和十六进制也无法精确表示某些十进制小数。这是因为进制之间的基数差异,导致在转换过程中出现无限循环小数。

1.1 八进制小数
  • 八进制的基数是8,小数部分的每一位表示(1/8)^n的倍数。
  • 某些十进制小数在八进制中会出现无限循环小数。

示例:将十进制小数0.3转换为八进制。

0.3 × 8 = 2.4      整数部分:2
0.4 × 8 = 3.2      整数部分:3
0.2 × 8 = 1.6      整数部分:1
0.6 × 8 = 4.8      整数部分:4
0.8 × 8 = 6.4      整数部分:6
0.4 × 8 = 3.2      整数部分:3
...得到八进制小数:0.231463...#### 1.2 十六进制小数- **十六进制的基数是16**,小数部分的每一位表示`(1/16)^n`的倍数。
- 同样地,某些十进制小数在十六进制中无法精确表示。**示例**:将十进制小数`0.1`转换为十六进制。

0.1 × 16 = 1.6 整数部分:1
0.6 × 16 = 9.6 整数部分:9
0.6 × 16 = 9.6 整数部分:9

得到十六进制小数:0.1999…(无限循环)


### 2. 任意进制之间的小数转换小数在不同进制之间的转换,本质上是基数的不同。由于某些进制之间的基数无法整除,导致小数在转换时会出现无限循环的情况。#### 2.1 常见的无法精确表示的情况- **二进制无法精确表示十分之一(0.1)**
- **十进制无法精确表示三分之一(0.(3))**
- **八进制无法精确表示某些十进制小数**#### 2.2 循环小数的概念在某个进制下,小数部分无限循环的数称为循环小数。例如,在十进制中,`1/3 = 0.(3)`,表示`0.3333...`无限循环。## 四、如何避免和解决精度问题### 1. 使用整数进行计算(定点数运算)将浮点数转换为整数,进行运算后再转换回浮点数。例如,将金额以最小单位(如“分”)存储和计算。```javascript
let a = 0.1;
let b = 0.2;
let result = (a * 100 + b * 100) / 100;
console.log(result); // 输出:0.3

注意:这种方法适用于有限的小数位数,且需要谨慎处理除法运算。

2. 使用toFixed()方法

toFixed()方法可以指定小数点后的位数,并返回一个字符串。

let result = (0.1 + 0.2).toFixed(1);
console.log(result); // 输出:"0.3"

缺点toFixed()返回的是字符串,可能需要转换为数字。此外,toFixed()也有可能出现四舍五入误差。

3. 引入精度校正函数

编写一个函数,对浮点数运算进行精度校正。

function add(a, b) {let r1 = 0, r2 = 0, m;try { r1 = a.toString().split(".")[1].length } catch (e) {}try { r2 = b.toString().split(".")[1].length } catch (e) {}m = Math.pow(10, Math.max(r1, r2));return (a * m + b * m) / m;
}console.log(add(0.1, 0.2)); // 输出:0.3

解释:通过计算小数位数,转换为整数进行运算,然后再转换回浮点数。

4. 使用第三方精度计算库

使用成熟的第三方库,可以方便地进行高精度的数值计算。

  • Decimal.js

    const Decimal = require('decimal.js');
    let result = new Decimal(0.1).plus(0.2);
    console.log(result.toString()); // 输出:"0.3"
    
  • BigNumber.js

    const BigNumber = require('bignumber.js');
    let result = new BigNumber(0.1).plus(0.2);
    console.log(result.toString()); // 输出:"0.3"
    

优点:支持任意精度的数值计算,避免了JavaScript原生的浮点数精度问题。

缺点:需要引入外部库,增加了项目的依赖。

5. ES2020中的BigInt

ES2020引入了BigInt类型,用于表示任意精度的整数。不过它只能处理整数,不能直接解决小数的精度问题。

let bigIntNum = BigInt("9007199254740993");
console.log(bigIntNum); // 输出:9007199254740993n

注意BigInt不能与Number类型直接混合运算,需要进行类型转换。

五、实际应用中的注意事项

1. 金融计算

在涉及金额的计算中,必须特别小心精度问题。通常的做法是:

  • 将金额以最小单位(如“分”)存储和计算。
  • 使用高精度的计算库。
  • 避免直接使用浮点数进行金额计算。

2. 比较浮点数

在比较两个浮点数是否相等时,不要直接使用=====,而是判断它们的差值是否在一个很小的范围内。

function numbersAlmostEqual(a, b, epsilon = Number.EPSILON) {return Math.abs(a - b) < epsilon;
}console.log(numbersAlmostEqual(0.1 + 0.2, 0.3)); // 输出:true

解释Number.EPSILON是JavaScript中能够表示的最小的正数,使得1 + Number.EPSILON !== 1

3. 避免累积误差

在大量的浮点数运算中,微小的误差会累积,导致结果偏差较大。可以考虑:

  • 将计算过程中的中间结果进行精度校正。
  • 使用精度更高的数据类型或计算方法。

六、总结

关键点:

  • 十进制小数在二进制中可能无法精确表示,导致舍入误差。
  • 浮点数的有限位数表示,限制了存储精度。
  • 在不同进制之间,小数的转换可能导致无限循环小数,这是进制间转换的固有问题。

解决方案:

  • 使用整数替代:将小数转换为整数进行计算,避免浮点数精度问题。
  • 使用精度校正函数:在运算过程中进行精度调整,减少误差。
  • 使用高精度计算库:引入第三方库,如Decimal.jsBigNumber.js,实现任意精度的数值计算。
  • 在比较浮点数时,使用容差范围:判断两个浮点数的差值是否在可接受的范围内,而不是直接比较相等。

通过深入理解JavaScript中的精度问题,以及十进制小数如何存储为二进制,我们可以编写出更加健壮和可靠的代码,提升程序的准确性和稳定性。

参考资料:

  • MDN文档 - Number
  • IEEE 754标准
  • 浮点数精度问题详解
  • BigNumber.js库

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

带外管理卡虚拟控制台连接总结--持续更新

为避免维护服务器、小型机为连接虚拟控制台&#xff0c;采用的办法不对而导致浪费时间&#xff0c;特做以下总结&#xff1a; ##1、DELL PowerEdge R510 idrac 6(intergrated dell remote access contoller 6) server 2003 R2 SP2 用chomre 版本 47.0.2526.80 m打开https://…

C++:thread | condition_variable|mutex

欢迎来到 破晓的历程的 博客 ⛺️不负时光&#xff0c;不负己✈️ 文章目录 引言thread创建线程传递参数给线程函数 mutexmutex常见用法 condition_variable&#xff1a;条件变量生产消费模型 引言 相信大家在Linux系统编程中都接触过线程创建和退出的相关系统调用&#xff0…

【数据结构与算法】简单聊聊图数据的存储

文章目录 1. 邻接矩阵&#xff08;Adjacency Matrix&#xff09;2. 邻接表&#xff08;Adjacency List&#xff09;3. 邻接多重表4. 十字链表5. 图数据库&#xff08;Graph Database&#xff09; 存储图数据的方法主要有几种&#xff0c;每种方法都有其特定的应用场景和优缺点。…

毕业设计项目-古典舞在线交流平台的设计与实现(源码/论文)

项目简介 基于springboot实现的&#xff0c;主要功能如下&#xff1a; 技术栈 后端框框&#xff1a;springboot/mybatis 前端框架&#xff1a;html/JavaScript/Css/vue/elementui 运行环境&#xff1a;JDK1.8/MySQL5.7/idea&#xff08;可选&#xff09;/Maven3&#xff08…

什么是物联网nb水表?

物联网NB水表是一种利用NB-IoT(窄带物联网)技术实现远程数据传输的智能水表。这种水表不仅能够精确计量用户的用水量&#xff0c;还能通过无线通信技术实现数据的远程传输和管理。下面我们来详细介绍物联网NB水表的主要特点和功能。 一、基本概念 -定义&#xff1a;物联网NB水…

如何优化spotbugsXml.xml文件来方便debug的落地方案来了

不优化的spotbugsXml.xml 使用maven 构建来运行spotbugs的小伙伴都知道&#xff0c;执行完下面的命令后 mvn clean install -U spotbugs:spotbugs 会在默认的在target目录下生成一个spotbugsXml.xml 文件&#xff0c;而打开这个文件&#xff0c;想要debug里面的具体问题&am…

嵌入式面试——FreeRTOS篇(六) 任务通知

本篇为&#xff1a;FreeRTOS 任务通知篇 任务通知简介 1、任务通知介绍 答&#xff1a; 任务通知&#xff1a;用来通知任务的&#xff0c;任务控制块中的结构体成员变量ulNotifiedValue就是这个通知值。 使用队列、信号量、事件标志组时都需要另外创建一个结构体&#xff0c…

Ubuntu终端配置

选择shell shell有很多&#xff0c;默认的是bash&#xff0c;一般就够用里&#xff0c;想要花里胡哨点就用zsh&#xff0c;还有最近比较火的fish 如果在刚开始安装完Ubuntu没有改shell&#xff0c;后面就不要改了。 安装的软件会设置环境变量&#xff0c;这些环境变量都是写入…

QDateTime 使用详解

QDateTime 是 Qt 框架中用于处理日期和时间的类。本篇文章详细介绍、通过示例 快速了解QDateTime的各种操作&#xff0c;包括: 当前时间、获取日期和时间、获取日期、获取时间、获取时间戳、格式化输出、年、月、日、QTime时间、获取微妙、操作日期和时间、添加时间、减去时间、…

无人机避障——4D毫米波雷达点云滤波去噪(四)

噪声的来源&#xff1a; 对于4D毫米波雷达的前后两帧点云数据进行去噪&#xff0c;可以采用多种方法。首先&#xff0c;需要了解点云数据的噪声来源&#xff0c;可能是由于硬件限制、环境干扰或目标本身的反射特性等因素造成的。噪声点通常包括漂移点、孤立点、冗余点和混杂点…

毕业设计项目——基于RISC-V的标签化跨层调度应用任务管理(论文/代码)

完整的论文代码见文章末尾 以下为核心内容 摘要 在现代操作系统中&#xff0c;高效的系统调度策略对于优化系统性能、提高资源利用率和保证系统稳定性至关重要。本文提出了一个基于Linux进程文件系统&#xff08;procfs&#xff09;的系统监控工具&#xff0c;旨在通过实时收…

Spring Cloud全解析:链路追踪之springCloudSleuth简介

文章目录 springCloudSleuth简介链路追踪&#xff1f;SpringCloudSleuth术语链路示意图zipkin依赖配置 springCloudSleuth简介 链路追踪&#xff1f; 什么是链路追踪&#xff1f;就是将一次分布式请求还原成调用链路&#xff0c;将一次分布式请求的调用情况集中展示&#xff…

算法:1、动态规划算法DP(Dynamic Programming)

算法介绍 动态规划&#xff08;Dynamic Programming&#xff0c;DP&#xff09;‌&#xff0c;通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。它的关键思想是对于最终结果依赖前序步骤的问题&#xff0c;将结果定义为状态值dp&#xff0c;然后推导出后续步骤由…

深度学习常见问题

1.YOLOV5和YOLOV8的区别 YOLOv5 和 YOLOv8 是两个版本的 YOLO&#xff08;You Only Look Once&#xff09;目标检测算法&#xff0c;它们在网络架构、性能优化、功能扩展等方面有显著的区别。YOLOv5 是 YOLO 系列的重要改进版本&#xff0c;而 YOLOv8 是最新的一次重大升级&am…

SQL性能优化指南:如何优化MySQL多表join场景

目录 多表join问题SQL 这里解释下 Using join buffer (Block Nested Loop)&#xff1a; 对性能产生的影响&#xff1a; 三种join算法介绍 join操作主要使用以下几种算法&#xff1a; &#xff08;1&#xff09;Nested Loop Join &#xff08;2&#xff09;Block Nested …

搭建企业域名服务器案例

任务要求&#xff1a; 某企业要建立一台应用于以下情况的主域名服务器 拥有一个C类网段地址&#xff0c;为202.101.55.0。企业域名注册为company.com。域名服务器的IP地址定位为202.101.55.55&#xff0c;主机名为dns.company.com。企业网通过路由器与Internet连接。要解析的…

第九届清洁能源与发电技术国际学术会议(CEPGT 2024)

第九届清洁能源与发电技术国际学术会议&#xff08;CEPGT 2024&#xff09; 2024 9th International Conference on Clean Energy and Power Generation Technology (CEPGT 2024) 【早投稿早录用&#xff0c;享受早鸟优惠】 第九届清洁能源与发电技术国际学术会议&#xff0…

记录一个Ajax发送JSON数据的坑,后端RequestBody接收参数小细节?JSON对象和JSON字符串的区别?

上半部分主要介绍我实际出现的问题&#xff0c;最终下面会有总结。 起因&#xff1a;我想发送post请求的data&#xff0c;但是在浏览器中竟然被搞成了地址栏编码 如图前端发送的ajax请求数据 如图发送的请求体&#xff1a; 很明显是keyvalue这种形式&#xff0c;根本就不是…

开源的键鼠共享工具「GitHub 热点速览」

十一长假回来&#xff0c;我的手放在落灰的键盘上都有些陌生了&#xff0c;红轴竟敲出了青轴般的响声&#xff0c;仿佛在诉说对假期结束的不甘。 假期回归的首更&#xff0c;让我们看看又有什么好玩的开源项目冲上了开源热榜。一套键盘和鼠标控制多台电脑的工具 deskflow&#…

supOS加速数实融合发展

作为工业操作系统领军企业&#xff0c;蓝卓受邀参加2024金砖国家新工业革命伙伴关系论坛&#xff0c;深度参与多个环节。在9月11日召开的金砖国家新工业革命伙伴关系论坛产融合作专题研讨上&#xff0c;蓝卓总经理谭彰分享了supOS在产融协同的最新实践&#xff0c;以及supOS进入…