天天看點

前端面試系列-JavaScript-精度問題(0.1+0.2!==0.3、大數相加)一、雙精度浮點數二、十進制小數轉二進制小數三、關于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 标準下的雙精度浮點數。

前端面試系列-JavaScript-精度問題(0.1+0.2!==0.3、大數相加)一、雙精度浮點數二、十進制小數轉二進制小數三、關于0.1+0.2 !== 0.3的問題四、大數相加

可以從圖中看到 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

繼續閱讀