JS計算精度小記

一. 前置知識點

1. 十進制如何轉爲二進制?

整數部分除二取餘數, 直到商爲0,逆序排列,小數部分乘2取整,順序排列,直到積中小數部分爲0或者到達要求精度javascript

8轉爲二進制

8 / 2 = 4...00
4 / 2 = 2...00
2 / 2 = 1...00
1 / 2 = 0...11

二進制結果爲:1000

0.25轉爲二進制

0.25 * 2 = 0.500
0.50 * 2 = 1.001

二進制結果爲:01

因而可得出8.25的二進制表示:1000.01
複製代碼
2. 二進制如何轉爲十進制?

注意:二進制轉爲十進制不分整數部分與小數部分。java

二進制1000.01轉爲十進制

1 * 2^3 + 0 * 2^2 + 0 * 2^1 + 0 * 2^0 + 0 * 2^-1 + 0 * 2^-2 = 8.25
複製代碼

二. javascript是如何保存數字的

JavaScript 裏的數字是採用 IEEE 754 標準的 64 位 double 雙精度浮點數函數

  1. sign bit(符號): 用來表示正負號,1位 (0表示正,1表示負)ui

  2. exponent(指數): 用來表示次方數,11位spa

  3. mantissa(尾數): 用來表示精確度,52位3d

對於沒有接觸的讀者來講,以上可能理解起來很模糊,不要緊,接下來咱們用案例具體說明其流程,先看一下上述的十進制數8.25在JS中是如何保存的code

  1. 十進制的8.25會被轉化爲二進制的1000.01
  2. 二進制1000.01可用二進制的科學計數法1.00001 * 2^4表示;
  3. 1.00001 * 2^4的小數部分00001(二進制)就是mantissa(尾數)了,4(十進制)加上1023就是exponent(指數)了(這裏後面講解爲何要加上1023);
  4. 接下來指數4要加上1023後轉爲二進制10000000011
  5. 咱們的十進制8.25是一個正數,因此符號爲二進制表示爲0
  6. 8.25最終的二進制保存0-10000000011-0000100000000000000000000000000000000000000000000000

注意點:cdn

  1. 不夠位的咱們都用0補充;
  2. 步驟2得出的科學計數中的整數本分1咱們好像忘記,這裏由於Javascript爲了更最大限度的提升精確度,而省略了這個1, 這樣在咱們咱們原本只能保存(二進制)52位的尾數,實際是有(二進制)53位的;
  3. 指數部分是11位,表示的範圍是[0, 2047],因爲科學計數中的指數可正可負,因此,中間數爲 1023,[0,1022] 表示爲負,[1024,2047] 表示爲正, 這也解釋了爲何咱們科學計數中的指數要加上1023進行存儲了。

三. javascript是如何讀取數字的

咱們仍是以8.25的二進制0-10000000011-0000100000000000000000000000000000000000000000000000來說述blog

  1. 首先咱們獲取指數部分的二進制1000000001,轉化爲十進制爲10271027減去1023就是咱們實際的指數4了;
  2. 獲取尾數部分0000100000000000000000000000000000000000000000000000實際是0.00001(後面的0就不寫了),而後加上咱們忽略的1,得出1.00001
  3. 由於首位爲0,因此咱們的數爲正數,得出二進制的科學計數爲1.00001 * 2^4,接着再轉爲十進制數,就獲得了咱們的8.25

四. 從0.1+0.2來看javascript精度問題

這裏就要進入咱們的正題了,看懂了前面的原理說明,這部分將會變得很好理解了。ip

要計算0.1+0.2,首先計算要先讀取到這兩個浮點數

0.1存儲爲64位二進制浮點數

沒有忘記以上步驟吧~

  1. 先將0.1轉化爲二進制的整數部分爲0,小數部分爲0001100110011001100110011001100110011...咦,這裏竟然進入了無限循環,那怎麼辦呢?暫時先無論;
  2. 咱們獲得的無限循環的二進制數用科學計數表示爲1.100110011001100110011001100110011... * 2^-4
  3. 指數位便是-4 + 1023 = 1019,轉化位11位二進制數01111111011
  4. 尾數位是無限循環的,可是雙精度浮點數規定尾數位52位,因而超出52位的將被略去,保留1001100110011001100110011001100110011001100110011010
  5. 最後得出0.1的64位二進制浮點數:0-01111111011-1001100110011001100110011001100110011001100110011010

同上,0.2存儲爲64位二進制浮點數:0-01111111100-1001100110011001100110011001100110011001100110011010

讀取到兩個浮點數的64爲二進制後,再將其轉化爲可計算的二進制數

  1. 0.1轉化爲1.1001100110011001100110011001100110011001100110011010 * 2^(1019 - 1023)——0.00011001100110011001100110011001100110011001100110011010;
  2. 0.2轉化爲1.1001100110011001100110011001100110011001100110011010 * 2^(1020 - 1023)——0.0011001100110011001100110011001100110011001100110011010;

接着將兩個浮點數的二進制數進行加法運算,得出0.0100110011001100110011001100110011001100110011001100111轉化爲十進制數即爲0.30000000000000004

不難看出,精度缺失是在存儲這一步就丟失了,後面的計算只是在不精準的值上進行的運算。

五. javascript如何解決精度問題出現的計算錯誤問題

對於小數或者整數的簡單運算可以下解決:

function numAdd(num1, num2) { 
  let baseNum, baseNum1, baseNum2; 
  try { 
    baseNum1 = String(num1).split(".")[1].length; 
  } catch (e) { 
    baseNum1 = 0; 
  } 
  try { 
    baseNum2 = String(num2).split(".")[1].length; 
  } catch (e) { 
    baseNum2 = 0;
  } 
  baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
  return (num1 * baseNum + num2 * baseNum) / baseNum;
};

複製代碼

如:0.1 + 0.2 經過函數處理後,至關於 (0.1 * 10 + 0.2 * 10) / 10

可是如同咱們前面所瞭解的,浮點數在存儲的時候就已經丟失精度了,因此浮點數乘以一個基數仍然會存在精度缺失問題,好比2500.01 * 100 = 250001.00000000003, 因此咱們能夠在以上函數的結果之上使用toFixed(),保留須要的小數位數。

一些複雜的計算,能夠引入一些庫進行解決。

相關文章
相關標籤/搜索