930

# Why 0.1 + 0.2 != 0.3, or the mysterious world of floating-point numbers.

Yes, running the above expression in PHP, and in a lot of other languages, will confirm that 0.1 + 0.2 does not equal to 0.3.

But this weird behavior is explainable and even justified.

Floating point numbers can open you up a completely new universe.
Provided of course that they are properly used.

This talk will go through a bit of theory, and a lot of concrete examples.

From the Gulf War to the Quake graphics engine, via the Ariane 5 space rocket, we will see the benefits and the potential dangers of using floating-point numbers.

From these examples, we will extract some lessons we can translate into our day to day development practices.

And finally, the talk will cover when to use floating-point numbers, and when to avoid them, like when managing prices or billing, and what are the alternatives in those cases.

October 23, 2020

## Transcript

1. ### Why Why 0.1 + 0.2 != 0.3 0.1 + 0.2

!= 0.3 The Mysterious World of Floating Point Numbers The Mysterious World of Floating Point Numbers Benoit Jacquemont Benoit Jacquemont @bjacquemont @bjacquemont
2. ### <?php if (0.1 + 0.2 != 0.3) { echo "0.1

+ 0.2 is NOT EQUAL to 0.3\n"; } 0.1 + 0.2 is NOT EQUAL to 0.3
3. ### \$sum = 0.1 + 0.2; echo "Sum is ".\$sum."\n"; if

(\$sum != 0.3) { echo "\$sum is NOT EQUAL to 0.3\n"; } Sum is 0.3 0.3 is NOT EQUAL to 0.3 GO HOME GO HOME PHP PHP, ,
4. ### Let's try the same in JavaScript if (0.1 + 0.2

!= 0.3) { console.log('0.1 + 0.2 is NOT EQUAL to 0.3'); } 0.1 + 0.2 is NOT EQUAL to 0.3
5. ### Let's continue with JavaScript... let sum = 0.1 + 0.2;

console.log("Sum is " + sum); if (sum != 0.3) { console.log(sum + ' is NOT EQUAL to 0.3'); } Sum is 0.30000000000000004 0.30000000000000004 is NOT EQUAL to 0.3
6. ### Why in PHP ? \$sum = 0.1 + 0.2; echo

"Sum is ".\$sum."\n"; if (\$sum != 0.3) { echo "\$sum is NOT EQUAL to 0.3\n"; } Sum is 0.3 0.3 is NOT EQUAL to 0.3
7. ### The precision con g option No impact on the precision

of the computation! Round oating point numbers for display php.net/manual/en/ini.core.php#ini.precision
8. ### Increasing how many digits are displayed ini_set('precision', 17); // default

14 \$sum = 0.1 + 0.2; echo "Sum is ".\$sum."\n"; if (\$sum != 0.3) { echo "\$sum is NOT EQUAL to 0.3\n"; } Sum is 0.30000000000000004 0.30000000000000004 is NOT EQUAL to 0.3

10. ### Our computers are Our computers are binary binary 0 0

or or 1 1 How to represent something in How to represent something in between? between?
11. ### Representing Real Numbers in Scienti c Notation 0.006458 6.458 x

10-3 6 . 458 x 10 -3 6: most signi cant digit, always non zero 458: other signi cant digits -3: exponent 3 parts, each representable by integers
12. ### Binary Implementation 0.0110111 1 . 10111 x 2 -10 Let's

play with the exponent! Look ⇑, a oating point!
13. ### 64 bits Floating Point Encoding sign 1 bit exponent 11

bits significand 52 bits
14. ### Binary Floating Point to Decimal Representation 000000000110000000000000000000000000000000000000000000000010101 1.10101 x 23

110.101 1 1 0 . 1 0 1 22= 4 21= 2 20= 1 2-1= ½ 2-2= ¼ 2-3= ⅛ 1.10101 x 22 = 4 + 2 + ½ + ⅛ = 6.625

17. ### Largest integer: 9.22 x 10 Largest integer: 9.22 x 1018

18 Earthworms mass: 7.6 x 10 Earthworms mass: 7.6 x 1012 12 kg kg
18. ### Largest integer: 9.22 x 10 Largest integer: 9.22 x 1018

