Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

Slide 3

Slide 3 text

$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, ,

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

0.1 + 0.2 == 0.30000000000000004 What is going on?

Slide 10

Slide 10 text

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?

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Binary Implementation 0.0110111 1 . 10111 x 2 -10 Let's play with the exponent! Look ⇑, a oating point!

Slide 13

Slide 13 text

64 bits Floating Point Encoding sign 1 bit exponent 11 bits significand 52 bits

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

Floating Point Numbers Solve Another Integers Limitation: Size

Slide 16

Slide 16 text

Largest Integer echo PHP_INT_MAX."\n"; 9223372036854775807 9.22 x 1018

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

Integers are very limited in size

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

Floating point numbers are better than integers at representing very small and very large numbers

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

Float can only represent a small sample of the numbers inside the boundaries

Slide 27

Slide 27 text

No content

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

No content

Slide 33

Slide 33 text

No content

Slide 34

Slide 34 text

No content

Slide 35

Slide 35 text

No content

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Floating point numbers rule #1: Most oat numbers are approximations

Slide 38

Slide 38 text

How to have a consistent behavior between applications and hardware? 754 Standard So most computers and programming languages have the same behavior

Slide 39

Slide 39 text

Floating Point Numbers Dangers and Feats

Slide 40

Slide 40 text

No content

Slide 41

Slide 41 text

No content

Slide 42

Slide 42 text

No content

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

Ariane 5 Maiden Flight 4 June 1996 Payload: 4 satellites of 1.2 T each. Total cost: $500 million

Slide 45

Slide 45 text

No content

Slide 46

Slide 46 text

Oops... Oops...

Slide 47

Slide 47 text

No content

Slide 48

Slide 48 text

No content

Slide 49

Slide 49 text

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);

Slide 50

Slide 50 text

Lesson learned Converting oat to integer is highly risky Due to the oat wide range

Slide 51

Slide 51 text

Same in PHP $myFloat = 600000000000000000000; $myInt = (int) $myFloat; echo $myInt."\n"; -8742554432415203328 No error, and the result doesn't make sense...

Slide 52

Slide 52 text

The Gulf War & the The Gulf War & the Patriot Missile Event Patriot Missile Event

Slide 53

Slide 53 text

No content

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

No content

Slide 56

Slide 56 text

No content

Slide 57

Slide 57 text

No content

Slide 58

Slide 58 text

No content

Slide 59

Slide 59 text

No content

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

No content

Slide 64

Slide 64 text

No content

Slide 65

Slide 65 text

No content

Slide 66

Slide 66 text

No content

Slide 67

Slide 67 text

No content

Slide 68

Slide 68 text

// 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

Slide 69

Slide 69 text

Float rule #2 The bigger a oat, the less precise it becomes aka Loss of precision

Slide 70

Slide 70 text

// 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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

Lesson learned A oat number cannot be big and precise at the same time.

Slide 73

Slide 73 text

But Floating Point numbers can bring cool stuff

Slide 74

Slide 74 text

No content

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

Lesson learned Floating Point Numbers calculations are crazy fast

Slide 77

Slide 77 text

What about us?

Slide 78

Slide 78 text

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

Slide 79

Slide 79 text

No content

Slide 80

Slide 80 text

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

Slide 81

Slide 81 text

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

Slide 82

Slide 82 text

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

Slide 83

Slide 83 text

When oat are shining Non-exhaustive list

Slide 84

Slide 84 text

Scienti c Calculations Scienti c Calculations

Slide 85

Slide 85 text

Sensors Sensors

Slide 86

Slide 86 text

Statistics Statistics

Slide 87

Slide 87 text

Machine Learning Machine Learning

Slide 88

Slide 88 text

When avoiding oat... ...and how. Non-exhaustive list

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

Using Floating Point Numbers with money: It seems to work... ... until it doesn't.

Slide 92

Slide 92 text

Money is a discrete entity. No approximation in money!

Slide 93

Slide 93 text

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!

Slide 94

Slide 94 text

All amount in cents

Slide 95

Slide 95 text

Other alternatives to Floating point numbers

Slide 96

Slide 96 text

Strings ini_set('precision', 20); $myRealNumber = "0.1"; echo $myRealNumber."\n"; 0.1 Simple No computation No comparison

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

Floating Point Numbers Fear them Respect them Love them Thank you! joind.in/talk/1d52e