Dealing with floating-point calculations in PHP
Long time ago – while learning PHP – I found the following piece of code:
echo intval((0.1+0.7)*10); // int(7)
and then I forgot about it. And while I can understand that the above code returns the number 7 instead of 8, the knowledge and memory of this is critically important while performing any arithmetic calculations.
Explanation of the source of the above error can be found in the PHP documentation: representation of that number in the memory, after the adding and the multiplying is done, is as follows: 7.9999999999999991118… and a simple casting it to Integer returns 7 (the (int)
operator and the intval()
function give us, as expected, only the integer part of the number).
I’ve found several solutions to this problem, some less and others more elegant, and the problem itself arose while calculating the number of grosze (or any other fractional part of any currency) in my engineering work. It emerged in the implementation phase only thanks to TDD and I guess I wouldn’t even notice such a small error in the calculation without testing :)
Solution no. 1 – work on Integers
echo intval((0.1*10) + (0.7*10)); // int(8)
It’s a very safe method (as far as accuracy is concerned) – before making any calculations on floating-point numbers we multiply them by a chosen power of 10, depending on the accuracy we want to achieve, and then we can continue :) Some drawbacks of this solution:
the need for multiplying each of the numbers before performing arithmetic operations limited ability to perform calculations (basically just adding / subtracting / multiplying and “derivative” operations)
Solution no. 2 – the round() function
echo intval(round((0.1+0.7)*10, 0)); // int(8)
This is the solution that I use – it is the fastest one (computationally), probably the most justified one in the case of operations performed in my code (quite a few decimal places), and also quite elegant.
Solution no. 3 – casting to string
echo intval(strval((0.1+0.7)*10)); // int(8)
This solution is slower than the previous one, and the idea of (manually) casting a number to a text string and then again to a number doesn’t really convince me. However, it does exist and can be used if necessary. More on this idea can be found below.
Solution no. 4 – BC Math
echo intval(bcmul(bcadd(0.1, 0.7, 1), 10); // int(8)
Definitely the slowest solution of them all, however, it provides the most accurate calculations. A native PHP solution which collects data, performs calculations, and returns numbers as strings. It’s bulky, accurate and slow, but in some critical cases there is no other way.
Summary
This is probably where I’ll stop talking about my dilemma with the accuracy of floating-point numbers in PHP, but I do have one last comment for this final paragraph :) The fact that I wrote that one of these solutions is faster and the other is slower should not affect your decision about which one to choose ;) Well, unless you have some hundreds of thousands of calculations to perform. Otherwise, you will surely find a few other places where things can be accelerated a bit. Good luck! :)