Why my numbers are not equal PHP?
Floating point precision
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
https://www.php.net/manual/en/language.types.float.php0.1
or0.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 return7
instead of the expected8
, since the internal representation will be something like7.9999999999999991118...
.
Example
We already know that there are some technical issues which not allow to store precise value in base 2 and conversion to binary representation. Let see some examples of that odd behaviour.
<?php
(0.1+0.7)*10; // PHP8 returns float(7.999999999999999) | PHP7 returns float(8)
(0.1+0.7)*10 == 8; // returns false in both PHP7 & PHP8
round((0.1+0.7)*10); // returns float(8)
round((0.1+0.7)*10) == 8; // returns bool(true)
You would expect that in PHP7 when you get float(8) be equal to 8, but it’s not true. In php8 this was updated in a way that the quotation is no longer returns 8, but instead it returns 7.999999999999999. See the test example below:
<?php
var_dump((0.1+0.7)); // PHP7.4.30 & PHP5.6.40 returns float(0.8) but in PHP8.* returns float(0.7999999999999999) which is expected output since comparison against 0.8 would fail in both php versions 7 & 8.
var_dump(round((0.1+0.7)) == 0.8); // 7.4.30, 5.6.40& PHP8.* returns false
var_dump(round((0.1+0.7), 2) == 0.8); // 7.4.30, 5.6.40& PHP8.* returns true
var_dump(round((0.1+0.7), 2) * 10 == 8); // 7.4.30, 5.6.40& PHP8.* returns true
Precise calculation in PHP using GMP & BCMath
((bcadd($a, $b, 2) * 10) == 8); // returns true
When dealing with floating | double | real (in PHP those are the same). Use special build in GMP or BCMath library for float precise calculation. To see all available functions, click here. Note that in some cases, GMP gives better performance vs BCMath.
Notice that those libraries comes from php extensions. You’d either need to enable them in your php.ini, or install them if you don’t have them on your server. If that’s OK, you can add ext-gmp
in composer.lock file.
You could run your script based on whats available and prioritizing GMP over BCMath falling back to default PHP Math lib like so:
<?php
if (extension_loaded('gmp')) {
// return new ...\GMP();
} elseif (extension_loaded('bcmath')) {
// return new ...\BCMath();
} else {
// return new ...\PHPMath();
}
For quick troubleshoot, you could run this instead:
<?php
$a = '1.234'; $b = '5';
if (extension_loaded('gmp')) {
$sum = gmp_add("123456789012345", "76543210987655");
echo gmp_strval($sum). PHP_EOL; // 200000000000000
} elseif (extension_loaded('bcmath')) {
echo bcadd($a, $b). PHP_EOL; // 6
echo bcadd($a, $b, 4). PHP_EOL; // 6.2340
} else {
echo $a + $b; // 6.234
}
Storing and calculating a price in PHP & MySQL
Also note that you do not want or need to use them for every calculation ever. Only use them in places which do requier this precision so perhaps price calculation. Also note that you should store them in database as decimal(8,2)
or convert them to integers.
Seen this in payment system, which required to convert comma separated price to full integer.
1,34$
would become 134
0,3$ + 0,4$
would become 30+40
Similarly, you could store price as integers instead of decimal in MySQL database. The benefit is that all arithmatic operations in the database and storage happens using integers rather than decimals. Which means they take up less space and operations are faster.
You can also avoid some floating point errors that can sometimes creep in.
At the time of displaying the price, simply divide by 100
https://www.php.net/manual/en/language.types.float.php
https://softwareengineering.stackexchange.com/questions/127512/when-must-arbitrary-precision-arithmetic-functions-be-used-in-php