2

After spending an hour trying to find out why a PHP script gave me incorrect output, it turned out that a loop ran one iteration short in one particular case.
To explain what is going on: a version number (2-digits, e.g. 1.5 or 2.0) is read from an XML-attribute and is multiplied by 100. The script later iterates over a defined range of version number multiples.

It turns out that 410 == 409, which gives a funny surprise if you compare a counter that increments in steps of 10 against that value.

Which brings me to my question: Am I fundamentally understanding something wrong? Certainly, 4.1, 100, and 410 should all be well-representable as float and should be well-convertible to int without rounding errors?

However, on my system (with PHP 5.3.2 CLI, Zend Engine 2.3.0), the following test case

<? $a = 100 * 4.1; $b = (string) $a; $c = (int) $a; $d = (int)(string) $a; var_dump($a); var_dump($b); var_dump($c); var_dump($d); ?> 

outputs:

float(410) string(3) "410" int(409) int(410) 

I am now doing a (int)(string) conversion which works, but this is kind of a nasty hack that isn't pretty and doesn't quite feel right.
Is there a better (correct, no-hack) solution to get a precise result?

2
  • 1
    possible duplicate of PHP typecasting float->int Commented Apr 13, 2011 at 14:42
  • 0.1, or 1/10, is not expressible exactly in binary. Rather like 1/3 in decimal. Commented Apr 13, 2011 at 14:50

6 Answers 6

3

You usually can not see, if a value is well-convertible into a float just by looking at it. Read The warning in the manual for an example.

Sign up to request clarification or add additional context in comments.

3 Comments

I'm aware that floating point numbers cannot represent every number, but numbers with a mere 2 and 3 decimal digits respectively, should be representable with 52 mantissa bits, in my understanding. It's not like I'm complaining that 100.00001*41000000000 gives an incorrect result :-)
@Damon Rational numbers in decimal are not necessarily rational in binary. 0.5 base 10 = 0.1 base 2 That's easy. 0.7 base 10 = 0.101100110011... base 2 Thus, inherent value loss, since converting that back to base 10 yields 0.6999999 or so. "It doesn't look too bad" doesn't count for anything. :-)
@Damon: No, what you're doing is more like complaining that 1/3 gives you an incorrect result...
3

Your problem is in the first line:

$a = 100 * 4.1; 

The float literal 4.1 does not, in fact, have the value 4.1, and so the result of multiplying it with 100 is not 410 but a little less. Apparently, casting to string rounds up, but casting to int rounds down.

To get the decimal math you expect, use the BC Math functions.

2 Comments

After repeating the "but it does..." mantra a million times, I'll agree that it doesn't. :-) On a different note, since you mentioned int and string are apparently rounding differently and the initial conversion to float is secretly done by the interpreter converting a string, I guess that the solution of casting back to string again isn't that bad after all.
@Damon - Much better than converting to string (not a cheap operation) and then casting to int, would be to use the round() function.
3

You can use BC Math Functions to get predictable results of float multiplication

http://php.net/manual/en/ref.bc.php

intval(4.1 * 100) === 409 bcmul(4.1, 100) === '410' 

Comments

2

Try using intval

$c = intval($a); 

Edit:

Try rounding it using normal rounding:

$c = round($a, 0, PHP_ROUND_HALF_UP); 

2 Comments

Doesn't work for me: $a = 100 * 4.1; var_dump(intval($a)); int(409)
Same result for intval, sadly.
1

The multiplication with 4.1 will return a float, as you have seen. Casting a float to int will do something like floor(), that means, if the result was 409.999... it will return 409. If you want to work with integers you should either not mix float and integer values in a calculation, or you should use round() afterwards to get the nearest integer.

Possible values for an exact representation would be 0.5, 0.25, 0.125, the values you can get with divisions by 2, since the value is stored in the binary system.

Comments

0

I've used this to solve the problem:

round($a); 

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.