The surface of the work
In daily work and study, we often detect our own bottom line. Good or bad computer foundation can completely determine a person’s code level and bug occurrence rate. I’m sure you’ve all learned this before, but you’ve forgotten it for a long time, so I’m going to review it for you.
In line with the principle of easy to understand, I will explain this topic clearly today.
Let’s talk about the very general question of why 0.1 + 0.2! There are a few preconditions that need to be understood before we formally introduce this issue.
- What is the representation of computer binary and how binary is calculated?
- What is the source code, complement code, inverse code, shift code, are used to do what?
Almost all of these are enough to understand the general 0.1 + 0.2! == 0.3 problem.
The first pre knowledge, binary
We know that in daily life, there are many kinds of data display, including the regular use of 10 in our daily life, CSS to represent the hexadecimal color, computer computing binary.
Binary representation
Calculations in computers are performed in binary form, that is, all numbers are 0 or 1, let’s take the base 10 system for example, such as:
- The decimal 1 is represented as 1 in the computer
- The base two is represented as ten in the computer
- The decimal number of eight is represented in the computer as 1000
- 15 in the base 10 system is represented in the computer as 1111
Binary computing
For the binary calculation method, we are divided into two cases, one is the integer calculation, a decimal calculation.
Binary calculation of integer parts
Let’s first explain how base 10 is converted to binary. The way the decimal system is converted to binary is called “mod two”, which means you take a decimal number and divide it by 2 all the way to the rest of the digits. Let me give you two examples
30% 2 · · · · · · · · · 0 15% 2 · · · · · · · · · 1 7% 2 · · · · · · · · · 1 3% 2 · · · · · · · · · 1 1% 2 · · · · · · · · · 1 0
The binary conversion of an integer is read from the bottom up, so the binary representation of 30 is 11110.
100% 2 · · · · · · · · · 0 50% 2 · · · · · · · · · 0 25% 2 · · · · · · · · · 1 12% 2 · · · · · · · · · 0 6% 2 · · · · · · · · · 0 3% 2 · · · · · · · · · 1 1% 2 · · · · · · · · · 1 0
The binary conversion of an integer is read from the bottom up, so the binary representation of 100 is 1100100.
I also wrote a function specifically to convert this binary.
function getBinary(number) { const binary = []; function execute(bei) { if (bei === 0) { return ; } const next = parseInt(bei / 2, 10); const yu = bei % 2; binary.unshift(yu); execute(next); } execute(number); return binary.join(''); } console.log(getBinary(30)); // 11110 console.log(getBinary(100)); / / 1100100
Next, let’s look at how to convert binary to base 10. In plain English, you multiply each number in binary from right to left by the corresponding power of 2 and then increment it. For example, take the 100 above. The binary representation of 100 is 1100100. What we need to do is:
1100100
= 1 * 2^6 + 1 * 2^5 + 0 * 2^4 + 0 * 2^3 + 0 * 2^2 + 0 * 2^1 + 0 * 2^0
= 100
Without further ado, take a look at the implementation code:
function getDecimal(binary) { let number = 0; for (let i = binary.length - 1; i >= 0; i--) { const num = parseInt(binary[binary.length - i - 1]) * Math.pow(2, i); number += num; } return number; } console.log(getDecimal('11110')); // 30 console.log(getDecimal('1100100')); / / 100
Binary calculation of fractional parts
The binary calculation of the decimal part is different from the binary calculation of the integer part. The method of converting a decimal number into a binary decimal is called “multiplication by two”. That is, a decimal number is multiplied by 2 and the integer part is taken until the decimal part is 0. Here’s an example:
0.0625 * 2 = 0.125 · · · · · · · · · 0 0.125 * 2 = 0.25 · · · · · · · · · 0 0.25 * 2 = 0.5 · · · · · · · · · 0 0.5 * 2 = 1.0 · · · · · · · · · 1
And the decimal part is read in different directions. The binary conversion of a decimal is read from the top down, so the binary representation of 0.0625 is 0.0001. This is exactly divisible. In many cases, it is not divisible, such as 0.1 and 0.2. Write a function conversion:
function getBinary(number) { const binary = []; function execute(num) { if (num === 0) { return ; } const next = num * 2; const zheng = parseInt(next, 10); binary.push(zheng); execute(next - zheng); } execute(number); return '0.' + binary.join(''); } the console. The log (getBinary (0.0625)); / / 0.0001
Try to convert the decimal number from binary to decimal. Since the decimal number is multiplied, this is the division. Binary division can also be expressed as the multiplication of negative exponents, such as 1/2 = 2^-1; Let’s see how 0.0001 is converted to 0.0625:
0.0001 * 2 = 0 0 * 2 ^ - ^ 1 + 2 + 0 * 2 + 1 * 2 ^ ^ - 3-4 = 0.0625
So let’s implement this form in terms of a function.
function getDecimal(binary) { let number = 0; let small = binary.slice(2); for (let i = 0; i < small.length; i++) { const num = parseInt(small[i]) * Math.pow(2, 0 - i - 1); number += num; } return number; } the console. The log (getDecimal (' 0.0001 ')); / / 0.0625
This is where the binary conversion section comes in. For 0.1 + 0.2! The == 0.3 problem, the binary part above, is basically enough. Of course, the code part is only for reference, boundary and other issues have not been dealt with…
18.625 What is the binary representation of
18.625? =>
18.625? =>
The binary representation of 18 is: 100010, and the binary representation of 0.625 is: 0.101, so the binary representation of 18.625 is: 100010.101
</pre>
</details>
<sammry></sammry>
The second pre-knowledge, computer code
As we know, the computer is the use of binary to calculate, when it comes to the computer code, we have to mention the IEEE standard, and when it comes to the fractional part of the operation, we have to mention the IEEE binary floating-point arithmetic standard number (IEEE 754). Its standard binary representation is
V = (-1)^s * M * 2^E
- Where s is the sign bit, 0 is a positive number and 1 is a negative number;
- M is a mantissa, a binary decimal, which specifies that the first can only be 1,1 and the decimal point is omitted;
- E is the exponent, or the order code
Why are 1’s and decimal places omitted? Since all the first digits are 1, you can add one more digit to the end to increase accuracy. If the first one is 0, that doesn’t make any sense.
In general, today’s computers support two kinds of computational floating-point formats. One is single precision (float), the other is double precision (double).
format | The sign bit | mantissa | exponent | The total number | Offset value |
---|---|---|---|---|---|
Single precision | 1 | 8 | 23 | 32 | 127 |
double | 1 | 11 | 52 | 64 | 1023 |
Take JavaScript as an example. JS uses a double-precision format for computation, and its floating point number is 64 bits.
The original code
What is the source code, the source code is the simplest, is the sign bit plus the absolute value of the true value, that is, with the first sign, the remaining bits represent the value. We represent it in 11 bits as follows:
- +1 = [000 0000 0001] +1 = [000 0000 0001
-
-1 = [100 0000 0001], so the value range is [111 1111 1111 1111 1111] = [-1023, 1023];
Radix-minus-one complement
What is the inverse code, the inverse code is in the original code on the basis of the reversal. The opposite of a positive number is itself; The inverse code of a negative number is the sign bit unchanged, and the rest of the bits are reversed.
- +1 = [000 0000 0001] Original = [000 0000 0001] Reverse
- -1 = [100 0000 0001] Original = [111 1111 1110] Reverse
complement
What is complement, complement is on the basis of the reverse complement. The complement of a positive number is itself, and the complement of a negative number is the addition of 1 to its inverse.
- +1 = [000 0000 0001] Original = [000 0000 0001] Reverse = [000 0000 0001] Supplement
- -1 = [100 0000 0001] Original = [111 1111 1110] Reverse = [111 1111 1111 1111] Complement
- First of all in the computer there is no subtraction, it’s all addition, like 1 minus 1 in the computer is 1 + (-1).
-
If the source code is used for subtraction:
1 + (-1) = [000 0000 0001] + [100 0000 0001] = [100 0000 0010] = -2
=== =>>>
-
To solve this wrong problem, we have the inverse subtraction:
1 + (-1) = [000 0000 0001] inverse + [111 1111 1110] inverse = [111 1111 1111 1111] inverse = [100 0000 0000] original = 0
Found that the value is correct, only the sign bit is wrong; Although +0 and -0 are understood to be the same, 0 with a sign is meaningless, and there will be [000 0000 0000] primitive and [100 0000 0000] primitive encoding methods.
=== =>>> Conclusion: Not good
-
To solve the problem caused by the above symbol, there is a complement for subtraction:
1 + (-1) = [000 0000 0001] complement + [111 1111 1111] complement = [000 0000 0000] complement = [000 0000 0000] complement = [000 0000 0000] original = 0
The result is perfect, with 0 represented by [000, 0000, 0000], instead of [100, 0000, 0000].
=== =>>> Conclusion: Perfect
frameshift
Shift code, which is obtained by the reverse of the sign bit of the complement code, is generally used as the order code of floating point number. The purpose of introducing is to ensure that the machine zero of floating point number is all 0. This doesn’t have to be positive or negative.
- +1 = [000 0000 0001] Original = [000 0000 0001] Reverse = [000 0000 0001] Supplement = [100 0000 0001] Shift
- -1 = [100 0000 0001] Original = [111 1111 1110] Reverse = [111 1111 1111 1111] Supplement = [011 1111 1111 1111]
- +1 = [000 0000 0001] Original = [100 0000 0001] Shift
- -1 = [100 0000 0001] Original = [011 1111 1111] Shift
Why 0.1 + 0.2! = = 0.3?
Back to our topic, let’s see why 0.1 + 0.2! == 0.3. Take a look at the binary representations of 0.1 and 0.2.
0.1 = 0.0 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011.... 0011 infinite loop 0.2 = 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011.... 0011 is an infinite loop
We know that 0.1 and 0.2 are both binary decimals of a 0011 infinite loop.
As we know from the above, floating point numbers in JavaScript are represented by 64 bits, so how do 0.1 and 0.2 represent on the computer?
0.1 = (-1)^ 0 * 1.1 0011 0011 0011 * 2^(-4)
-4 = 10 0000 0100
According to IEEE 754 standard, it can be known that:
V = (-1)^S * M * 2^E (-1)^S = 0 // The negative number is 1 E = [100 0000 0100] original // 11th bit = [111 1111 1011] inverse = [111 1111 1100] complement = [011 1111 1100] shift M = 1001 1001 1001 1001 1001 1001 1001 1001 // 52 digits (1 decimal point omitted)
Similarly, the expression of 0.2 can be seen as follows:
0.2 = (-1)^ 0 * 1.1 0011 0011 0011 * 2^(-3) -4 = 100 0000 0011 V = (-1)^S * M * 2^E S = 0 Negative number is 1 E = [100 0000 0011] original // 11th bit = [111 1111 1100] inverse = [111 1111 1101] complement = [011 1111 1101] shift M = 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 // 52 digits (including 1 decimal point)
If you add them together, the order is different, so we need to do order reversal.
To order
There will be mantissa shift.
- If the larger order code is in line with the smaller order code, it is necessary to move the mantissa of the larger order code to the left. At this time, it is possible to remove the higher part of the mantissa in the process of shifting, which leads to data errors. This is not desirable
- If the small order code is in line with the large order code, it is necessary to move the number of the small order code to the right and fill up the high order with 0. This would crowd out the data on the right, which would affect the accuracy of the data, but not the overall size of the data.
Computers take the latter, smaller approach. And that’s what’s causing the problem today, the loss of precision.
So now, let’s look at this move up here.
// 0.1e = [100 0000 0011] Original = [111 1111 1100] Reverse = [111 1111 1101] Add // 11 bits, // Before moving M = 0100 1100 1100 1100 1100 1100 1100 E = [100 0000 0011] Original = [111 1111 1100] Reverse = [111 1111 1101] Add // 11 bits, M is equal to 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1001, 1/52, same
The above is the result after the binary order of 0.1 and 0.2. It is troublesome for us to calculate this number, so let’s directly take the truth value of 0.1 and 0.2 to calculate.
True value calculation
0.1 = 0.0 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011.... 0011 infinite loop 0.2 = 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011.... 0011 infinite loop 0.1 + 0.2 = 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 + 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 (0011... Discard) = 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 (1100... Discard) = 0.2999999999999998
This is so wrong!!
The values we get when the browser is running are:
0.1 + 0.2 = 0.30000000000000004
The reason for the above problem is that there will be rounding processing in the computer calculation. For example, the value discarded after the calculation of the true value is 1100. In the computer, there will also be rounding 0 to 1, that is, as follows:
0.1 + 0.2 = 0.0001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 1001 + 0.0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 0011 (0011... Discard) = 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 1100 (1100... Discard) = 0.0100 1100 1100 1100 1100 1100 1100 1100 1100 1101 (enter 1) = 0.30000000000000004
Hereby, we have understood this part of the chat, if there is anything wrong, welcome to point out. Thanks for reading.
Focus on
Welcome everyone to pay attention to my public number [Delai asked the front], the article first in the public number above.
In addition to the daily collection of selected articles in the community, technical articles will also be shared from time to time.
I hope we can learn together and make progress together.