The result was an online BUG where the calculated values did not match the calculated values of the back-end Java BigDecimal, tracking data, and inaccurate data in tofixed and round

10.135. ToFixed (2) / / 10.13Copy the code

That’s not friendly. Think of a solution. First of all, you need to know why

  • If the decimal part of the argument is exactly 0.5, it is rounded to the next integer in the direction of positive infinity (+∞). Note that unlike the round() function in many other languages, math.round () is not always rounded away from zero (especially if the fractional part of a negative number is exactly 0.5) –MDN

This situation is why, experienced know, is certainly into the pot ah

Let’s take a look at the storage structure

Ecma-262 follows IEEE 754 specification and uses double precision, occupying 64 bits

As can be seen from the storage structure, the length of the index part is 11 binary, that is, the maximum value that the index part can represent is 2047 (211-1), and the middle value is offset to represent the negative index, that is, the range of the index is [-1023,1024]. Therefore, this storage structure can represent a range of values from 21024 to 2-1023, and numbers beyond this range cannot be represented. 21024 is converted to scientific notation as follows:

21024  = 1.7976931348623157 × 10308
Copy the code

Therefore, the maximum value that can be represented in JavaScript is 1.7976931348623157e+308, and the minimum value is 5e-324.

These two boundary values can be obtained by accessing the MAX_VALUE and MIN_VALUE attributes of the Number object, respectively:

Number.MAX_VALUE; / / 1.7976931348623157 e+308 Number. MIN_VALUE; //5e-324Copy the code

If the number exceeds the maximum or minimum value, JavaScript will return an incorrect value, which is called overflow or underflow.

Number.MAX_VALUE+1 == Number.MAX_VALUE; //true
Number.MAX_VALUE+1e292; //Infinity
Number.MIN_VALUE + 1; //1
Number.MIN_VALUE - 3e-324; //0
Number.MIN_VALUE - 2e-324; //5e-324
Copy the code

Then the numerical accuracy is known

In 64-bit binary, the sign bit determines whether a number is positive or negative, the exponential part determines the size of the number, and the decimal part determines the accuracy of the number.

IEEE754 states that the first significant digit is always 1 by default. Therefore, there is a hidden bit, fixed to 1, before the mantissa indicating precision, but it is not stored in 64-bit floating point numbers. That is, the significant digit is always 1.xx… In the form of xx, where xx.. Parts of xx are stored in 64-bit floating point numbers up to 52 bits. So, JavaScript provides up to 53 bits of significant digits, which internally look like this:

(-1)^ symbol bit * 1.xx… Xx * 2^ exponent bit

This means that the integer range that JavaScript can represent and perform precise arithmetic operations is: [-253-1, 253-1], from the minimum value of -9007199254740991 to the maximum value of 9007199254740991.

Math.pow(2, 53)-1 ; // 9007199254740991 -Math.pow(2, 53)-1 ; / / - 9007199254740991Copy the code

The maximum and minimum values can be obtained by number.max_safe_INTEGER and number.min_safe_INTEGER, respectively.

console.log(Number.MAX_SAFE_INTEGER) ; // 9007199254740991 console.log(Number.MIN_SAFE_INTEGER) ; / / - 9007199254740991Copy the code

For integers beyond this range, JavaScript can still perform operations, but the accuracy of the results is not guaranteed.

Math.pow(2, 53) ; // 9007199254740992 Math.pow(2, 53) + 1; / / 9007199254740992 9007199254740993; / / 9007199254740992 90071992547409921; 90071992547409920 / / 0.923456789012345678; / / 0.9234567890123456 `Copy the code

And then we’ll see why we lose precision

The numbers in a computer are stored in binary. To compute 0.1 + 0.2, the computer converts 0.1 and 0.2 to binary, adds them together, and finally converts the sum to decimal.

But there are some floating point numbers that will loop indefinitely when converted to binary. For example, converting 0.1 from decimal to binary yields the following result:

0.0001 1001 1001 1001 1001 1001 1001... (1001 infinite loop)  Copy the code

The mantissa part of the storage structure can only represent 53 bits at most. In order to represent 0.1, it has to be rounded like decimal, but binary only has 0 and 1, so it is rounded by 0 and 1. Thus, the binary representation of 0.1 on a computer is as follows:

0.0001100110011001100110011001100110011001100110011001101
Copy the code

It is expressed by standard counting method as follows:

(1) - 0 x 2-4 x 2 (1.1001100110011001100110011001100110011001100110011010)Copy the code

Similarly, the binary of 0.2 can also be expressed as:

(1) - 2-0 x 3 x (1.1001100110011001100110011001100110011001100110011010) 2Copy the code

When calculating the addition of floating point numbers, it is necessary to do the counterpoint first, converting the smaller exponent to the larger exponent and shifting the decimal part to the right accordingly:

0.1 - (1 -) 2-0 x 3 x (0.11001100110011001100110011001100110011001100110011010) 2 - (1 -) 0.2 0 x 2-3 x 2 (1.1001100110011001100110011001100110011001100110011010)Copy the code

