As Laravel developer we don’t deal only with Laravel specific features, but quite often with pure PHP. Let’s verify your knowledge a bit.
What would be the result of the following script:
1 2 3 |
<?php die(var_dump(1200.85 * 100 === 120085)); |
Possible answers:
- false
- true
- It depends …
Okay, you’ve chosen your answer. The second question – would the answer change if we changed operator from ===
to ==
? If yes, what would be the correct answer then?
Please don’t read further if you haven’t chosen your answers yet.
.
.
.
Here are the correct answers.
In first case what is:
1 2 3 |
<?php die(var_dump(1200.85 * 100 === 120085)); |
the answer is quite obvious. Because we use ===
operator on left we should get float and on right we should get int, so we should get false. This it the correct answer and I think we have nothing more to discuss here.
But what in second case?
When looking at below code
1 2 3 |
<?php die(var_dump(1200.85 * 100 == 120085)); |
you can think – on left we will get 120085 and on right 120085 so it’s obvious the answer is true. Well, if you have chosen this answer you are wrong. Don’t worry I was also wrong, hopefully found this issue just by accident.
Try to run this code and it’s very possible you should see false. But it’s also very possible that if you change values a bit you’ll get true.
For example when I run:
1 2 3 |
<?php die(var_dump(1200.81 * 100 == 120081)); |
I’m getting true. So the correct answer is in fact It depends on …
Okay, but what’s exact problem here? Let’s try to var_dump()
parts of the expression like so:
1 2 3 4 5 |
<?php var_dump(1200.85 * 100); die(var_dump(120085)); |
Well, this is quite tricky, because we get:
float(120085) int(120085)
so the problem is not visible at all here. Ok, so it seems values are equal, but operator ==
shows they are not equal. Let’s display difference between those 2 numbers then:
1 2 3 |
<?php die(var_dump(120085 - 1200.85 * 100)); |
Now we should get something like this:
float(1.4551915228367E-11)
So it seems the difference is really not 0, but very small number that is almost zero. If we look at this big warning on http://php.net/manual/en/language.types.float.php everything should be clear why it that. We can read in PHP manual:
Floating point numbers have limited precision. Although it depends on the system, PHP typically uses the IEEE 754 double precision format, which will give a maximum relative error due to rounding in the order of 1.11e-16. Non elementary arithmetic operations may give larger errors, and, of course, error propagation must be considered when several operations are compounded.
Additionally, rational numbers that are exactly representable as floating point numbers in base 10, like 0.1 or 0.7, do not have an exact representation as floating point numbers in base 2, which is used internally, no matter the size of the mantissa. Hence, they cannot be converted into their internal binary counterparts without a small loss of precision. This can lead to confusing results: for example, floor((0.1+0.7)*10) will usually return 7 instead of the expected 8, since the internal representation will be something like 7.9999999999999991118….
So the problem is float precision here. The worst thing here is that it’s so obvious thing that should work without a problem that most of us, don’t event expect issue here.
So what to do in case we would like to make comparison here to get expected result?
We could use abs()
:
1 2 3 |
<?php var_dump(abs((1200.85 * 100 - 120085)) < 0.01)); |
to verify if difference between those numbers is low enough (for example below 0.01 as showed above).
We can also use round()
to make it a bit cleaner:
1 |
die(var_dump(round(1200.85 * 100) == 120085)); |
As you see, sometimes something quite obvious might not work in PHP as expected and you can test it manually and also write some unit tests and for sample data you will get desired results but on real data you might get some unexpected results for some cases if you don’t know those small details about floats and don’t know how to avoid them.
Mehedi Hassan
Didn’t knew that before… thanx for sharing
Giacomo
It is not just a PHP issue, it is, as you correctly state, a representation issue.
Every language that implements the IEEE754 standards will suffer the very same (on the same machine) behaviour.