Brief
One day a friend asked me, “How can 0.7 * 180 be equal to 125.99999999998 in JS calculation? There are too many pits!” At that time, I guessed that it was caused by round-off error when binary values were represented, but I didn’t know exactly how it was caused and what methods to avoid it. Therefore, I spent 3 weeks to settle down and understand this problem. In the process of learning, I also found not only 0.7 * 180==125.99999999998, but also the following pits
-
The famous 0.1 + 0.2 === 0.30000000000000004
-
1000000000000000128 = = = 1000000000000000129
IEEE 754 Floating-point
As we all know, JS only has the numeric type Number, and Number adopts IEEE 754 64-bit double precision floating point encoding. Floating point number representation has the following characteristics:
-
Floating point numbers can represent a much larger range of values than the integer representation of the same number of digits;
-
Floating-point numbers cannot accurately represent all values in their range of values, whereas signed and unsigned integers accurately represent every value in their range of values;
-
Floating point numbers can only accurately represent values of m*2e;
-
When biased-Exponent is 2E-1-1, the integer values in the range can be accurately represented.
-
When bias-Exponent is not 2E-1-1, the floating point number cannot accurately represent the integer values in the range.
Because some values cannot be accurately represented (stored), the deviation becomes more obvious after the calculation.
To learn more about floating point numbers, see the following article:
-
Basic Field: A Closer Look at Source, Inverse, and Complement
-
Foundano: A Closer Look at unsigned Integers
-
Foundano: A Closer Look at signed Integers
-
Foundano: Floating Point numbers in Detail
Why 0.1 + 0.2 === 0.30000000000000004?
The most famous example of a floating point error is 0.1 + 0.2 === 0.30000000000000004. Just look at this website 0.30000000000000004.com/. This is not just a problem with JavaScript, but with any floating-point encoding used by IEEE 754 Floating-point. Now let’s analyze the whole operation.
-
Binary representation of 1.1001100110011001100110011001100110011001100110011001 0.1 (0011) + 1 * 2 ^ 4;
-
IEEE 754 Floating-Point uses round to nearest, tie to even rounding mode when 64-bit storage space cannot store complete infinite repeating decimals. 0.1 when the actual storage so bit pattern is 0-01111111011-1001100110011001100110011001100110011001100110011010;
-
The binary representation of 1.1001100110011001100110011001100110011001100110011001 0.2 1 (0011) + * ^ 2-3;
-
IEEE 754 Floating-Point uses round to nearest, tie to even rounding mode when 64-bit storage space cannot store complete infinite repeating decimals. 0.2 when the actual storage so bit pattern is 0-01111111100-1001100110011001100110011001100110011001100110011010;
-
The actual storage pattern as operands to floating-point addition, get a 0-01111111101-0011001100110011001100110011001100110011001100110100. When converted to decimal, the value is 0.30000000000000004.
According to 0.7 * 180 = = = 125.99999999998?
-
0.7 when the actual storage bit pattern is 0-01111111110-0110011001100110011001100110011001100110011001100110;
-
180 actually stored pattern is 0-10000000110-0110100000000000000000000000000000000000000000000000;
-
The actual storage pattern as operands to floating-point multiplication, get a 0-10000000101-1111011111111111111111111111111111111111101010000001. The decimal value is 125.99999999998.
Why 1000000000000000128 === 1000000000000000129?
-
1000000000000000128 actually stored pattern is 0-10000111010-1011110000010110110101100111010011101100100000000001;
-
1000000000000000129 actually stored pattern is 0-10000111010-1011110000010110110101100111010011101100100000000001;
-
Therefore, the actual storage bit patterns of 1000000000000000128 and 1000000000000000129 are the same.
Solution
By now we all understand that any language that adopts IEEE 754 FP floating-point encoding can have this problem, but their standard libraries already provide a solution. JS? Apparently not. The disadvantage of course is the pit, and the advantage is exactly the pit 🙂
For different application requirements, we have different implementation methods.
Solution 0x00 – Simple implementation
Simple operations on decimals and small integers can be done as follows
function numAdd(num1/*:String*/, num2/*:String*/) {
var baseNum, baseNum1, baseNum2;
try {
baseNum1 = num1.split(".")[1].length;
} catch (e) {
baseNum1 = 0;
}
try {
baseNum2 = num2.split(".")[1].length;
} catch (e) {
baseNum2 = 0;
}
baseNum = Math.pow(10, Math.max(baseNum1, baseNum2));
return (num1 * baseNum + num2 * baseNum) / baseNum;
};Copy the code
Solution 0x01 – math.js
For complex and comprehensive computing functionality you must go to math.js, which internally references decimal.js and fraction.js. Extremely powerful function, no problem for production environment!
Solution 0x02 – D.js
D. JS is my practice project. By the time this article is published, the version of D. JS is V0.2.0, which only realizes addition, subtraction, multiplication and divisible operations. There are a lot of bugs, but at least it has solved the problem of 0.1+0.2.
Var product = d.addd (0.1, 0.2) console.log(sum + ") // "2E-4 ") console.log(Product + ") // 0.000002 var Quotient = D.div(-3, 2) console.log(Quotient + ") // -(1+1/2)Copy the code
Answer:
-
Since only integers between number. MIN_SAFE_INTEGER and number. MAX_SAFE_INTEGER can be accurately represented, that is, as long as the operands and results of the operation are guaranteed to fall within this threshold, the operation result is accurate.
-
The key problem is how to convert or split decimals and maxima into numbers.MIN_SAFE_INTEGER to Number.MAX_SAFE_INTEGER.
-
The conversion of decimals to integers is, of course, represented by scientific notation, and handled by moving the decimal point to the right and reducing the power; (for example, 0.000123 is equivalent to 123 * 10-6)
-
Maxima requires splitting, and the rules for splitting are varied.
-
Factorization: suppose 12345 is split to get 5 * 2469;
-
Bitwise split: Suppose you split 12345 in a group of three values to get 345 and 12, whereas the actual value is 12*1000 + 345. As far as I am concerned, the split rule of 1 is structurally unstable and not intuitive; While the rule of 2 is intuitive, and the formula of split and recovery is fixed.
-
-
The remainder consists of sign bits, numerator, and denominator, and the sign is the same as the integer part, so you only need to think about how to represent the numerator and denominator.
-
The infinite number of cycles is just a matter of how to represent the number of cycles. (For example, 10.2343434 can be divided into 10.23 and the weight of 34 and 34 cycles)
Once the coding rules are in place, it is just a matter of how to implement the various operations based on the specified encoding.
-
How to implement addition and subtraction operations based on the above numerical coding rules?
-
How to realize multiplication and division based on the above numerical coding rules? (In fact, as long as the addition and subtraction operations are solved, multiplication and division must be solved, it is just an efficiency problem)
-
How can other mathematical operations such as sin, tan and % be implemented based on the above numerical coding rules?
In addition, because of mathematical operations, it is necessary to keep the variables as add, sub, mul and div arguments pure as mathematical formula operands. (D. JS now uses the on-demand copy generation method, which can be expected to lead to unmaintainable overall code as the amount of code increases.)
Conclusion
Based on my urine nature, D. JS will adopt a strategy of Persistent/Immutable Data Structure (after I understand it). Welcome your advice!
Respect the original, reproduced please indicate from: www.cnblogs.com/fsjohnhuang… ^_^ fat son John
Thanks
Es5. Making. IO github.com/MikeMcl/dec… www.ruanyifeng.com/blog/2010/0… Demon. Tw/copy – paste /…