【100%面试题】总有一天在JS 精度问题上你会踩坑

JS 精度问题是一个经典面试问题,不管是对 Javascript 语言细节还是对业务熟练度的考察,它都非常适合,也是中小企业以及大厂面试题库必备。

1. 问题现象

1.1 浮点数精度问题

javascript 复制代码
console.log(0.1 + 0.2); // 输出 0.30000000000000004
console.log(0.1 + 0.7); // 输出 0.7999999999999999

1.2 整数精度问题

javascript 复制代码
console.log(9007199254740991 + 1); // 输出 9007199254740992(正确)
console.log(9007199254740991 + 2); // 输出 9007199254740992(错误)

2. 中小公司会怎么问?

2.1. 0.1 + 0.2 输出什么以及为什么?

浮点数在计算机中以二进制表示,某些十进制小数(如 0.1 和 0.2)无法精确表示,导致运算结果不精确。

2.2. JS Numer 类型的数值范围?

JavaScript 中的 Number 类型能表示的安全整数范围是 (-2的53次方) - (2的53次方 - 1),超过这个范围可能会丢失精度。

2.3. 如何解决数值精度问题?

(1)避免浮点运算,转换为整数再计算,这种方式比较麻烦

javascript 复制代码
const result = (0.1 * 10 + 0.2 * 10) / 10;
console.log(result); // 输出 0.3

(2)借助第三库解决,推荐使用 decimal.js www.npmjs.com/package/dec... 等库。

javascript 复制代码
const Decimal = require('decimal.js');
console.log(new Decimal(0.1).plus(0.2).toString()); // 输出 "0.3"

2.4. 为什么后端返回的 int64 值,前端收到不准确

1)后端通常将数据序列化为 JSON 格式字符串,发送给前端。

2)前端接收到 JSON 数据后,通过 window.JSON.parse 序列化,其中的 int64 字段被解析为 JS 的 Number 类型。

3)如果 int64 的值超出了安全范围,会导致精度丢失。

需要重写 window.JSON.parse,比如借助 jsonbigint www.npmjs.com/package/jso... ,或许 axios 库真应该支持自定义 parse 功能 😂。

3.3. 大厂会怎么问

大厂一般会循序渐进,从简单到困难逐渐深入。

3.1. 0.1 + 0.2 = 0.3 吗?

浮点数在计算机中以二进制表示,某些十进制小数(如 0.1 和 0.2)无法精确表示,导致运算结果不精确。

3.2 浮点数是怎么被表示的

IEEE二进制浮点数算术标准 baike.baidu.com/item/IEEE%2...,如果你理解起来有困难,可以在群里群主,我们有专门的人免费解答。

二进制 中,数字只能用 2 的幂次 来表示,浮点数使用科学计数法表示,例如:

在这种表示方法下,小数只能用 2的负幂来表示。

比如将 0.125 转换成二进制小数,我们需要不断将小数部分乘以 2,并记录整数部分,直到小数部分为 0 或者达到所需精度。

javascript 复制代码
0.125 * 2 // 整数部分0, 小数部分 0.25
0.25  * 2 // 整数部分0, 小数部分 0.5
0.5   * 2 // 整数部分1, 小数部分 0

计算完成因此,0.125 的二进制为 0.001 采用科学计数法:符号位是 0,指数为 -3, 尾数位为0

javascript 复制代码
0.125 = 2^-3 * 1.0

我们再来计算 0.1 的二进制数

javascript 复制代码
0.1   * 2 // 整数部分0, 小数部分 0.2
0.2   * 2 // 整数部分0, 小数部分 0.4
0.4   * 2 // 整数部分0, 小数部分 0.8
0.8   * 2 // 整数部分1, 小数部分 0.6 // 标记M1
0.6   * 2 // 整数部分1, 小数部分 0.2 
0.2   * 2 // 整数部分0, 小数部分 0.4
0.4   * 2 // 整数部分0, 小数部分 0.8
0.8   * 2 // 整数部分1, 小数部分 0.6 // 又回到M1

所以他会导致无限循环计算,最终表示 0.000110011(0011)一直循环,采用科学计数法时不能正确的表示,因为尾数位的长度是有限的,所以 0.1 实际存储的是一个近似值,而不是精确的二进制表示。在 IEEE 754 双精度浮点数结构中,尾数位的长度是 52 位。

