The paper

In JS, 0.1 + 0.2 = 0.30000000000000004, not 0.3 as expected.

This is a typical floating-point error that occurs in any language that uses the IEEE-754 floating-point standard. There is even a website called 0.30000000000000004.com that lists the output of various languages. The following will analyze the floating-point operation of 01 + 0.2 and discuss the solutions to floating-point precision loss.

You may need the following points to understand the following:

  • Basino: Floating point numbers in detail
  • Get behind 0.1 + 0.2 === 0.30000000000000004
  • How to round binary numbers

Decimal to binary

Internal floating-point operations are performed in binary form, so we need to convert decimal to binary first.

Taking 4.375 as an example, the integer part is converted to binary by mod, for example

4/2 = 2.. 0 2/2 = 1.. 0 = > 100.Copy the code

The conversion rule for the decimal part is to multiply the decimal part by 2 and take the ones place of the result in sequence until the product is 1.0, for example

0.75 0.75 * 2 = 1.5 0.5 * 2 = 1.0 0.375 => 011Copy the code

The binary representation of 4.375 is 100.011.

In accordance with the rules on

/ / 0.1 ` ` conversion result is 0.000110011001100110011001100110011 (0011) / / it follows that the cycle of 0011 / / 0.2 ` ` for the transformation of the results 0.001100110011001100110011001100110011 (0011).Copy the code

The binary floating point number IEEE-754 is represented

Ieee-754 represents a floating point number as defined below



S is the flag bit, 0 is a positive number and 1 is a negative number

M stands for significant number, 1 <= M < 2

E is the exponential bit

If an IEEE-754 floating-point number is stored in 64 bits, each bit is defined as follows.

S is stored in the sign bit, accounting for 1 bit.

M is stored in thefractionBecause offractionThe first digit is always 1, so the first digit is hidden, equivalent to 53 bits to store. M moves the decimal point left and right, changing the exponent to ensure that the first digit is 1. For example,

E stored in theexponent, full name isBiased-exponent, is encoded as an offset, that is, there is no sign bit, and an intermediate value is specified as 0. Greater than this value is positive, and less than this value is negative. setexponentTake upLBit, the intermediate value is calculated as.

Round to nearest, ties to even

Based on the above rule, we find that the binary representation of 0.1 is an infinite repeating decimal, but only 53 bits of our M are available. Similar to rounding of decimal operation, IEEE-754 adopts Round to nearest and ties to even for rounding, as shown in the figure.

For example, the binary decimal 0.11100 requires two significant digits. Under the Round to nearest rule, there are two optional values of 0.11 and 1.00, and the absolute values of the difference between the two values and the original value are the same, thus a TIES situation occurs, and the second rule ties to even is triggered, so the final rounding result is 1.00

So, 0.1 ends up as a floating point

43210 ------ Fraction Index 1.100... (1100) x 11... 11001100 11010 ------ Rounding result V = 1.100... (1100) x 11... 11010 * (2 ^ 4)Copy the code

Similarly, 0.2 is

V = 1.100... (1100) x 11... 11010 * (2 ^ 3)Copy the code

Floating-point addition

The principle of pair to pair is that the lower order is aligned to the higher order, because the precision of moving the decimal point to the right is much less than that of moving the decimal point to the left, so the result after 0.1 pairs of order is

V = 0.1100... (1100) x 11... 1101 * (2 ^ 3)Copy the code

Sign bit + mantissa (including hidden bit) Add and subtract the sign bit adopts the double sign judgment method. Therefore, the sign bit of a positive number is 00.

00 00-01100110011001100110011001100110011001100110011001101-0.1-11001100110011001100110011001100110011001100110011010 - 0.2 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- - 01-00110011001100110011001100110011001100110011001100111.Copy the code

Normalizing & rounding sign bit is 01 when the overflow occurs, normalizing is performed to the right, mantissa is moved 1 bit to the right, order code +1, the result is

210 -- fraction index 01-00110011001100110011001100110011001100110011001100111 00-100110011001100110011001100110011001100110011001100111 - tail moves to the right one 00-10011001100110011001100110011001100110011001100110100 - rounding the final results as V = 1.0011001100110011001100110011001100110011001100110100 * (2 ^ 2)Copy the code

Convert to base 10 with the following code

/ / decimal part according to the following rules accumulative 0.010011001100110011001100110011001100110011001100110100 | | __ + 1 * 2 (2) | | __0 * 2 ^ (1)Copy the code

JS code examples

'010011001100110011001100110011001100110011001100110100' .split('') .map(val => +val) .reduce((pre, val, index) => { return pre + val * (2 ** -(index + 1)); }, 0); / / output 0.30000000000000004Copy the code

Bingo! Never put off till tomorrow what you can.

Through the above calculation, we find that desperming occurs in the rounding part, in other words, cannot beAccurately represented floating-point numbers, the problem of sperm loss occurs during operation.

The countermeasure of spermatosia problem

So what can we do to avoid it?

The most direct and productive recommendation is to use math.js

JS implements accurate calculation of floating point numbers. The most basic principle is to divide floating point numbers into numeral. MIN_SAFE_INTEGER and numeral. MAX_SAFE_INTEGER, which can be accurately represented, and then calculate and summarize the results.

There is no further research here, the reader can consult by himself.