18 Oceans mass: 1.42 x 10 Oceans mass: 1.42 x 1021 21 kg kg
19. ### Largest integer: 9.22 x 10 Largest integer: 9.22 x 1018

18 Earth mass: 5.42 x 10 Earth mass: 5.42 x 1024 24 kg kg
20. ### Largest integer: 9.22 x 10 Largest integer: 9.22 x 1018

18 Milky way mass: 2.98 x 10 Milky way mass: 2.98 x 1042 42 kg kg
21. ### Largest integer: 9.22 x 10 Largest integer: 9.22 x 1018

18 Observable Universe mass: Observable Universe mass: 1.5 x 10 1.5 x 1053 53 kg kg

23. ### Largest oating point number echo number_format(PHP_FLOAT_MAX, 0, "", "")."\n"; 1797693134862315708145274237317043567980705675258449965989174768031

5726078002853876058955863276687817154045895351438246423432132688946 4182768467546703537516986049910576551282076245490090389328944075868 5084551339423045832369032229481658085593321233482747978262041447231 68738177180919299881250404026184124858368 1.80 x 10308 290 orders of magnitude bigger than max integer
24. ### Floating point numbers are better than integers at representing very

small and very large numbers
25. ### Float range is immensely wider than integers Integer: 1 to

9.2 x 1018 vs Float: 2.25 x 10-308 to 1.80 x 10308 But both use 64 bit storage...
26. ### Float can only represent a small sample of the numbers

inside the boundaries
27. None
28. None
29. None
30. None
31. None
32. None
33. None
34. None
35. None
36. ### Why 0.1 + 0.2 == 0.30000000000000004 ini_set('precision', 20); \$op1 =

0.1; \$op2 = 0.2; \$expectedSum = 0.3; echo "Op #1: ".\$op1."\n"; echo "Op #2: ".\$op2."\n"; \$actualSum = \$op1 + \$op2; echo "Actual Sum: ".\$actualSum."\n"; echo "Expected Sum: ".\$expectedSum."\n"; Op #1: 0.10000000000000000555 ← closest approximation of 0.1 Op #2: 0.2000000000000000111 ← closest approximation of 0.2 Actual Sum: 0.30000000000000004441 ← this is an approximation as well

38. ### How to have a consistent behavior between applications and hardware?

754 Standard So most computers and programming languages have the same behavior

40. None
41. None
42. None
43. None
44. ### Ariane 5 Maiden Flight 4 June 1996 Payload: 4 satellites

of 1.2 T each. Total cost: \$500 million
45. None

47. None
48. None
49. ### Same code as Ariane 4, but with 4x thrust... //64

bits float float horizontalVelocity = Inertial.getHorizontalVelocity(); // conversion to 16 bits integer (max 32767) int correction = computeCorrection((int) horizontalVelocity); // overflow! applyHorizontalCorrection(correction);
50. ### Lesson learned Converting oat to integer is highly risky Due

to the oat wide range
51. ### Same in PHP \$myFloat = 600000000000000000000; \$myInt = (int) \$myFloat;

echo \$myInt."\n"; -8742554432415203328 No error, and the result doesn't make sense...
52. ### The Gulf War & the The Gulf War & the

Patriot Missile Event Patriot Missile Event
53. None
54. None
55. None
56. None
57. None
58. None
59. None
60. None
61. None
62. None
63. None
64. None
65. None
66. None
67. None
68. ### // return the time in seconds in a float \$timeBefore

= getElapsedTimeFromStartup(); 1 2 3 sendRadarPingAndWaitForEcho(); 4 5 \$timeAfter = getElapsedTimeFromStartup(); 6 7 \$targetDistance = (\$timeAfter - \$timeBefore) * LIGHT_SPEED/2; 8 // return the time in seconds in a float \$timeBefore = getElapsedTimeFromStartup(); sendRadarPingAndWaitForEcho(); 1 2 3 4 5 \$timeAfter = getElapsedTimeFromStartup(); 6 7 \$targetDistance = (\$timeAfter - \$timeBefore) * LIGHT_SPEED/2; 8 // return the time in seconds in a float \$timeBefore = getElapsedTimeFromStartup(); sendRadarPingAndWaitForEcho(); \$timeAfter = getElapsedTimeFromStartup(); 1 2 3 4 5 6 7 \$targetDistance = (\$timeAfter - \$timeBefore) * LIGHT_SPEED/2; 8 // return the time in seconds in a float \$timeBefore = getElapsedTimeFromStartup(); sendRadarPingAndWaitForEcho(); \$timeAfter = getElapsedTimeFromStartup(); \$targetDistance = (\$timeAfter - \$timeBefore) * LIGHT_SPEED/2; 1 2 3 4 5 6 7 8
69. ### Float rule #2 The bigger a oat, the less precise

