JavaScript(第二篇)浮点数运算精度问题,一网打尽所有相关面试题

本文详细解析了JavaScript中的浮点数运算精度问题,基于IEEE754标准,讨论了整数和浮点数的存储方式,指出精度丢失的原因,提供了解决方案,包括使用BigInt处理大整数和比较技巧。
摘要由CSDN通过智能技术生成

前言

本篇文章是《面试题一网打尽》专栏的 javascript 第二篇文章,彻底解决浮点数运算精度相关的面试题目。欢迎大家关注我的这个专栏。

一、IEEE 754 标准

我们经常在文档中看到这个标准感觉是什么高深的东西,其实 IEEE 是一个组织类似公司名称,754 就是一个编号而已,所以 IEEE754 就是这个组织提出的编号为 754 的规范文档,并不是什么高深的东西,这个文档我们可以在网上查看,也可以通过下面的网盘链接获取,这个文档的全称是《二进制浮点运算的IEEE754标准》

链接: https://pan.baidu.com/s/1wuKA_jE0cyn3X9w5WlSUKw 提取码: zh6t

1.1 IEEE754 规范

javascript 中整数和浮点数都是通过双精度浮点数的格式进行存储的,遵循IEEE754规范。下图截取自 IEEE754 规范的官方 pdf 文档

js 中应用的就是 double 的那一行,就是双精度,一共 11 + 53 = 64 位。

1.2 二进制的科学计数法

无论是整数还是浮点数,在存储之前都是需要先将十进制的数字转成二进制,再表示为科学记数法表示。二进制的科学记数法就是指数的底数为 2,比如

1.0001 * 2^10

转成科学记数法之后,一定是 1. 开头的,所以在存储的时候尾数部分【0001】存在后52位,但是实际上有一个永远是 1. 的开头,所以实际上是 53 为,这也是为什么最大的安全整数是 2^53 -1。

指数部分【10】存储在指数部分使用 11位存储,最后还有一位是符号位,0 表示正数,1表示负数。

一共就是 1 + 11 + 52 = 64 位。

符号位(1位)指数位(11位)尾数位(52位)

1.3 转成二进制

(1)十进制的数字整数部分转成二进制的方法是【除二取余法】

(2)小数部分转成二进制的方法是【乘二取整法】

所以对于一个即有整数部分也有小数部分的浮点数,整数部分和小数部分就需要分开来计算,然后再合并。比如整数部分得到【1101】,小数部分得到【00101】,合并一块就是【1101.00101】然后再用科学记数法表示。

1.10100101 * 2^3

1.4 指数部分的偏移量

在存储的时候指数部分需要加上一个固定的偏移量 1023【十进制】,然后再转成二进制进行存储。

为什么需要这个偏移量,因为指数部分一共 11 位,指数位的取值范围是【 -1023 ~ 1023】

1 - 2^10 ~ 2^10 -1

即便是负指数再加上这个偏移量【1023】之后也会变成正数,这样指数部分就不用符号位来指定指数的符号了。

二、数字存储

2.1 整数

整数部分转换成二进制使用除 2 取余法,当整数可以被精确地表示为 64 位浮点数时,连续的整数值不会存在偏差,但是当整数的值太大,即大于 2^53 -1【9007199254740992】,整数转化成二进制就不止 53 位了,可能54,55位,需要截取 53 前 53 位存储。所以无法精确表示。

所以有 Number.MAX_SAFE_INTEGER 表示最大安全值,是 2^53 -1, 超过这个安全值的整数,连续的整数值不能再用双精度浮点数一一对应表示了。

所有整数也都是用科学计数法存储的,比如

  1. 2^52 指数位是 52+1023的二进制形式, 小数位都是 0(因为是 2 的幂)
  2. 2^53 = 9007199254740992 二进制
  3. 能够被精确表示是指,转化成二进制后小于等于53位

对于大整数解决精度的问题,可以写个方法,通过转化成字符串的方式,模拟手工算术实现大数相加。

可以,使用Number.isInteger()判断数值是否是整数

2.2 对于浮点数

浮点数是指有小数位的数字。

浮点数转换成二进制,整数部分使用除 2 取余数,小数部分使用乘 2 取整的方法,转化成二进制101.000111....然后再把这个结果用使用科学计数法表示,1.01000111*2^2

