Precision of js operation

We all know that using js to do arithmetic will definitely encounter the problem of accurate calculation (or rounding error), but how to avoid these pits, here is my plan from the Internet, welcome to discuss.

Reasons for Accuracy Loss

The binary implementation of a computer and the number limitation of digits can not be expressed in finite numbers. Just like some irrational numbers can not be expressed in finite terms, such as the circumference of 3.1415926, 1.3333, etc. JavaScript uses 64 bits to store numeric types, so it goes beyond that. The missing part is the missing part of accuracy.

Here is the binary representation of decimal decimal

0.1 > > 0.0001 1001 1001 1001... (1001 infinite cycle)
0.2 > > > 0.0011 0011 0011... (0011 infinite cycle)

Solution

For more complex computational class libraries, consider well-known class libraries such as math.js

Floating point (decimal)

For decimals, there are still many probabilities of front-end problems, especially in some e-commerce websites involving data such as the amount of money. Solution: Put decimal into bitwise integer (multiplier), and then reduce it back to original multiplier (divider) and convert it into integer. The result of operation can not exceed Math.pow(2,53).

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

Floating point precise operation

/**
 * floatObj Four methods of addition, subtraction, multiplication and division are included to ensure that the accuracy of floating-point arithmetic is not lost.
 *
 * ** method **
 *  add / subtract / multiply /divide
 *
 * ** explame **
 *  0.1 + 0.2 == 0.30000000000000004 (It's 0.00000000004 more.
 *  0.2 + 0.4 == 0.6000000000000001  (0.0000000001 more)
 *  19.9 * 100 == 1989.9999999999998 (Less than 0.0000000002)
 *
 * floatObj.add(0.1, 0.2) >> 0.3
 * floatObj.multiply(19.9, 100) >> 1990
 *
 */