it becomes aka Loss of precision
70. ### // return the time in seconds in a float \$timeBefore

= getElapsedTimeFromStartup(); sendRadarPingAndWaitForEcho(); \$timeAfter = getElapsedTimeFromStartup(); \$targetDistance = (\$timeAfter - \$timeBefore) * LIGHT_SPEED/2; The longer the missile battery runs, the less precise getElapsedTimeFromStartup() becomes
71. ### Missile battery run time: 100 hours Inaccuray due to loss

of precision: 0.3433s Scud missile speed: Mach 5 Patriot missile offset to its target: 589m
72. ### Lesson learned A oat number cannot be big and precise

at the same time.

74. None
75. ### Quake Quake Released in Feb. 1997 Released in Feb. 1997

Full real-time 3D Full real-time 3D rendering without 3D rendering without 3D accelerator accelerator Runs on a 60Mhz Runs on a 60Mhz Pentium Pentium

78. ### About me E-COMMERCE MOBILE APPLICATION PRINT CATALOG POINTS OF SALE

ERP MEDIA SERVER SUPPLIERS PURCHASING DPT MARKETING DPT CSV FTP XML XLS SUPPLIERS PORTAL ENRIC H TRAN SLATE CONTROL Single Source of Truth for Product Information
79. None
80. ### Storing real numbers in database mysql> CREATE TABLE my_float(f FLOAT);

mysql> INSERT INTO my_float(f) VALUES(0.1); mysql> SELECT * FROM my_float; +------+ | f | +------+ | 0.1 | +------+ mysql> SELECT * FROM my_float WHERE f = 0.1; Empty set (0.00 sec) mysql> SELECT ROUND(f, 17) FROM my_float; +---------------------+ | ROUND(f, 17) | +---------------------+ | 0.10000000149011612 | +---------------------+ Loss of data due to approximation
81. ### Storing real numbers with Decimal mysql> CREATE TABLE my_decimal(d DECIMAL(10,5));

mysql> INSERT INTO my_decimal(d) VALUES(0.1); mysql> SELECT * FROM my_decimal; +---------+ | d | +---------+ | 0.10000 | +---------+ mysql> SELECT * FROM my_decimal WHERE d = 0.1; +---------+ | d | +---------+ | 0.10000 | +---------+ No approximation with xed point type
82. ### Transmitting real numbers ini_set('precision', 17); \$myJsonFromAPI = '{"name": "foo", "price":

29.99}'; \$myObject = json_decode(\$myJsonFromAPI, false); echo "Price:".\$myObject->price."\n"; Price:29.989999999999998 Loss of data due to approximation Use strings to avoid approximation

89. ### Money, payment & billing \$price = 49.99; \$coupon = 20.25;