IEEE 754标准定义了浮点数的二进制表示方法

  1. 分解:将浮点数分解为整数部分和小数部分
  2. 转换整数部分:使用➗2取余法,假如结果是 10011
  3. 转换小数部分:使用✖️2取整法,无限循环小数取所需要的精度即可,假设结果是 00101
  4. 格式化:合并整数部分和小数部分 10011.00101,用科学计数法表示为指数形式1.001100101 * 2^4,【二进制数中的最高位1,并将其左侧的所有位移到小数点右边,形成一个介于1(含)和2(不含)之间的数。这个数称为尾数(Mantissa)】【小数点左移正指数,小数点右移负指数】
  5. 编码尾数:将尾数的小数部分和整数部分(整数部分一定是1,所以常被省略)【001100101】存在后52位中
  6. 编码指数:指数【4】,以特定的偏移量表示,在IEEE 754双精度浮点数中,偏移量是1023,所以实际指数存储的是,科学计数法的指数【4】 + 1023 = 1027 ,将1027转化为二进制的形式,存在11位的指数位
  7. 加上符号位,+0、 -1

有些小数使用乘2取整方法得到的结果是无限循环的值,所以在实际存储中只能截取其中的52位,就存在精度的问题。

对于浮点数的精度问题可以先转化成整数再做除法,对于浮点数的比较可以使用Number.EPSILON,计算两个浮点数的差值是否小于这个浮点数精度。

三、面试题

3.1 说说 js 为什么会存在数字精度丢失的问题,以及如何进行解决

  1. js 中的数字类型在内存中是按照 IEEE 754 规范中双精度浮点数存储的(共 64 位),(单精度浮点数32位)
  2. IEEE 754 是《二进制浮点数规范》其中 IEEE 代表一个公司,754 是一个规范的编号
  3. 64 = 1位符号位(0 代表整数,1代表负数) + 11 位指数位(k+1) + 52 位小数位 其实是 53 位,科学计数法的第一位永远是 1,也就是有效数总是 1.xxx 的形式,所以忽略不存了)
  4. 任何数字都可以使用科学计数法表示,在存储数字时是按照以 2 为基数科学计数法存储的,一个数值使用科学计数法表示之后,后 52 位只存储科学计数法的小数部分。所以能够存储的数值在一定范围内,有些数字使用科学计数法表示小数部分超过 52 位,就会精度丢失
  5. 可以使用 BigInt 来解决大整数相加,或者手动实现一个大数相加的方法。

3.2 为什么0.1 + 0.2 不等于 0.3?

  1. 这涉及到 JavaScript 使用 IEEE 754 标准来表示浮点数,而在该标准下,某些十进制小数无法完全准确表示为二进制小数,导致精度问题。
  2. 可以把小数*10转化成整数或者,使用 Number.EPSILON 比较差值

3.3 如何解决浮点数运算精度问题?

  1. 可以使用一些技巧,比如将浮点数转换为整数进行运算,然后再转换回浮点数。另外,可以使用库如 BigNumber.js 或者使用 ECMAScript 新引入的 Number.EPSILON 进行比较。
  2. 使用 toFixed(小数位数) 、toPrecision(整数+小数的总共的位数)

3.4 解释下 JavaScript 中的 Number.EPSILON。

  1. Number.EPSILON 是 JavaScript 中表示1与大于 1 的最小浮点数之差。它可以用于比较两个浮点数是否相等,例如 Math.abs(a - b) < Number.EPSILON。

3.5 如何在 JavaScript 中判断一个数字是整数?

  1. 可以使用 Number.isInteger() 方法,例如 Number.isInteger(5) 会返回 true,而 Number.isInteger(5.5) 会返回 false。

3.6 解释下 NaN(Not a Number)在浮点数运算中的作用。

  1. 当涉及到无效的浮点数运算时,JavaScript 会返回 NaN。NaN 是一种特殊的浮点数,表示一个无效或未定义的数值。
  2. 0/0
  3. Infinity/Infinity 、Infinity - Infinity
  4. 非数学运算,比如对负数开平方根、对负数取对数等
  5. 使用未初始化的变量进行运算
  6. 当阶码(指数部分)全为1时,只要位数不全为0 就是NaN

3.7 如何比较两个浮点数是否相等?

  1. 由于浮点数精度的问题,直接使用 == 或 === 可能会导致错误的比较结果。推荐使用 Math.abs(a - b) < Number.EPSILON 这种方式来比较浮点数的相等性。希腊字母

3.8 在 JavaScript 中如何处理大数运算?

  1. 对于大数运算,可以使用第三方库如 BigNumber.js,它提供了对大整数和大浮点数的支持。
  2. 自定义大数运算函数,具体代码请参考这篇文章
  3. 接受字符串形式的大数作为参数
  4. js 特别大,或者特别小的数会用指数格式展示,所以在实现大数相加的时候参数要是字符串,
  5. 用 toExponential 展示数字的指数表示形式

  • 23
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值