js summary of toFixed issues, retaining decimals and rounding

js summary on toFixed issues

https://blog.csdn.net/Leon_940002463/article/details/124094588


js Summary of the toFixed problem
Recently at work, I encountered a strange problem when calculating the medical insurance settlement of a prescription cost. There would be a penny difference. Design The calculation of the amount is often what hospitals and banks are most concerned about. After a morning of troubleshooting, I finally found out that it was a problem with the completely trusted js native toFixed method.

test(){ console.log(1.05.toFixed(1)); // 1.1 True console.log(1.005.toFixed(2)); // 1.00 False console.log(1.0005.toFixed(3)); // 1.000 wrong console.log(1.00005.toFixed(4)); // 1.0001 right console.log(1.000005.toFixed(5)); // 1.00001 pairs console.log(1.0000005.toFixed(6)); // 1.000001 pairs console.log(1.00000005.toFixed(7)); // 1.0000000 is wrong console.log(1.000000005.toFixed(8)); // 1.00000000 is wrong } My whole head was buzzing after seeing the above test results










js encapsulation solves problems

/**
 * Keep the number of decimal points, automatically fill in zeros, and round off
 * @param num: numerical value
 * @param digit: Number of digits after the decimal point
 * @returns string
 * Object.is() was introduced in es6 and is used to judge two Or a method to determine whether multiple data are congruent. A very important feature is that the result of Object.is(NaN,NaN) is true. This is to determine whether the value is a number. If it is not a number, the parseFloat method cannot be converted and it will be displayed as true
 * Number.EPSILON can be used to set the "acceptable error range". For example, the error range is set to the -50th power of 2 (ie Number.EPSILON * Math.pow(2, 2)), that is, if the difference between two floating point numbers is less than this value, we consider the two floating point numbers to be equal. Here is to set the minimum error value 
 * Math.pow The original method is to find the power of 10
 */
function myFixed(num, digit) {   if(Object.is(parseFloat(num), NaN)) {     return console.log(`The value passed in: ${num} is not a number`);   }   num = parseFloat(num);   return (Math.round((num + Number.EPSILON) * Math.pow(10, digit)) / Math.pow(10, digit)).toFixed (digit); }






test(){
console.log(myFixed(1.05,1)); // 1.1 对
console.log(myFixed(1.005,2)); // 1.01 对
console.log(myFixed(1.0005,3)); // 1.001 对
console.log(myFixed(1.00005,4)); // 1.0001 对
console.log(myFixed(1.000005,5)); // 1.00001 对
console.log(myFixed(1.0000005,6)); // 1.000001 对
console.log(myFixed(1.00000005,7)); // 1.0000001 对
console.log(myFixed(1.000000005,8)); // 1.00000001 对
}

eg: 
var f = 3.15
undefined
f.toFixed(1)< a i=4> '3.1' myFixed(f,1) '3.2' Ok, the problem is solved, now let’s pursue the specific problem of tofixed. I program for Baidu, and after searching, I found various results. It seems that it is indeed a classic problem. I have also learned before that it conforms to the banker's algorithm (rounding up to five to make an even number). What does it mean: rounding up to five is considered, and if it is non-zero after five, then one is advanced. , if the last five is zero, look at the odd or even number, if the first five is even, it should be discarded, if the first five is odd, it should be rounded up by one (when the value of the discarding digit is ≤ 4, it is discarded, when it is ≥ 6, it is added, but when it = 5 , it is determined based on the number after 5; when there are non-zero digits after 5, round 5 to 1; when there are no valid digits after 5, two situations need to be divided: if the number before 5 is an even number, 5 will not be rounded; 5 The first is an odd number, rounded to 1)








Based on the above rules, I conducted some tests and found another problem:

const a = 3.15 
const b = 3.25
console.log(a.toFixed(1));  // 3.1
console.log(b.toFixed(1));  // 3.3 

What's going on? Variable a is an odd number before 5 and is not rounded up by 1; variable b is an even number before it is not discarded but is rounded up by 1; 

Guess 1: It is caused by js precision problem
We We all know that all numbers in JavaScript, including integers and decimals, have only one type - Number. Its implementation follows the IEEE 754 standard and uses a 64-bit fixed length to represent it, which is a standard double double-precision floating point number (related to float 32-bit single precision). Why? Because it saves storage space.

That is 17652.19 + 7673.78 = 25325.969999999998. In fact, the simplest example is 0.1+0.2 = 0.30000000000000004

The binary representation of 0.1 is an infinitely looping decimal. This version of JS adopts the floating point number standard and needs to intercept this infinitely looping binary, which results in loss of accuracy and causes 0.1 to no longer be 0.1. After interception, 0.1 becomes It became 0.100…001, and 0.2 became 0.200…002. So the sum of the two is greater than 0.3.

Converting 0.1 to binary plus 0.2 will result in 53 digits in binary, but the maximum number of digits in binary is 52 digits as an approximation.

The above 3.25 is actually similar to 3.2500000002. There is a mantissa, so according to the tofixed algorithm, it should be 3.3. But what about 3.15? Is it similar to 3.14999999999998? Here I introduce decimal.JS to solve the problem of js precision.

I think everyone may have encountered the problem of insufficient precision when using js to process the addition, subtraction, multiplication and division of numbers.

There are also those classic interview questions 02+0.1 == 0.3

As for the reason, it is that the underlying calculation of js is using , and there are limitations on accuracy.

Then, Decimal.js helps us solve the problem of precision inaccuracy in js


Introduction of Decimal and addition, subtraction, multiplication and division
Introduction
npm install --save decimal.js // Installation
import Decimal from "decimal.js" //Introduce into specific files

add
let a = 1
let b = 6 
// a and b can be of any type, Decimal will handle the compatibility internally
// Both of the following two can be used with new or not. With new
let res = new Decimal(a).add(new Decimal(b)) 
let res = Decimal(a).add(Decimal(b )) 

minus

let a = "4"
let b = "8"
// a and b can be of any type, Decimal will internally Handle it yourself for compatibility
// Both of the following can be used with new or without new
let res = new Decimal(a).sub(new Decimal (b)) 
let res = Decimal(a).sub(Decimal(b)) 

Multiply

let a = 1
let b = 6 
// a and b can be of any type, Decimal will handle the compatibility internally let res = new Decimal(a).mul(new Decimal(b))  divide let res = Decimal(a).mul(Decimal(b)) 
// Both of the following can be used with or without new



let a = 1
let b = 6 
// a and b can be of any type, Decimal will handle the compatibility internally let res = new Decimal(a).div(new Decimal(b))  The above result is a Decimal object, You can convert to Number or String Note: let res = Decimal(a).div(Decimal(b)) 
// Both of the following can be used with new or without new




let res = Decimal(a).div(Decimal(b)).toNumber() // Convert the result to Number
let res = Decimal(a).div(Decimal (b)).toString() // The result is converted into String
1
2
Related to how many decimal places are saved< /span>

//Check how many decimals there are (note that the 0 at the end of the decimal point is not counted)
y = new Decimal(987000.000)
y.sd () // ' 3 ' Effective digits
y.sd (true) // ' 6 ' A total of digits

//How many digits to retain (0 will be added to the decimal place)
x = 45.6
x.toPrecision(5) 39;45.600'

//How many significant digits are reserved (the decimal place will not be filled with 0, it is the calculated significant digit)
x = new Decimal(9876.5)

// Keep a few decimal places, the same as number in js
toFixed
x = 3.456
/ / Round down
x.toFixed(2, Decimal.ROUND_DOWN) // '3.45' (Rounding mode up 0 down 1 rounding 4, 7)< /span> Decimal.ROUND_UP 
// Round up

//Rounding
ROUND_HALF_UP



calculate

let a = 1
let b = 6 
//Add
let res = new Decimal(a ).add(new Decimal(b)) //The obtained value is a Decimal object that needs to be converted
let res1 = new Decimal(a).add(new Decimal(b)).toNumber( ) //The result is converted into number
let res2 = new Decimal(a).add(new Decimal(b)).toString() //The result is converted into string
//Same as above
//Subtract
let res = new Decimal(a).sub(new Decimal(b))< a i=9> //Multiply let res = new Decimal(a).mul(new Decimal(b)) //Divide< a i=12> let res = new Decimal(a).div(new Decimal(b)) Fixed to take two decimal places, and erase the others





/**
 * 取2位小数(可自定义)
 * 
 * @param num1 参数1
 * @param num2 参数2
 * @param status 1(+) 2(-) 3(*)
 * @param num 小数后 num-1位
 * @returns 
 */
export const multiply = (num1, num2, status,num=3) => {
  let sum = ''
  if (status === 1) {
    sum = new Decimal(parseFloat(num1)).add(new Decimal(parseFloat(num2))).toFixed(3).toString()
    return +sum.substring(0, sum.indexOf(".") + 3)
  } else if (status === 3) {
    sum = new Decimal(parseFloat(num1)).mul(new Decimal(parseFloat(num2))).toFixed(3).toString()

    return +sum.substring(0, sum.indexOf(".") + 3)
  }
}



but

created() {
    // const a = 2.998;
    // const b = 8.037;
    // var g = parseFloat(a + b);
    // var h = g.toFixed(2);
    // // 加法
    // let c = new Decimal(a).add(new Decimal(b)).toNumber();
    // let i = c.toFixed(2);
    // // 减法
    // let d = new Decimal(a).sub(new Decimal(b));
    // // 乘法
    // let e = new Decimal(a).mul(new Decimal(b));
    // // 除法
    // let f = new Decimal(a).div(new Decimal(b));
    // console.log("---->>>", c, d, e, f, g, h, i);
    // console.log(
    //   "---->>>",
    //   new Decimal(c),
    //   new Decimal(d),
    //   new Decimal(e),
    //   new Decimal(f)
    // );
    var z = 3.25;
    var x = z.toFixed(3);
    var xx = parseFloat(x).toFixed(1);
    var o = new Decimal(z);
    let y = new Decimal(z).toNumber();
    var q = y.toFixed(1);
    console.log("---->>>>sss", o, y, q, x, xx);
  }

 // ---->>>>sss Decimal {s: 1, e: 0, d: Array(2), constructor: ƒ} 3.25 3.3 3.250 3.3

Still not right, this may not only be a problem of accuracy, but also a hair-pulling problem. . .​ 




After some exploration, I finally got something. Now let’s take a look at the ECMAScript specification’s definition of this method. Sometimes returning to the specification is the most reliable way.

remove

The above picture is about the definition of the entire toFixed method, but it is a translated version. There will be discrepancies but the difference is not big. You can also click on the link above to view the original text. We mainly focus on the red box in the picture and calculate the rounding bits through the formula. numerical value.

Let's give an example below. have a test:

eg:
console.log(1.0000005.toFixed(6)); // 1.000001 is correct
console.log(1.00000005.toFixed(7) )); // 1.0000000 error 