\$card = 29.74; 1 2 3 4 echo "Price: \$price. Using coupon \$coupon.\n"; 5 \$remaining = \$price - \$coupon; 6 7 echo "\$remaining still to pay. Using card \$card.\n"; 8 \$remaining = \$remaining - \$card; 9 10 if (\$remaining == 0) { 11 echo "Thank you for your payment!\n"; 12 } else { 13 echo "Paiement not finished: \$remaining to pay\n"; 14 } 15 echo "Price: \$price. Using coupon \$coupon.\n"; \$remaining = \$price - \$coupon; \$price = 49.99; 1 \$coupon = 20.25; 2 \$card = 29.74; 3 4 5 6 7 echo "\$remaining still to pay. Using card \$card.\n"; 8 \$remaining = \$remaining - \$card; 9 10 if (\$remaining == 0) { 11 echo "Thank you for your payment!\n"; 12 } else { 13 echo "Paiement not finished: \$remaining to pay\n"; 14 } 15 echo "\$remaining still to pay. Using card \$card.\n"; \$remaining = \$remaining - \$card; \$price = 49.99; 1 \$coupon = 20.25; 2 \$card = 29.74; 3 4 echo "Price: \$price. Using coupon \$coupon.\n"; 5 \$remaining = \$price - \$coupon; 6 7 8 9 10 if (\$remaining == 0) { 11 echo "Thank you for your payment!\n"; 12 } else { 13 echo "Paiement not finished: \$remaining to pay\n"; 14 } 15 \$price = 49.99; \$coupon = 20.25; \$card = 29.74; echo "Price: \$price. Using coupon \$coupon.\n"; \$remaining = \$price - \$coupon; echo "\$remaining still to pay. Using card \$card.\n"; \$remaining = \$remaining - \$card; if (\$remaining == 0) { echo "Thank you for your payment!\n"; } else { echo "Paiement not finished: \$remaining to pay\n"; } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 Price: 49.99. Using coupon 20.25. 1 29.74 still to pay. Using card 29.74. 2 Paiement not finished: 3.5527136788005E-15 to pay 3 Price: 49.99. Using coupon 20.25. 29.74 still to pay. Using card 29.74. 1 2 Paiement not finished: 3.5527136788005E-15 to pay 3 Price: 49.99. Using coupon 20.25. 29.74 still to pay. Using card 29.74. Paiement not finished: 3.5527136788005E-15 to pay 1 2 3
90. ### Money, payment & billing ini_set('precision', 17); //Only change! \$price =

49.99; \$coupon = 20.25; \$card = 29.74; echo "Price: \$price. Using coupon \$coupon.\n"; \$remaining = \$price - \$coupon; echo "\$remaining still to pay. Using card \$card.\n"; \$remaining = \$remaining - \$card; if (\$remaining == 0) { echo "Thank you for your payment!\n"; } else { echo "Paiement not finished: \$remaining to pay\n"; } Price: 49.990000000000002. Using coupon 20.25. 29.740000000000002 still to pay. Using card 29.739999999999998. Paiement not finished: 3.5527136788005009E-15 to pay
91. ### Using Floating Point Numbers with money: It seems to work...

... until it doesn't.

93. ### Use cents and integers \$price = 4999; \$coupon = 2025;

\$card = 2974; function f(int \$amount): string { return substr(\$amount, 0, -2).'.'.substr(\$amount, -2); } echo "Price:".f(\$price).". Using coupon ".f(\$coupon).".\n"; \$remaining = \$price - \$coupon; echo f(\$remaining).' still to pay. Using card '.f(\$card)."\n"; \$remaining = \$remaining - \$card; if (\$remaining == 0) { echo "Thank you for your payment!\n"; } else { echo "Payement not finished.\nRemaining amount to pay:".f(\$remaining)."\n"; } Price:49.99. Using coupon 20.25. 29.74 still to pay. Using card 29.74 Thank you for your payment!

96. ### Strings ini_set('precision', 20); \$myRealNumber = "0.1"; echo \$myRealNumber."\n"; 0.1 Simple

No computation No comparison
97. ### BCMath Arbitrary Precision Mathematics bcscale(2); \$sum = bcadd("0.1", "0.2"); //

strings!! if (bccomp(\$sum, "0.3")) { echo "\$sum is NOT EQUAL to 0.3\n"; } else { echo "\$sum is EQUAL to 0.3\n"; } 0.30 IS EQUAL to 0.3 Computation Comparison Needs an extension
98. ### php decimal Arbitrary-Precision Decimal Arithmetic For PHP 7 use Decimal\Decimal;

\$op1 = new Decimal("0.1"); \$op2 = new Decimal("0.2"); \$sum = \$op1->add(\$op2); if (!\$sum->equals("0.3")) { echo "\$sum is NOT EQUAL to 0.3\n"; } else { echo "\$sum is EQUAL to 0.3\n"; } 0.3 is EQUAL to 0.3 Nice object interface Not easily available php-decimal.io
99. ### Floating Point Numbers Fear them Respect them Love them Thank

you! joind.in/talk/1d52e