# 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.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) {
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 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 {
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));
A = A.substr(lb);
let temp, r;
for (let j = 0; j < la - lb + 1; j++) {
}
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);
A = A.substr(1);
continue;
}
for (let i = max - 1; i >= 0; i--) {
if (i === 0) {
result.push(0);
A = A.substr(1);
break;
} else {
temp = temp || multiply(B, i + '');
if (r === 0 || r === -1) {
result.push(i);
if (r) {
} else {
}
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;
}

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
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 + ''
}``````