JS處理大數相加問題

先從一個例子講起

var number1 = 10000000000000000000000000 + 11111111111111111111111111   //理論上number1的值應該是21111111111111111111111111(javascript中會表示爲科學計數法:2.111111111111111e+25)
var number2 = 21111111111111111111111000
console.log(number1 === number2)  //true
複製代碼

這個不用算簡單看一下都知道計算結果不對,最後幾位確定應該都是1的,但是爲何會獲得一個不對的值呢?javascript

JavaScript Number的精度丟失問題

由於JavaScriptNumber類型是遵循IEEE 754規範表示的,這就意味着JavaScript能精確表示的數字是有限的,JavaScript能夠精確到個位的最大整數是9007199254740992,也就是2的53次方,超過這個範圍就會精度丟失,形成JavaScript沒法判斷大小,從而會出現下面的現象:php

Math.pow(2, 53);    // 9007199254740992
Math.pow(2, 53) === Math.pow(2, 53) + 1;    // true
9007199254740992 === 9007199254740992 + 1;    // true
複製代碼

能夠從下面這張圖上看到JavaScript Number可以精確表示的上下限: html

image

解決方案

那當兩個數據相加時,其中一個或者兩個數據都超過了這個精度範圍,直接相加結果就會不許了,那怎麼解決呢?java

參考網上經常使用的一中方案是將Number轉爲String,而後將String轉爲Array,而且注意補齊較短的數組,將他們的長度標稱同樣再一一相加獲得一個新數組,再講和組成的新數組轉爲數字就能夠了,下面是實現代碼:數組

function sumString(a, b) {
  a = '0' + a;
  b = '0' + b;  //加'0'首先是爲了轉爲字符串,並且兩個數相加後可能須要進位,這樣保證了和的長度就是a、b中長的那個字符的長度
  var arrA = a.split(''),  //將字符串轉爲數組
      arrB = b.split(''),
      res = [],  //相加結果組成的數組
      temp = '',  //相同位數相加的值
      carry = 0,  //同位數相加結果大於等於10時爲1,不然爲0
      distance = a.length - b.length,  //計算兩個數字字符串的長度差
      len = distance > 0 ? a.length : b.length;  //和的長度
  // 在長度小的那個值前加distance個0,保證兩個數相加以前長度是想等的
  if(distance > 0) {
    for(let i = 0; i < distance; i++) {
      arrB.unShift('0');
    }
  }else{
    for(let i = 0; i < distance; i++) {
      arrA.unShift('0');
    }
  }
  // 如今獲得了兩個長度一致的數組,須要作的就是把他們想通位數的值相加,大於等於10的要進一
  // 最終獲得一個和組成的數組,將數組轉爲字符串,去掉前面多餘的0就獲得了最終的和
  for(let i = len-1; i >= 0; i--) {
    temp = Number(arrA[i]) + Number(arrB[i]) + carry;
    if(temp >= 10) {
      carry = 1;
      res.unshift((temp + '')[1])
    }
    else{
      carry = 0;
      res.unshift(temp)
    }
  }
  res = res.join('').replace(/^0/, '');
  console.log(res);
}
複製代碼

關於0.1+0.2 !== 0.3的問題

前面講到,在JavaScript中,使用浮點數標準IEEE 754表示數字的,在表示小數的時候,在轉化二進制的時候有些數是不能完整轉化的,好比0.3,轉化成二進制是一個很長的循環的數,是超過了JavaScript能表示的範圍的,因此近似等於0.30000000000000004。瀏覽器

這個是二進制浮點數最大的問題(不只 JavaScript,全部遵循 IEEE 754 規範的語言都是如此)。bash

怎麼判斷兩個值是否想等

在這裏咱們要引入ES6中在Number對象上新增的一個極小的常量Number.EPSILON。它表示1與大於1的最小浮點數之差,等於2的-52次方。學習

Number.EPSILON其實是 JavaScript 可以表示的最小精度。偏差若是小於這個值,就能夠認爲已經沒有意義了,即不存在偏差了。ui

因此能夠用這個來判斷兩個數浮點數是否想等:spa

function numIsEqual(lef, rig) {
    return Math.abs(lef - rig) < Number.EPSILON
}
複製代碼

若是考慮瀏覽器兼容的話能夠這樣寫:

function numIsEqual(lef, rig) {
    let EPSILON = Number.EPSILON ? Number.EPSILON : Math.pow(2,-52)
    return Math.abs(lef - rig) < EPSILON
}
複製代碼

固然咱們平常使用小數的話通常精確到小數點後兩位就夠了,是不會有太大的問題的,可是必定要記得用toFied()方法保留小數點後位數,不能直接取計算的值,避免偏差。

尾巴

關於JavaScript中數據的表示問題,我以前在極客時間的《深刻淺出計算機組成原理》中還聽到過一些,那裏面詳細講解了爲何浮點數的IEEE 754標準只能表示有限的數字,不過對於這麼底層的東西我還只能勉強意會,哈哈。

參考

相關文章
相關標籤/搜索