var floatObj = function() {
    
    /*
     * Determine whether obj is an integer
     */
    function isInteger(obj) {
        return Math.floor(obj) === obj
    }
    
    /*
     * Converts a floating-point number to an integer, returning integers and multiples. If 3.14 > > 314, the multiple is 100
     * @param floatNum {number} decimal
     * @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
    }
    
    /*
     * Core method to realize addition, subtraction, multiplication and division operation to ensure that accuracy is not lost
     * Idea: Enlarge decimal to integer (multiply), perform arithmetic operation, and then reduce to decimal (divide)
     *
     * @param a {number} Operator 1
     * @param b {number} Operator 2
     * @param digits {number} Precision, reserved decimal points, such as 2, reserved for two decimal places
     * @param op {string} Operational type, 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) { // Two decimal digits are the same
                    result = n1 + n2
                } else if (t1 > t2) { // O 1 decimal digit greater than O 2
                    result = n1 + n2 * (t1 / t2)
                } else { // O 1 decimal less than O 2
                    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
        }
    }
    
    // Four interfaces for addition, subtraction, multiplication and division
    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
    }
}();

Usage method:

floatTool.add(a,b);//Summation
floatTool.subtract(a,b);//subtract
floatTool.multiply(a,b);//Multiplication
floatTool.divide(a,b);//be divided by

Super large integer

Although the operation results do not exceed Math.pow(2,53) integer (9007199254740992) can also use the above method, but if there is more than that, the actual scenario may be some batch number, number and other requirements, here I also found a solution, directly on the code.

Online operations: 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 += '0';
  }
  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] === '0') {
      Adb = Adb.substr(1);
    }
    if (Adb.length === lb) {
      max = Math.ceil((parseInt(Adb.charAt(0)) + 1) / b0); // It's impossible to get this maximum, 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 + '');
        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('0');
  }
  result.splice(result.length - point, 0, '.');

  if (!result[0] && result[1] !== '.') {
    result.shift();
  }

  point = false;
  let position = result.indexOf('.');

  for (let i = position + 1; i < result.length; i++) {
    if (result[i]) {
      point = true;
      break;
    }
  }
  if (!point) {
    result.splice(position);
  }

  result = result.join('');
  return result;
}

function multiply(A, B) {
  let result = [];
  (A += ''), (B += '');
  const l = -4; // Supports millions of bits of precise computation, but reduces the speed by half

  let r1 = [],
    r2 = [];
  while (A !== '') {
    r1.unshift(parseInt(A.substr(l)));
    A = A.slice(0, l);
  }
  while (B !== '') {
    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] += '';
    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] = '0' + result[i];
    }
  }
  if (result[0]) {
    result = result.join('');
  } else {
    result = '0';
  }
  return result;
}

function add(A, B) {
  let result = [];
  (A += ''), (B += '');
  const l = -15;
  while (A !== '' && B !== '') {
    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] += '';
    if (result[i].length > -l) {
      result[i - 1] += 1;
      result[i] = result[i].substr(1);
    } else {
      while (result[i].length < -l) {
        result[i] = '0' + result[i];
      }
    }
  }

  while (A && (result[0] + '').length > -l) {
    result[0] = (result[0] + '').substr(1);
    result.unshift(parseInt(A.substr(l)) + 1);
    A = A.slice(0, l);
  }

  if (A) {
    while ((result[0] + '').length < -l) {
      result[0] = '0' + result[0];
    }
    result.unshift(A);
  }

  if (result[0]) {
    result = result.join('');
  } else {
    result = '0';
  }

  return result;
}

function reduce(A, B) {
  let result = [];
  (A += ''), (B += '');
  while (A[0] === '0') {
    A = A.substr(1);
  }
  while (B[0] === '0') {
    B = B.substr(1);
  }
  const l = -15;
  let N = '1';
  for (let i = 0; i < -l; i++) {
    N += '0';
  }
  N = parseInt(N);
  while (A !== '' && B !== '') {
    result.unshift(parseInt(A.substr(l)) - parseInt(B.substr(l)));
    A = A.slice(0, l);
    B = B.slice(0, l);
  }
  if (A !== '' || B !== '') {
    let s = B === '' ? 1 : -1;
    A += B;
    while (A !== '') {
      result.unshift(s * parseInt(A.substr(l)));
      A = A.slice(0, l);
    }
  }
  while (result.length !== 0 && result[0] === 0) {
    result.shift();
  }
  let s = '';
  if (result.length === 0) {
    result = 0;
  } else if (result[0] < 0) {
    s = '-';
    for (let i = result.length - 1; i > 0; i--) {
      if (result[i] > 0) {
        result[i] -= N;
        result[i - 1]++;
      }
      result[i] *= -1;
      result[i] += '';
      while (result[i].length < -l) {
        result[i] = '0' + 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] += '';
      while (result[i].length < -l) {
        result[i] = '0' + result[i];
      }
    }
  }

  if (result) {
    while ((result[0] = parseInt(result[0])) === 0) {
      result.shift();
    }
    result = s + result.join('');
  }
  return result;
}

Usage: Negative numbers are not allowed. Strings are preferred for parameters.

divide(A,B)    // division
multiply(A,B)    //multiplication
add(A,B)    //addition
reduce(A,B)    //subtraction

Restoration of toFixed

In Firefox / Chrome, toFixed does not round off the last 5.

1.35.toFixed(1) // 1.4 Correct
1.335.toFixed(2) // 1.33 Error
1.3335.toFixed(3) // 1.333 Error
1.33335.toFixed(4) // 1.3334 Correct
1.333335.toFixed(5)  // 1.33333 error
1.3333335.toFixed(6) // 1.333333 error

The implementation of Firefox and Chrome is not a problem, the root cause is the loss of floating-point accuracy in the computer.

Repair methods:

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

Reference link:
https://blog.csdn.net/qq_41827547/article/details/89487456

Keywords: Javascript Firefox less

Added by allaboutthekick on Mon, 26 Aug 2019 08:46:52 +0300