JavaScript四捨五入及精度丟失問題

在JS中使用數值計算時,常常會遇到未知的結果。
問題:用toFixed保留小數時,四捨五入規則不固定
image.png
常見的解決思路:將小數放大爲整數,進行四捨五入後,再縮小爲小數。html

// 四捨五入(若要直接捨去,flag可設爲0)
function myFixed(num, s) {
  var digit = Math.pow(10, s)
  var flag = (num >= 0 ? 0.5 : -0.5)
  var res = num * digit + flag
  res = parseInt(res, 10) / digit
  return res + ''
}

image.png


問題:小數加減乘除時,有時會出現精度丟失問題:
image.png
常見的解決思路:將小數放大爲整數,進行運算後,再縮小爲小數。git

/**
 * floatObj 包含加減乘除四個方法,能確保浮點數運算不丟失精度
 *
 * 咱們知道計算機編程語言裏浮點數計算會存在精度丟失問題(或稱舍入偏差),其根本緣由是二進制和實現位數限制有些數沒法有限表示
 * 如下是十進制小數對應的二進制表示
 *      0.1 >> 0.0001 1001 1001 1001…(1001無限循環)
 *      0.2 >> 0.0011 0011 0011 0011…(0011無限循環)
 * 計算機裏每種數據類型的存儲是一個有限寬度,好比 JavaScript 使用 64 位存儲數字類型,所以超出的會捨去。捨去的部分就是精度丟失的部分。
 *
 * ** method **
 *  add / subtract / multiply /divide
 *
 * ** explame **
 *  0.1 + 0.2 == 0.30000000000000004 (多了 0.00000000000004)
 *  0.2 + 0.4 == 0.6000000000000001  (多了 0.0000000000001)
 *  19.9 * 100 == 1989.9999999999998 (少了 0.0000000000002)
 *
 * floatObj.add(0.1, 0.2) >> 0.3
 * floatObj.multiply(19.9, 100) >> 1990
 *
 */
var floatObj = function() {
    
  /*
   * 判斷obj是否爲一個整數
   */
  function isInteger(obj) {
      return Math.floor(obj) === obj
  }
  
  /*
   * 將一個浮點數轉成整數,返回整數和倍數。如 3.14 >> 314,倍數是 100
   * @param floatNum {number} 小數
   * @return {object}
   *   {times:100, num: 314}
   */
  function toInteger(floatNum) {
      var ret = {times: 1, num: 0}
      var isNegative = floatNum < 0
      if (isInteger(floatNum)) {
          ret.num = floatNum
          return ret
      }
      var strfi  = floatNum + ''
      var dotPos = strfi.indexOf('.')
      var len    = strfi.substr(dotPos+1).length
      var times  = Math.pow(10, len)
      var intNum = parseInt(Math.abs(floatNum) * times + 0.5, 10)
      ret.times  = times
      if (isNegative) {
          intNum = -intNum
      }
      ret.num = intNum
      return ret
  }
  
  /*
   * 核心方法,實現加減乘除運算,確保不丟失精度
   * 思路:把小數放大爲整數(乘),進行算術運算,再縮小爲小數(除)
   *
   * @param a {number} 運算數1
   * @param b {number} 運算數2
   * @param digits {number} 精度,保留的小數點數,好比 2, 即保留爲兩位小數
   * @param op {string} 運算類型,有加減乘除(add/subtract/multiply/divide)
   *
   */
  function operation(a, b, digits, op) {
      var o1 = toInteger(a)
      var o2 = toInteger(b)
      var n1 = o1.num
      var n2 = o2.num
      var t1 = o1.times
      var t2 = o2.times
      var max = t1 > t2 ? t1 : t2
      var result = null
      switch (op) {
          case 'add':
              if (t1 === t2) { // 兩個小數位數相同
                  result = n1 + n2
              } else if (t1 > t2) { // o1 小數位 大於 o2
                  result = n1 + n2 * (t1 / t2)
              } else { // o1 小數位 小於 o2
                  result = n1 * (t2 / t1) + n2
              }
              return result / max
          case 'subtract':
              if (t1 === t2) {
                  result = n1 - n2
              } else if (t1 > t2) {
                  result = n1 - n2 * (t1 / t2)
              } else {
                  result = n1 * (t2 / t1) - n2
              }
              return result / max
          case 'multiply':
              result = (n1 * n2) / (t1 * t2)
              return result
          case 'divide':
              result = (n1 / n2) * (t2 / t1)
              return result
      }
  }
  
  // 加減乘除的四個接口
  function add(a, b, digits) {
      return operation(a, b, digits, 'add')
  }
  function subtract(a, b, digits) {
      return operation(a, b, digits, 'subtract')
  }
  function multiply(a, b, digits) {
      return operation(a, b, digits, 'multiply')
  }
  function divide(a, b, digits) {
      return operation(a, b, digits, 'divide')
  }
  
  // exports
  return {
      add: add,
      subtract: subtract,
      multiply: multiply,
      divide: divide
  }
}();

因此最上面四捨五入的方法也存在風險,可調用floatObj完善:編程

// 四捨五入(若要直接捨去,flag可設爲0)
function myFixed(num, s) {
  var digit = Math.pow(10, s)
  var flag = (num >= 0 ? 0.5 : -0.5)
  var res = floatObj.add(floatObj.multiply(num, digit), flag)
  res = floatObj.divide(parseInt(res, 10), digit)
  return res + ''
}
參考: https://www.cnblogs.com/snand...
相關文章
相關標籤/搜索