First of all, according to the conditions of the red box, x<10^21, 1.0000005 and 1.00000005 are all less than 10^21, so we can directly use the formula n / 10^ - x to play.

Let’s first substitute x=1.0000005 into the formula to see the situation:

// Assume n1
var n1 = 1000000;
var x = 1.0000005;
var f = 6 ;
console.log((n1 / Math.pow(10, f) - x)); // -5.00000000069889e-7
 
// Assume n2
var n2 = 1000001;
var x = 1.0000005;
var f = 6;
console.log((n2 / Math.pow(10, f) - x)); // 4.999999998478444e-7

It can be seen from the result that when n1=1000001, the result obtained is The value closest to 0, so:

console.log(1.0000005.toFixed(6)); // 1.000001 is correct
1
Try again when x=1.00000005 and substitute it into the formula:

// Assume n1
var n1 = 10000000;
var x = 1.00000005;
var f = 7 ;
console.log((n1 / Math.pow(10,f) - x)); // -4.9999999918171056e-8
 
// Assume n2
var n2 = 10000001;
var x = 1.00000005;
var f = 7;
console.log((n2 / Math.pow(10,f) - x)); // 5.000000014021566e-8

It can be seen from the result that when n2=10000001, the result obtained is The value closest to 0, so:

    
console.log(1.00000005.toFixed(7)); // 1.0000000 error
1
2
In general, the above example is just to teach you how to calculate the results through the formula defined by the specification. If you understand the specification, then there is no problem in directly substituting it.
————————————————
Copyright statement: This article is an original article by CSDN blogger "Siang". Comply with the CC 4.0 BY-SA copyright agreement, please attach the original source link and this statement when reprinting.
Original link: https://blog.csdn.net/Leon_940002463/article/details/124094588

Guess you like

Origin blog.csdn.net/wwf1225/article/details/131822301