>웹 프론트엔드 >JS 튜토리얼 >js의 계산 정확도에 대해 이야기합시다.

js의 계산 정확도에 대해 이야기합시다.

angryTom
angryTom앞으로
2019-11-25 17:27:552280검색

js의 계산 정확도에 대해 이야기합시다.

JS를 사용하여 계산을 수행하면 분명히 계산 정확도 문제(또는 반올림 오류)가 발생한다는 것을 우리 모두 알고 있지만 이러한 함정을 피하는 방법은 다음과 같습니다. 인터넷에서 편집한 계획입니다. 토론을 환영합니다.

js의 계산 정확도에 대해 이야기합시다.

정밀도 손실 이유

컴퓨터의 바이너리 구현과 비트 수 제한으로 인해 일부 숫자는 유한하게 표현할 수 없습니다. 파이 3.1415926…, 1.3333… 등과 같은 일부 무리수를 유한하게 표현할 수 없는 것과 같습니다. JavaScript는 64비트를 사용하여 숫자 유형을 저장하므로 초과된 유형은 삭제됩니다. 생략된 부분은 정확성이 떨어지는 부분이다.

다음은 소수에 해당하는 이진수 표현입니다

0.1 >> 0.0001 1001 1001 1001…(1001无限循环)
0.2 >> 0.0011 0011 0011 0011…(0011无限循环)

해결책

더 복잡한 계산 라이브러리가 필요한 경우 math.js와 같은 잘 알려진 라이브러리를 고려해 보세요

부동 소수점 숫자(소수점)

For 소수점, 프런트엔드 특히 일부 전자상거래 웹사이트에 금액 및 기타 데이터가 포함된 경우 문제가 발생할 가능성이 여전히 많습니다. 해결 방법: 소수를 정수에 넣은 다음(배수 곱하기) 다시 원래 배수로 줄입니다(배수 나누기). 정수로 변환한 후의 연산 결과는 Math.pow(2,53)

// 0.1 + 0.2
(0.1*10 + 0.2*10) / 10 == 0.3 // true

를 초과할 수 없습니다. 부동소수점 정밀도 연산

