Came across a rather “interesting” peculiarity in PHP, which affects mathematical operations involving floating point numbers. Before getting into the details, let me introduce you to the problem I was trying to help a friend solve. The goal was to take an arbitrary floating point number, let’s say 1.841243, and convert it to a whole number where decimal points became part of the whole, so 1.8432432 would become 18432432. The first solution was a very simple scriptlet, which if C has taught us anything should’ve worked.
PHP:
<?php
$a = 1.8432432;
while ((int)$a != $a) $a *= 10;
?>
This code relies on 2 premises, which are true in PHP:
1) Casting a float to an int, results in a drop of decimal points. (Ex. (int) 1.8432432 => 1)
2) By multiplying value by 10, all decimal places will eventually be gone and (float)$a will equal (int)$a.
While both premises are true, the above code does not work, in fact, it results an unterminated loop, YIKES!
Quick confirmation with C code, included below, re-affirms the fact that there is no problem with the logic.
CODE:
int main()
{
float a = 1.8432432;
while ((int)a != a) {
a *= 10;
}
printf("%d\n", (int)a);
return 0;
}
# prints the expected 18432432
The problem with PHP lies in the infinitesimal “fudge” being added when performing float math. While we see float(123), internally we are actually working with 122.999999999999 or 123.000000000000001, these little seemingly insignificant decimal points make a world of difference. Their presence causes integer value which strips them to be slightly larger or smaller then the supposedly equivalent float, and leads to an unterminated loop.
The solution, a hack really, we came up with was to do something like this:
PHP:
<?php
$a = 1.8432432;
while ($a % 10) $a *= 10;
$a /= 10;
?>
This relies on the fact that the number will not have “0”s inside it and modulus 10 would only return 0, once we’ve multiplied the number by 10 one too many times. This causes the loop to stop and a quick divide by 10 gives us the desired value.
A more universal, but far slower solution would require something along these lines:
PHP:
<?php
$a = 1.8432432;
$a = (float) str_replace(".", "", rtrim(sprintf("%.10f", $a), "0"));
?>