【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 给浏览器使用

相关推荐
借来一夜星光3 分钟前
【前端动效】原生js实现拖拽排课效果
前端·javascript·css3·html5
未命名冀40 分钟前
多线程面试相关
java·面试·多线程
2401_897444641 小时前
AI在软件工程教育中的应用与前景展望
前端·人工智能·软件工程
林涧泣1 小时前
【Uniapp-Vue3】使用defineExpose暴露子组件的属性及方法
前端·vue.js·uni-app
移知2 小时前
备战春招—FPGA 2024年的面试题库
fpga开发·面试·职场和发展
无限大.2 小时前
Vue 常用指令详解(附代码实例)
前端·javascript·vue.js
Python私教2 小时前
做编程教学多年,我总结了这些错误页面的封装技巧,附赠程序员笑话!
前端·javascript·vue.js
网络点点滴2 小时前
现代JavaScript开发
开发语言·javascript·ecmascript
棋丶2 小时前
Webpack和Vite的区别
前端·webpack·node.js
lin-lins2 小时前
微前端介绍
前端