The opening

Spring is coming, and everyone is full of energy and energy, jumping and jumping. I also read a set of questions online, did not expect to see the beginning of the eye:

var END = Math.pow(2, 53);
var START = END - 100;
var count = 0;
for (var i = START; i <= END; i++) {
    count++;
}
console.log(count);
Copy the code

It is not difficult to solve the problem, this is about the JS precision coefficient, just need to remember that 2 ^ 53 is the largest integer that can be correctly calculated without losing precision, then. But I have been working for two years, and I feel that I should step into the ranks of slightly advanced programmers. Even if I am not advanced at all, I should use the principles of advanced programmers to demand myself. Thus, I have this article to explore the summary of floating point number language level implementation principle. By the end of this article you will not only be able to solve the problem but also understand why. The following output looks like this.

> 9007199254740992 + 1 9007199254740992 > 9007199254740992 + 2 9007199254740994 > 0.1 + 1-1 0.10000000000000009Copy the code

On the whole

As we know, all numbers in JS are represented by floating point values, which are represented in 64-bit floating point format defined by IEEE 754 standard. The fraction takes up 52 bits from 0 to 51, the exponent takes up 11 bits from 52 to 62, and the sign takes up 1 bit.

10^3 + b
10^1+d
10^(-1)+f
10 ^ (3). A binary floating-point number is also represented as: abcd. Efg (where a~g values are 0 or 1), and its value is: a
2^2 + c
2^0+e
2^(-1)+g
(-1)^s * f* 10^e
(-1)^s * f* 2^e
offset binary

Analysis of the

How do we represent decimals?

Here we agree that a number preceded by % represents a binary number. Here is a set of examples representing non-negative floating-point numbers. JS uses rational numbers to represent significant numbers in the following way: 1.f. F is a 52-bit fraction. Ignoring the positive and negative signs, the significant number multiplied by 2^p(p = e – 1023) is our final binary number, which is how JS represents decimals.

How do I represent integers?

JS has 53 significant digits, one of which is fixed in front of the decimal point and has a value of 1, and the rest have 52 digits behind the decimal point. When p(p = e-1023) = 52, we will use 53 bits to represent the number. Since the highest digit is always 1, this means that we can’t always manipulate the bits as we want. But IEEE solves this problem subtly in two steps. Step 1: As shown in the figure, we make an inference. If the highest bit of the 53-bit number is 0 and the second bit is 1, then we set P = 51. If the lowest bit (that is, after the decimal point) is 0, we consider the number to be an integer. And so on until P = 0, f = 0, at which point we get a coded integer of 1. (Through Step 1, we can use 53 bits to accurately represent the number.) Step 2: Although part of the number can be accurately represented through Step 1, it cannot represent 0. In this step, we define the representation of 0 again: This value is 0 when p= -1023 (e = 0) and f = 0 (more on that later).

Special indices, special conventions

According to IEEE 754, we have two special definitions on the exponential bit, namely E = 0 and E = 2047. Special provisions:

1. When e = 0, if f = 0, the value is 0. Due to the existence of sign bit, we have -0 and +0 in JS.

2. When e = 0 and f > 0, the value is used to represent a number very close to 0 by the formula (-1)^s * %0. F × 2^−1022. Normalized normalized values %1.0 × 2^−1022 normalized normalized values %0.1… * 2^−1022 Thus, normalized and nonnormalized numbers can be seamlessly interacted. 3. When e = 2047, f = 0, this value is expressed as infinity (∞/infinity) in JS. 4. When e = 2047 and f > 0, this value is represented as NaN in JS. To sum up:

Decimal fraction

Not all decimal numbers can be accurately represented in JS. Take a look at the following example:

    > 0.1 + 0.2
    0.30000000000000004
Copy the code

So how does that work? Let’s start by looking at how 0.1 and 0.2 are represented in binary floating point numbers

0.5 is stored as a binary floating point number %0.1 but 0.1 = 1/10 so it is expressed as 1/16 + (1/10-1/16) = 1/16 + 0.0375 0.0375 = 1/32 + (0.0375-1/32) = 1/32 + 00625... Etc so 0.1 is represented as %0.00011 in binary floating point numbers... 0.1 - > % 0.0001100110011001... (unlimited) 0.2 -> %0.0011001100110011... (infinite)Copy the code

IEEE 754 supports a maximum of 53 binary bits for the decimal part of a 64-bit double precision floating-point number, so when the two are added together, the binary is:

% 0.0100110011001100110011001100110011001100110011001100Copy the code

Let’s look at another example:

> 0.1 + 1-1 0.100000000000000000009Copy the code

Why is that? The first thing we know

0.1 - > % 0.0001100110011001... (infinite)Copy the code

According to its accuracy, the result saved in memory should be

% 0.0001 (100110011001... 010) // Since the end is 1, 0 is rounded to 1, so... There are 52 digits in ()Copy the code

