前端面试系列-JavaScript-精度问题(0.1+0.2!==0.3、大数相加)

文章目录

  • 一、双精度浮点数
  • 二、十进制小数转二进制小数
    • 精度丢失
  • 三、关于0.1+0.2 !== 0.3的问题
  • 四、大数相加
    • 1.S 中整数的安全范围
    • 2.实现大数相加

一、双精度浮点数

ECMAScript 中的 Number 类型使用 IEEE754 标准来表示整数和浮点数值。所谓 IEEE754 标准,全称 IEEE 二进制浮点数算术标准,这个标准定义了表示浮点数的格式等内容。

在 IEEE754 中,规定了四种表示浮点数值的方式:单精确度(32位)、双精确度(64位)、延伸单精确度、与延伸双精确度。像 ECMAScript 采用的就是双精确度,也就是说,会用 64 位字节来储存一个浮点数。

在 JavaScript 中所有数值都以 IEEE-754 标准的 64 bit 双精度浮点数进行存储的。先来了解下 IEEE-754 标准下的双精度浮点数。
在这里插入图片描述
可以从图中看到 IEEE-754 标准下双精度浮点数由三部分组成,分别如下:

  • sign(符号): 占 1 bit, 表示正负;
  • exponent(指数): 占 11 bit,表示范围;
  • mantissa(尾数): 占 52 bit,表示精度,多出的末尾如果是 1 需要进位;

二、十进制小数转二进制小数

拿 173.8125 举例如何将之转化为二进制小数。

  1. 针对整数部分 173,采取除 2 取余,逆序排列;
173 / 2 = 86 ... 1
86 / 2 = 43 ... 0
43 / 2 = 21 ... 1   ↑
21 / 2 = 10 ... 1   | 逆序排列
10 / 2 = 5 ... 0    |
5 / 2 = 2 ... 1     |
2 / 2 = 1 ... 0
1 / 2 = 0 ... 1

得整数部分的二进制为 10101101。

  1. 针对小数部分 0.8125,采用乘 2 取整,顺序排列;
0.8125 * 2 = 1.625  |
0.625 * 2 = 1.25    | 顺序排列
0.25 * 2 = 0.5      |
0.5 * 2 = 1         ↓

得小数部分的二进制为 1101。

  1. 将前面两部的结果相加,结果为 10101101.1101;

精度丢失

将十进制小数 0.1 转为二进制:

0.1 * 2 = 0.2
0.2 * 2 = 0.4 // 注意这里
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
0.2 * 2 = 0.4 // 注意这里,循环开始
0.4 * 2 = 0.8
0.8 * 2 = 1.6
0.6 * 2 = 1.2
...

可以发现有限十进制小数 0.1 却转化成了无限二进制小数 0.00011001100…,可以看到精度在转化过程中丢失了!

能被转化为有限二进制小数的十进制小数的最后一位必然以 5 结尾(因为只有 0.5 * 2 才能变为整数)。所以十进制中一位小数 0.1 ~ 0.9 当中除了 0.5 之外的值在转化成二进制的过程中都丢失了精度。

三、关于0.1+0.2 !== 0.3的问题

0.1对应 64 个字节位的完整表示是:

0 01111111011 1001100110011001100110011001100110011001100110011010

0.2 表示的完整表示是:

0 01111111100 1001100110011001100110011001100110011001100110011010

当 0.1 存下来的时候,就已经发生了精度丢失,当我们用浮点数进行运算的时候,使用的其实是精度丢失后的数。

// 计算过程
0.00011001100110011001100110011001100110011001100110011010
0.0011001100110011001100110011001100110011001100110011010

// 相加得
0.01001100110011001100110011001100110011001100110011001110
0.01001100110011001100110011001100110011001100110011001110 
转化为十进制就是 0.30000000000000004。

所以:

0.1+0.2 === 0.30000000000000004
true
0.1+0.2 ===0.3
false

四、大数相加

1.S 中整数的安全范围

-9007199254740991~9007199254740991

console.log(Number.MAX_SAFE_INTEGER); //9007199254740991
console.log(Number.MIN_SAFE_INTEGER); //-9007199254740991
Math.pow(2, 53) - 1     // 9007199254740991
Math.pow(2,53) === Math.pow(2,53) + 1        //true

2.实现大数相加

用字符串来表示数据,不会丢失精度

  • 将字符串长度对齐
  • 从个位开始相加
let a = "9007199254740991";
let b = "1234567899999999999";

function add(a ,b){
   //取两个数字的最大长度
   let maxLength = Math.max(a.length, b.length);
   //用0去补齐长度
   a = a.padStart(maxLength , 0);//"0009007199254740991"
   b = b.padStart(maxLength , 0);//"1234567899999999999"
   //定义加法过程中需要用到的变量
   let t = 0;
   let f = 0;   //"进位"
   let sum = "";
   for(let i=maxLength-1 ; i>=0 ; i--){
      t = parseInt(a[i]) + parseInt(b[i]) + f;
      f = Math.floor(t/10);
      sum = t%10 + sum;
   }
   if(f == 1){
      sum = "1" + sum;
   }
   return sum;
}
add(a ,b); //结果为:1243575099254740990

参考:https://zhuanlan.zhihu.com/p/72179476

本文链接:https://blog.csdn.net/qq_39903567/article/details/115199703

你可能感兴趣的:(javaScript,javascript,精度问题,前端,面试)