In the end, 0.1 + 0.2 is computed in the computer as follows:

After the above calculation process, the result of 0.1 + 0.2 can also be expressed as:

(1) - 2-0 x 2 x 2 (1.0011001100110011001100110011001100110011001100110100)Copy the code

This binary result is then converted to a decimal representation using JS:

(-1)**0 * 2**-2 * (0b10011001100110011001100110011001100110011001100110100 * 2**-52); //0.30000000000000004
console.log(0.1 + 0.2) ; // 0.30000000000000004
Copy the code

This is a typical case of precision loss, as shown in the calculation above, 0.1 and 0.2 have a precision loss when converted to binary, and another precision loss for the calculated binary. Therefore, the results obtained are not accurate. 2.5 Special Values JavaScript provides several special values for determining the boundaries of numbers and other features. As follows:

MAX_VALUE: specifies the maximum value in JavaScript. Number.MIN_VALUE: specifies the minimum value in JavaScript. MIN_SAFE_INTEGER: indicates the minimum safe integer. The value is -(253-1) Number.POSITIVE_INFINITY: indicates the minimum safe integer. NEGATIVE_INFINITY: corresponding to -infinity, representing negative Infinity Number.EPSILON: is a very small value used to check whether the calculation result is within the error range. Denotes a non-number, NaN is not equal to any value, including NaN itself Infinity: denotes Infinity, divided into positive Infinity Infinity and negative Infinity -infinityCopy the code

How to perform numerical conversion

There are three functions to convert non-numeric values to numeric values: Number(), parseInt(), and parseFloat(). Number() can be used for any data type, and the other two functions are dedicated to converting strings to numbers.

For strings, Number() can only convert the string as a whole, while parseInt() and parseFloat() can convert the string partially, converting only the characters up to the first invalid character.

Number() is treated differently for different types of data, and its conversion rules are as follows:

[1] If Boolean, true and false will be converted to 1 and 0, respectively.

[2] If the value is numeric, it is simply passed in and returned.

[3] If null, return 0.

[4] If undefined, return NaN.

[5] For strings, follow the following rules: If the string contains only numbers (including those preceded by a plus or minus sign), convert them to decimal values; If the string contains a valid floating-point format, it is converted to the corresponding floating-point value; If the string contains a valid hexadecimal format, it is converted to a decimal integer value of the same size; If the string is empty (containing no characters), it is converted to 0; If the string contains characters other than those in the above format, it is converted to NaN. [6] If it is an object, the valueOf() method of the object is called and the returned value is converted according to the previous rules. If the result of the transformation is NaN, the object’s toString() method is called and the returned string value is converted again according to the previous rules.

Note: the unary addition operator [+] has the same effect as Number().

Faster and less accurate bit operations

Bitwise operators are used at the most basic level to manipulate values by the bits that represent them in memory. All values in ECMAScript are stored in IEEE754 64-bit format, but bitwise operators do not directly manipulate 64-bit values. Instead, the 64-bit value is converted to a 32-bit integer, the operation is performed, and the result is converted back to 64-bit. Common bit operations are the following:

Bitwise NOT (NOT) : ~ bitwise AND (AND) : & bitwise OR (OR) : | bitwise exclusive OR (XOR) : ^ left: < < signed right shift: > > unsigned right shift: > > >Copy the code

How does rounding work

First let’s take a look at how the data is stored. The ECMA-262 only requires a maximum of 21 display digits.

/ / 0.135 toPrecision (21)"10.1349999999999997868"Everyone may be different.Copy the code

The e problem is basically clear here, how to solve it, the code.

/** ** round * @param number Number to be rounded * @param Precision reserved decimal places * @returns {*} */function round(number,precision) {
      const enlargeDigits = function enlargeDigits(times) {
        return function (number) {
          return +(String(number) + "e" + String(times));
        };
      };
      const toFixed = function toFixed(precision) {
        return function (number) {
          return number.toFixed(precision);
        };
      };
      const compose = function compose() {
        for (var _len = arguments.length, functions = Array(_len), _key = 0; _key < _len; _key++) {
          functions[_key] = arguments[_key];
        }

        var nonFunctionTypeLength = functions.filter(function (item) {
          returntypeof item ! = ='function';
        }).length;
        if (nonFunctionTypeLength > 0) {
          throw new Error("compose's params must be functions");
        }
        if (functions.length === 0) {
          return function (arg) {
            return arg;
          };
        }
        if (functions.length === 1) {
          return functions[0];
        }
        return functions.reduce(function (a, b) {
          return function () {
            returna(b.apply(undefined, arguments)); }; }); }; var precision = arguments.length > 1 && arguments[1] ! == undefined ? arguments[1] : 2;if (Number.isNaN(+number)) {
        throw new Error("number's type must be Number");
      }
      if (Number.isNaN(+precision)) {
        throw new Error("precision's type must be Number");
      }
      return compose(toFixed(precision), enlargeDigits(-precision), Math.round, enlargeDigits(precision))(number)
    }
Copy the code