Benoit Jacquemont
October 23, 2020
3.5k

# 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. ### 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

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

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

31. ### Ariane 5 Maiden Flight 4 June 1996 Payload: 4 satellites

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

33. ### 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);
34. ### Lesson learned Converting oat to integer is highly risky Due

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

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

Patriot Missile Event Patriot Missile Event
37. ### // 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
38. ### Float rule #2 The bigger a oat, the less precise

it becomes aka Loss of precision
39. ### // 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
40. ### 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
41. ### Lesson learned A oat number cannot be big and precise

at the same time.

43. ### 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

46. ### 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
47. ### 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
48. ### 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
49. ### 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

56. ### 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
57. ### 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
58. ### Using Floating Point Numbers with money: It seems to work...

... until it doesn't.

60. ### 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!

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

No computation No comparison
64. ### 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
65. ### 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
66. ### Floating Point Numbers Fear them Respect them Love them Thank

you! joind.in/talk/1d52e