/**
 * floatObj 包含加减乘除四个方法,能确保浮点数运算不丢失精度
 *
 * ** 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}
        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(floatNum * times + 0.5, 10)
        ret.times  = times
        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
    }
}();

사용 방법:

floatTool.add(a,b);//相加
floatTool.subtract(a,b);//相减
floatTool.multiply(a,b);//相乘
floatTool.divide(a,b);//相除

초대형 정수

연산 결과가 Math.pow(2,53)의 정수(9007199254740992)를 초과하지 않지만 다음을 사용할 수도 있습니다. 위의 방법이지만 이를 초과하는 경우 실제 시나리오에서는 가능할 수 있습니다. 배치 번호 및 번호 세그먼트와 같은 몇 가지 요구 사항이 있을 것입니다. 여기에서도 코드를 직접 입력하는 것이 해결책을 찾았습니다.

온라인 계산: https://www.shen.ee/math.html

function compare(p, q) {
  while (p[0] === '0') {
    p = p.substr(1);
  }
  while (q[0] === '0') {
    q = q.substr(1);
  }
  if (p.length > q.length) {
    return 1;
  } else if (p.length < q.length) {
    return -1;
  } else {
    let i = 0;
    let a, b;
    while (1) {
      a = parseInt(p.charAt(i));
      b = parseInt(q.charAt(i));
      if (a > b) {
        return 1;
      } else if (a < b) {
        return -1;
      } else if (i === p.length - 1) {
        return 0;
      }
      i++;
    }
  }
}
function divide(A, B) {
  let result = [];
  let max = 9;
  let point = 5;
  let fill = 0;
  if (B.length - A.length > 0) {
    point += fill = B.length - A.length;
  }
  for (let i = 0; i < point; i++) {
    A += &#39;0&#39;;
  }
  let la = A.length;
  let lb = B.length;
  let b0 = parseInt(B.charAt(0));
  let Adb = A.substr(0, lb);
  A = A.substr(lb);
  let temp, r;
  for (let j = 0; j < la - lb + 1; j++) {
    while (Adb[0] === &#39;0&#39;) {
      Adb = Adb.substr(1);
    }
    if (Adb.length === lb) {
      max = Math.ceil((parseInt(Adb.charAt(0)) + 1) / b0); // 不可能取到这个最大值,1<= max <= 10
    } else if (Adb.length > lb) {
      max = Math.ceil((parseInt(Adb.substr(0, 2)) + 1) / b0);
    } else {
      result.push(0);
      Adb += A[0];
      A = A.substr(1);
      continue;
    }
    for (let i = max - 1; i >= 0; i--) {
      if (i === 0) {
        result.push(0);
        Adb += A[0];
        A = A.substr(1);
        break;
      } else {
        temp = temp || multiply(B, i + &#39;&#39;);
        r = compare(temp, Adb);
        if (r === 0 || r === -1) {
          result.push(i);
          if (r) {
            Adb = reduce(Adb, temp);
            Adb += A[0];
          } else {
            Adb = A[0];
          }
          A = A.substr(1);
          break;
        } else {
          temp = reduce(temp, B);
        }
      }
    }
    temp = 0;
  }
  for (let i = 0; i < fill; i++) {
    result.unshift(&#39;0&#39;);
  }
  result.splice(result.length - point, 0, &#39;.&#39;);
  if (!result[0] && result[1] !== &#39;.&#39;) {
    result.shift();
  }
  point = false;
  let position = result.indexOf(&#39;.&#39;);
  for (let i = position + 1; i < result.length; i++) {
    if (result[i]) {
      point = true;
      break;
    }
  }
  if (!point) {
    result.splice(position);
  }
  result = result.join(&#39;&#39;);
  return result;
}
function multiply(A, B) {
  let result = [];
  (A += &#39;&#39;), (B += &#39;&#39;);
  const l = -4; // 以支持百万位精确运算,但速度减半
  let r1 = [],
    r2 = [];
  while (A !== &#39;&#39;) {
    r1.unshift(parseInt(A.substr(l)));
    A = A.slice(0, l);
  }
  while (B !== &#39;&#39;) {
    r2.unshift(parseInt(B.substr(l)));
    B = B.slice(0, l);
  }
  let index, value;
  for (let i = 0; i < r1.length; i++) {
    for (let j = 0; j < r2.length; j++) {
      value = 0;
      if (r1[i] && r2[j]) {
        value = r1[i] * r2[j];
      }
      index = i + j;
      if (result[index]) {
        result[index] += value;
      } else {
        result[index] = value;
      }
    }
  }
  for (let i = result.length - 1; i > 0; i--) {
    result[i] += &#39;&#39;;
    if (result[i].length > -l) {
      result[i - 1] += parseInt(result[i].slice(0, l));
      result[i] = result[i].substr(l);
    }
    while (result[i].length < -l) {
      result[i] = &#39;0&#39; + result[i];
    }
  }
  if (result[0]) {
    result = result.join(&#39;&#39;);
  } else {
    result = &#39;0&#39;;
  }
  return result;
}
function add(A, B) {
  let result = [];
  (A += &#39;&#39;), (B += &#39;&#39;);
  const l = -15;
  while (A !== &#39;&#39; && B !== &#39;&#39;) {
    result.unshift(parseInt(A.substr(l)) + parseInt(B.substr(l)));
    A = A.slice(0, l);
    B = B.slice(0, l);
  }
  A += B;
  for (let i = result.length - 1; i > 0; i--) {
    result[i] += &#39;&#39;;
    if (result[i].length > -l) {
      result[i - 1] += 1;
      result[i] = result[i].substr(1);
    } else {
      while (result[i].length < -l) {
        result[i] = &#39;0&#39; + result[i];
      }
    }
  }
  while (A && (result[0] + &#39;&#39;).length > -l) {
    result[0] = (result[0] + &#39;&#39;).substr(1);
    result.unshift(parseInt(A.substr(l)) + 1);
    A = A.slice(0, l);
  }
  if (A) {
    while ((result[0] + &#39;&#39;).length < -l) {
      result[0] = &#39;0&#39; + result[0];
    }
    result.unshift(A);
  }
  if (result[0]) {
    result = result.join(&#39;&#39;);
  } else {
    result = &#39;0&#39;;
  }
  return result;
}
function reduce(A, B) {
  let result = [];
  (A += &#39;&#39;), (B += &#39;&#39;);
  while (A[0] === &#39;0&#39;) {
    A = A.substr(1);
  }
  while (B[0] === &#39;0&#39;) {
    B = B.substr(1);
  }
  const l = -15;
  let N = &#39;1&#39;;
  for (let i = 0; i < -l; i++) {
    N += &#39;0&#39;;
  }
  N = parseInt(N);
  while (A !== &#39;&#39; && B !== &#39;&#39;) {
    result.unshift(parseInt(A.substr(l)) - parseInt(B.substr(l)));
    A = A.slice(0, l);
    B = B.slice(0, l);
  }
  if (A !== &#39;&#39; || B !== &#39;&#39;) {
    let s = B === &#39;&#39; ? 1 : -1;
    A += B;
    while (A !== &#39;&#39;) {
      result.unshift(s * parseInt(A.substr(l)));
      A = A.slice(0, l);
    }
  }
  while (result.length !== 0 && result[0] === 0) {
    result.shift();
  }
  let s = &#39;&#39;;
  if (result.length === 0) {
    result = 0;
  } else if (result[0] < 0) {
    s = &#39;-&#39;;
    for (let i = result.length - 1; i > 0; i--) {
      if (result[i] > 0) {
        result[i] -= N;
        result[i - 1]++;
      }
      result[i] *= -1;
      result[i] += &#39;&#39;;
      while (result[i].length < -l) {
        result[i] = &#39;0&#39; + result[i];
      }
    }
    result[0] *= -1;
  } else {
    for (let i = result.length - 1; i > 0; i--) {
      if (result[i] < 0) {
        result[i] += N;
        result[i - 1]--;
      }
      result[i] += &#39;&#39;;
      while (result[i].length < -l) {
        result[i] = &#39;0&#39; + result[i];
      }
    }
  }
  if (result) {
    while ((result[0] = parseInt(result[0])) === 0) {
      result.shift();
    }
    result = s + result.join(&#39;&#39;);
  }
  return result;
}

사용법: 음수는 허용되지 않습니다. 매개변수에는 문자열 매개변수를 사용하는 것이 가장 좋습니다.

divide(A,B)    // 除法
multiply(A,B)    //乘法
add(A,B)    //加法
reduce(A,B)    //减法

toFixed fix

in Firefox/ Chrome, toFixed는 마지막 숫자가 5인 경우 반올림하지 않습니다.

1.35.toFixed(1) // 1.4 正确
1.335.toFixed(2) // 1.33  错误
1.3335.toFixed(3) // 1.333 错误
1.33335.toFixed(4) // 1.3334 正确
1.333335.toFixed(5)  // 1.33333 错误
1.3333335.toFixed(6) // 1.333333 错误

Firefox 및 Chrome 구현에는 문제가 없습니다. 근본 원인은 컴퓨터에서 부동 소수점 숫자의 정밀도가 손실되는 것입니다.

복구 방법:

function toFixed(num, s) {
    var times = Math.pow(10, s)
    var des = num * times + 0.5
    des = parseInt(des, 10) / times
    return des + &#39;&#39;
}

이 기사는 여기서 끝났습니다. 더 많은 흥미로운 콘텐츠를 보려면 PHP 중국어 웹사이트의 JavaScript 비디오 튜토리얼 칼럼을 주목하세요!

위 내용은 js의 계산 정확도에 대해 이야기합시다.의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 www.iyouhun.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제