[Solved] Do arithmetic operations on FLOAT numbers correctly in PHP


Sure the problem of PHP floating point precision was a topic on SO quite often, still I cannot see why this questions albeits 18 downvotes as the questionare asked very precisly for a concrete problem with a concrete floating point number. I have solved this in a way that everybody should understand clearly what the problem here is (not just those unprecise reasoning).

Here we go – see the results of the following:

$example = 90000000000009132 - 1;

var_dump(PHP_INT_MAX);
var_dump($example); 
var_dump(PHP_FLOAT_MAX);
var_dump(sprintf('%.0g',  $example)); 
var_dump(sprintf('%.0f',  $example));

The results:

int(9223372036854775807) 
int(90000000000009131) 
float(1.7976931348623E+308) 
string(7) "9.0e+16" 
string(17) "90000000000009136"

That means:

  • You are not above the maximum integer size which would make
    arithmetic operations fail
  • The result of your calculation is correct with integers
  • You are not above PHP_FLOAT_MAX (requires PHP 7.2) though the size of this depends on your environment
  • The floating point representation of your calculation is unexpectedly wrong (as your printf statement converts to float and then to string)

So why do we have this strange result? Lets make some tests:

var_dump(sprintf('%.0f',  90000000000009051));
var_dump(sprintf('%.0f',  90000000000009101));
var_dump(sprintf('%.0f',  90000000000009105));
var_dump(sprintf('%.0f',  90000000000009114));
var_dump(sprintf('%.0f',  90000000000009121));
var_dump(sprintf('%.0f',  90000000000009124));
var_dump(sprintf('%.0f',  90000000000009128));
var_dump(sprintf('%.0f',  90000000000009130));
var_dump(sprintf('%.0f',  90000000000009131));
var_dump(sprintf('%.0f',  90000000000009138));
var_dump(sprintf('%.0f',  90000000000009142));
var_dump(sprintf('%.0f',  90000000000009177));

The result of this is:

string(17) "90000000000009056"
string(17) "90000000000009104"
string(17) "90000000000009104"
string(17) "90000000000009120"
string(17) "90000000000009120"
string(17) "90000000000009120"
string(17) "90000000000009120"
string(17) "90000000000009136"
string(17) "90000000000009136"
string(17) "90000000000009136"
string(17) "90000000000009136"
string(17) "90000000000009184"

Look at those numbers. The digits at the end are always dividable by 16. And what means the 16 in information technology? A byte. Hexadecimal. What does it tell us?
You probably have exceeded the limit where floats are basically precise (not necessarily in arithmetic operations).

What if we reduce those numbers by one zero to 16 digits?

var_dump(sprintf('%.0f',  9000000000009051));
var_dump(sprintf('%.0f',  9000000000009101));
var_dump(sprintf('%.0f',  9000000000009124));
var_dump(sprintf('%.0f',  9000000000009138));
var_dump(sprintf('%.0f',  9000000000009142));
var_dump(sprintf('%.0f',  9000000000009177));

string(16) "9000000000009051"
string(16) "9000000000009101"
string(16) "9000000000009124"
string(16) "9000000000009138"
string(16) "9000000000009142"
string(16) "9000000000009177"

The output is always correct. Well. And what if we improve it by one zero digit?

var_dump(sprintf('%.0f',  900000000000009051));
var_dump(sprintf('%.0f',  900000000000009101));
var_dump(sprintf('%.0f',  900000000000009124));
var_dump(sprintf('%.0f',  900000000000009138));
var_dump(sprintf('%.0f',  900000000000009142));
var_dump(sprintf('%.0f',  900000000000009177));

string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009088"
string(18) "900000000000009216"

Interestingly this are factors of 16 again – even more imprecise.

And now lets solve this mystery:

var_dump(strlen(sprintf('%.0f',  9000000000009051)));

Guess what the result is:

int(16)

Wow. That means floating point numbers basically work precisly down to 16 digits in PHP (if you exceed this magical numbers it is getting imprecise).

So what has happend to your calculation?

It was rounded to the next 16 at the end as you exceeded the number of 16 digits by one.

More information on floating point precision in PHP can be found in dozens of questions on Stackoverflow and of course in the PHP manual.

To answer how to make your code work correctly:

If you want your numbers to be precise do not convert to float. With integers for instance higher calculations should be possible.

If you exceed the number of 16 digits also simple arithmetic like -1 or +1 will fail (at least in the current version of PHP as it seems).

3

solved Do arithmetic operations on FLOAT numbers correctly in PHP