tags:

views:

154

answers:

4

Boy, this one is really weird. I expect the following code to print 1990, but it prints 1989!

$val = '$19.9';

$val = preg_replace('/[^\d.]/','',$val);
$val = intval($val * 100);

echo $val;

Why on earth is this happening?

Edit: and this code:

$val = '$19.9';
$val = preg_replace('/[^\d.]/','',$val);
echo $val . "<br>";
$val = $val * 100;
echo $val . "<br>";
$val = intval($val);
echo $val;

Prints:

19.9
1990
1989

Why does intval(1990) equal 1989???

+11  A: 

This is a precision issue inherent to floating point numbers in PHP, and lots of other languages. This bug report discusses it a bit, in the context of casting as an int:

http://bugs.php.net/bug.php?id=33731

Try round($val * 100) instead.

Funkatron
Yeah, you got it.
zneak
Okay, `round` gives the expected answer. What implications does using `round` instead of `intval` have?
George Edison
as I understand it, casting the value to an int using `intval()` essentially chops the float value after the decimal instead of rounding it.
Funkatron
Because `round` rounds the number to the closest integer and `intval` simply takes the integer part of the floating point number (1989 in this case) and ignores the rest of it.
animuson
A: 

$val is a floating point number - the result of "19.9" * 100. Floating point numbers are not 100% accurate in any language (this is by design). If you need 100% decimal accuracy for dollar values, you should use integers and perform all calculations using cents (E.g., "$19.90" should be 1990).

too much php
So then... what are you saying?
George Edison
Using integers is not really a good solution at all. Have to deal with amounts larger than 20 million? Oops. Different currencies, not all of which have 100 sub-units? No can do.
Michael Borgwardt
@Michael - PHP doesn't have 64-bit integers? I think different currencies are a non-issue, since you can't compare them to each other directly anyway.
Joel Mueller
@Joel: PHPs integer type is platform-dependant. Could be 64bits, could be 32. You don't get to choose. And all it takes to compare currencies is an exchange rate. But even if you don't compare and only store them, you have to do so with a varying number of fractional digits.
Michael Borgwardt
Sure, but if you're converting different currencies into integers, you just need to know the number of fractional digits for that currency, so you multiply by the right value. The fact that they might not have the same number of fractions is irrelevant, as long as you don't lose track of which currency this particular integer represents.
Joel Mueller
+3  A: 

The usual answer to this kind of question is to read What Every Computer Scientist Should Know About Floating-Point Arithmetic.

R. Bemrose
...which is unfortunately no help at all to people asking this kind of basic questions.
Michael Borgwardt
and not even any help to those of us who produce a lot of code, but aren't mathematicians.
Wade Williams
I saw in another answer that someone had written a simplified version of this document, but I don't remember to what question or whom it was.
R. Bemrose
That was me, see my response for the link :)
Michael Borgwardt
+1  A: 

Why does intval(1990) equal 1989???

Because you're not taking intval(1990). You're taking intval($val * 100) where $val is a number close to, but slightly smaller than, 19.9.

Read The Floating-Point Guide to understand why this is so.

As for how to fix it: don't ever use floating-point values for money. In PHP, you should use BCMath instead.

Michael Borgwardt