Sometimes you need to work with big numbers in PHP (gulp). For example, sometimes 32-bit identifiers are not enough and you have to use BIGINT 64-bit ids; e.g. if you are encoding additional information like the server ID into high bits of the ID.
I had already written about the mess that 64-bit integers are in PHP. But if the numbers you use do not cover 64-bit range fully, floats might save the day. The trick is that PHP floats are in fact doubles, i.e. double-precision 64-bit numbers. They have 52 bits for mantissa, and integer values up to 2^53-1 can be stored exactly. So if you’re using up to 53 bits, you’re OK with floats.
However, there’s a conversion caveat you should be aware of.
On different systems, float is converted to string differently. (I spent a bit of time fighting with it today.) Consider the following script:
|
1 |
<br><?php<br><br>$f = 65536*65536*65536*4; // 2^50<br>$f += 1; // 2^50+1<br>$s1 = "$f"; // the usual way<br>$s2 = sprintf ( "%.0f", $f ); // the "right" way<br>print "s1=$s1 s2=$s2n";<br><br>?><br> |
It prints the following (normally expected) output on x64 box running Linux with PHP 5.1.6 or 5.2.2, and also with PHP 4.4.2 on SPARC64 under NetBSD 3.0:
|
1 |
<br>s1=1125899906842625 s2=1125899906842625<br> |
However 32-bit FreeBSD with PHP 4.4.7 produces different results:
|
1 |
<br>s1=1.1258999068426E+15 s2=1125899906842625<br> |
The same 32-bit FreeBSD box with PHP 5.2.5 starts to emit all digits but still incorrectly:
|
1 |
<br>s1=1125899906842600 s2=1125899906842625<br> |
And PHP 5.2.2 on Windows produces yet another variant:
|
1 |
<br>s1=1125899906840000 s2=1125899906842625<br> |
As you can see float to string conversion is not portable across systems and PHP versions. So if you’re handling large numbers stored as float (especially on 32-bit systems where 32-bit int overflow will implicitly convert to float) and getting strange bugs, remember that string conversion might let you down. sprintf(), on the other hand, is always a friend when it comes to fixing PHP “subtleties” in numeric value handling: it can be used to workaround signed vs. unsigned int issues; it helps with float formatting; always a saviour.
UPDATE: as Jakub Vrana pointed out in the comments (thanks!), it’s the “precision” option set from php.ini which affects the conversion. I’ve played with it a bit; the compiled-in and php.ini-dist values seem to vary across architectures and versions, but setting precision=16 (enough to hold 2^53 in decimal without sign) helped in all cases.
However at the moment the option is neither mentioned in the online documentation, nor explained clearly in php.ini. So this adds another fixing route (ie. you could just set higher precision); but the caveat is still there.
Resources
RELATED POSTS