So if I add 1, I can write it as 1

The % 1. (0001100110011... 010) // Since the end is 1, 0 is rounded to 1, so... There are 52 digits in ()Copy the code

Minus 1, we can write it as 1

Storage binary after subtracting 1: %0.0001(100110011001... 0.1 Original storage size: %0.0001(100110011001... 0011010).Copy the code

Note that the last 7 digits are slightly larger than the actual value. You can also calculate 0.2+1-1 as shown above and give it a try.

The largest integer

What is the greatest integer? Here we give a definition of the largest integer (x) : it means that every integer n can be represented in the range 0 ≤ n ≤ x, which cannot be guaranteed if it is greater than x. In JS, 2^53 is the answer, and all numbers less than that can be represented

    > Math.pow(2, 53)
    9007199254740992
    > Math.pow(2, 53) - 1
    9007199254740991
    > Math.pow(2, 53) - 2
    9007199254740990
Copy the code

But numbers larger than that can’t necessarily be represented:

    > Math.pow(2, 53) + 1
    9007199254740992
Copy the code

I will break down the reasons for the above situation into a few small questions, once you understand these small questions, then the above questions will definitely be understood. You just have to remember that the only thing that limits the maximum accuracy is the fraction, but there’s still a lot of room for the exponent to go up. Why 53? Because we have 53 bits that can be used to represent the size of a number (not including symbols), just 52 bits for the fractional part and always 1 for the integer part (binary scientific notation). Why is the largest integer not (2^53) – 1? In general, x bits are integers ranging from 0 to (2^x) -1. For example, if a byte has eight bits, the largest integer that a byte can represent is 255. In JS, the largest decimal part is indeed (2^53) -1, and thanks to the exponential bits, 2^53 is also possible. When the decimal place f =0, the exponent bit P = 53:

%1. F × 2p = %1.0 × 2^53 = 2^53Copy the code

How can we represent numbers greater than 2 to the 53? Take a look at the following example

    > Math.pow(2, 53)
    9007199254740992
    > Math.pow(2, 53) + 1  // not OK
    9007199254740992
    > Math.pow(2, 53) + 2  // OK
    9007199254740994

    > Math.pow(2, 53) * 2  // OK
    18014398509481984
Copy the code

2^53 × 2 is correct because it is within the normal range of exponents, and every time you multiply by 2 you add 1 to the index place without affecting the decimal place at all. So why can we write 2 plus 2 to the 53 but not 1 plus 2 to the 53? Let’s look at the following list:

    > Math.pow(2, 54)
    18014398509481984
    > Math.pow(2, 54) + 1
    18014398509481984
    > Math.pow(2, 54) + 2
    18014398509481984
    > Math.pow(2, 54) + 3
    18014398509481988
    > Math.pow(2, 54) + 4
    18014398509481988
Copy the code

And so on until p= 1023 and so on (p=1024 has special meanings, see special exponents, special convention modules above).

How to avoid floating point accuracy errors

We should avoid direct comparisons between decimals. Because in general there’s just no good way to deal with these errors. But we can determine if this is acceptable by setting an upper bound on the error. This upper limit of error is what we call machine epsilon. The standard double floating-point precision value is 2 ^-52.

   var epsEqu = function () { // IIFE, keeps EPSILON private
        var EPSILON = Math.pow(2, -53);
        return function epsEqu(x, y) {
            returnMath.abs(x - y) < EPSILON; }; } ();Copy the code

The above function ensures that our results are acceptable within the accuracy range.

  > 0.1 + 0.2 === 0.3
  false> epsEqu (0.1 + 0.2, 0.3)true
Copy the code

There are also several good libraries that can handle precision issues.

Math JS

Sinful JS

BigDecimal

JS native provides two methods of processing precision of the Number. The prototype. The toPrecision () and Number. The prototype. ToFixed () only the two methods are used to display values, type String. Such as:

function foo(x, y) {
    returnX.toprecision () + y.toprecision ()} > foo(0.1, 0.2)"0.10.2"
Copy the code

So use must be careful, it is best not to use JS to deal with floating point numbers, if you must use JS to deal with number problems, it is best not to write their own, good class library is often a better choice.

conclusion

We have come a long way, and finally fully understand how JS represents numbers internally. The profound conclusion we have come to is that if you calculate numbers with decimals in JS, you will not be sure what you get.

P.S.: the world belongs to those who understand it, and we’re happy to have a deeper understanding of the programming world. If the article is not enough or the understanding is not in place, please feel free to comment, if you do not understand the place can also leave a message, we discuss together (^-^)

The appendix

The original problem set that I looked at is right here

What Every JavaScript Developer Should Know About Floating Points

What Every Computer Scientist Should Know About Floating-Point Arithmetic

How numbers are encoded in JavaScript

Storage of floating point numbers in a computer

The representation of floating-point numbers in a computer