On Twitter: @JamesFirth and @s_r_o_c (post feed)

Got a tip? tip@sroc.eu

Wednesday, 12 January 2011

Amazing PHP decimal handling/formatting "bug" - sorry, nuance, and explanation

Apologies, this blog doesn't normally delve into the underpants of software development, but whilst my former blog - software psyche - remains in hiatus awaiting a major revamp I need to put this somewhere...

I discovered what seems to be a pretty fundamental bug in PHP today.  It's not a vulnerability but a problem that can lead to incorrect rendering of basic decimal values, and that's pretty serious is you're business revolves around building data applications used on high profile projects by valuable business partners.

UPDATE 14:22: Thanks to @eddm and @thatdavidmiller this is not a bug! But it is something that PHP programmers should be aware of (and, embarrassingly, as an ex-assembler coder myself (Z80 and MC68000) I should have already known the answer.  Explanation below.

Try executing the following line of code:
printf( "%d", (0.58*100));
It's pretty clear you should get the result 58But I get 57!  I tweeted this earlier today and got a couple of my followers reporting the same anomaly.  Thanks to @Jldb and @JimAnning for corroboration here and here.

The problem appears even if 0.58 is the value of a variable, and the bug can prove highly embarrassing for instance when important values need to be from a database and rendered accurately to screen.

There are workarounds, like using "%s" or "%f", but "%d" *should* work!


PHP attempts to hide the binary element of computing from its users, but inevitably values end up being stored as binary.  Non-integer numbers are stored as floating point values.

Due to the fundamental way CPUs store decimal data, not every value has an exact corresponding floating point value.  This allows a very wide range of values, from tiny to huge, to be represented by a limited number of binary bits.  When an exact value is not available, the nearest floating point value is used.

So back to the "bug".  0.58 is not an integer, so it must be stored as a floating point value.  The nearest available is actually 0.5799999...

0.5799999... * 100 is 57.99999...

When trying to display 57.99999... as decimal, the printf formatter quite correctly strips the decimal point and gives... 57!.

Lesson:  don't use printf("%d") on anything other than basic integers.

So far the bug appears in the following OS/Releases:

PHP 5.2.13 (64-bit) on Fedora 11 (Pentium D CPU)
PHP 5.1.6 (64-bit) on CentOS 5.5
PHP 5.2.6 on OS X (32-bit)
PHP 5.2.11 on OS X (64-bit)


No comments:

Post a Comment

Comments will be accepted so long as they're on-topic, do not include gratuitous language and do not include personal attacks or libellous assertions.

Comments are the views of the commentator and not necessarily the view of the blog owner.

Comments on newer posts are not normally pre-moderated and the blog owner cannot be held responsible for comments made by 3rd parties.

Requests for comment removal will be considered via the Contact section (above) or email to editorial@slightlyrightofcentre.com.