3.3 大数相加的实现

这个考察有没有去思考第三方库是怎么实现的,不要只是会用,还要明白其中的原理。

核心思路就是模拟算术过程

主要实现代码:

javascript 复制代码
function addStrings(strA, strB) {
  if (strA === "0") return strB;
  if (strB === "0") return strA;

  var m = strA.length;
  var n = strB.length;
  var maxLen = Math.max(m, n);

  var list = (new Array(maxLen + 1)).fill(0);

  // 倒序填充 arrA
  var arrA = new Array(maxLen + 1).fill(0);
  for (let i = 0; i < m; i++) {
    arrA[i] = Number(strA[m - i - 1]);
  }

  // 倒序填充 arrB
  var arrB = new Array(maxLen + 1).fill(0);
  for (let i = 0; i < n; i++) {
    arrB[i] = Number(strB[n - i - 1]);
  }

  // 计算结果填充到 list
  for (let i = 0; i < list.length; i++) {
    var t = list[i];
    t += arrA[i];
    t += arrB[i];

    if (t > 9) {
      t = t - 10;
      list[i + 1] = 1;
    }
    list[i] = t;
  }

  // 转为字符串
  var strRet = "";
  let findFirst = false;
  for (let i = list.length - 1; i >= 0; i--) {
    if (findFirst) {
      strRet += list[i].toString();
    } else {
      if (list[i] === 0) {
        continue;
      }
      findFirst = true;
      strRet += list[i].toString();
    }
  }

  return strRet;
}

// 用例
console.log(addStrings("5555", "6666"))

浮点数相加

将整数部分 和 小数部分 提取出来分别计算,如果新的小数的位数比两者都要大,说明整数部分需要进1,而小数位需要去掉第一位。然后返回二者的拼接。

javascript 复制代码
function numberSum(strA, strB){
  if(!(strA.includes(".")|| strB.includes("."))){
    // 整数
    return bigNumberSum(strA, strB)
  }

  // 整数部分
  var strAInt = (strA.split('.'))[0];
  var strBInt = (strB.split('.'))[0];
  var strInt = bigNumberSum(strAInt, strBInt);

  // 小数部分
  var strADot = (strA.split('.'))[1];
  var strBDot = (strB.split('.'))[1];

  // 小数对其
  var max = Math.max(strADot.length, strBDot.length);
  for(let i =0; i < max; i++){
    if(strADot[i] === undefined) strADot += "0";
    if(strBDot[i] === undefined) strBDot += "0";
  }
  var strDot = bigNumberSum(strADot, strBDot);
  if(strDot.length > max){
    strInt = bigNumberSum(strInt,"1");
    strDot = strDot.substr(1);
  }
  return strInt +"."+ strDot
}

// 用例
var a = "12345678932131.55555";
var b = "98765432156678.5444";
var r = numberSum(a, b);
console.log(`${a} + ${b}`);
console.log(r);
console.log(Number(a) + Number(b));

3.4 如何解决相关性能问题?

JS 不擅长计算密集型,比如字符串处理(JSON.parse)就属于计算密集型,在使用第三方库时比如 json-bigint 会遇到性能问题,该如何解决?

可以基于一些高性能的库(C或者C++),自定义魔改打包为 Wasm 给浏览器使用

相关推荐
加班是不可能的,除非双倍日工资2 小时前
css预编译器实现星空背景图
前端·css·vue3
wyiyiyi2 小时前
【Web后端】Django、flask及其场景——以构建系统原型为例
前端·数据库·后端·python·django·flask
gnip3 小时前
vite和webpack打包结构控制
前端·javascript
excel3 小时前
在二维 Canvas 中模拟三角形绕 X、Y 轴旋转
前端
阿华的代码王国3 小时前
【Android】RecyclerView复用CheckBox的异常状态
android·xml·java·前端·后端
一条上岸小咸鱼3 小时前
Kotlin 基本数据类型(三):Booleans、Characters
android·前端·kotlin
Jimmy3 小时前
AI 代理是什么,其有助于我们实现更智能编程
前端·后端·ai编程
ZXT4 小时前
promise & async await总结
前端
Jerry说前后端4 小时前
RecyclerView 性能优化:从原理到实践的深度优化方案
android·前端·性能优化
画个太阳作晴天4 小时前
A12预装app
linux·服务器·前端