3

PHP seems to round incorrectly when using (int) to cast variables. Why?

$multiplier = 100000000;
$value = 0.01020637;
echo (int)($value*$multiplier);

Output: 1020636. (unexpected output)

$multiplier = 100000000;
$value = 0.01020637;
echo ($value*$multiplier);

Output: 1020637. (Expected correct output)

Edit: it gets even worse...

$multiplier = 100000000;
$value = 0.01020637;
echo $temp = ($value*$multiplier);
echo '<br/>';
echo (int)$temp;

Output:

1020637

1020636

bvpx
  • 1,227
  • 2
  • 16
  • 33
  • What if you try (int)($value*$multiplier + 0.5); – CAPS LOCK Dec 28 '13 at 22:02
  • 1
    "`Never cast an unknown fraction to integer, as this can sometimes lead to unexpected results.`" from the PHP Manual on Integer. Can that be the cause? – display-name-is-missing Dec 28 '13 at 22:04
  • 1
    What is an "unknown fraction"? In my program, the `($value*multiplier)` expression will *always* resolve to an integer. Would switching it to read `($value*$multiplier)(int)` help? Or simply `$temp = ($value*$multiplier); $intVal = (int)$temp)` might cause expected results? – bvpx Dec 28 '13 at 22:05
  • @bvpx I have no idea, to be honest. But I did find that this link, describing a very similar problem like yours, check it out: http://php.net/language.types.float – display-name-is-missing Dec 28 '13 at 22:10
  • http://www.php.net/manual/en/language.types.float.php#warn.float-precision – Royal Bg Dec 28 '13 at 22:12

5 Answers5

2

Things can get hairy when you're dealing with floats, floating point math (and problems involved) are well understood, but can crop up when you're not expecting them. As seems to have happened here. You could read up on the rules extensively, or use language provided tools when handling floating point arithmetic.

When you care about the precision involved you should use the bcmul() function. It's an "optional" extension, but if you care about precision it starts being required rather quickly.

Example:

multiplier = 100000000;
$value = 0.01020637;
echo (int)($value*$multiplier);
echo "\n";
echo bcmul($value, $multiplier, 0);

Sample: http://ideone.com/Wt9kKb

preinheimer
  • 3,712
  • 20
  • 34
  • It should be noted that bcmath is not part of the PHP core functions and not all servers have it installed. – Machavity Dec 28 '13 at 22:21
  • Is there a way I can do this without bcmath? Currently my server doesn't have it installed. Perhaps I can find a library that contains just the bcmul function and include it in my project? – bvpx Dec 28 '13 at 22:23
  • I doubt you'll find a great userland solution here. I might start politely pestering your sysadmin to install the extension. – preinheimer Dec 28 '13 at 22:58
1

PHP (especially in 32 bit builds) has problems with floating point numbers. This is why casting float into int can have unpredictable results. See PHP Integer page for more detail. Basically, you're getting tiny imprecisions in the math and that can cause serious problems when trying to do something like ceil()

If you really need the numbers converted to int I would suggest you round the numbers first

$multiplier = 100000000;
$value = 0.01020637;
$temp = round($value*$multiplier);
echo $temp . '<br/>' . (int)$temp;

This works by truncating off the small floating point errors. While bcmath can also do the truncation, it's not part of PHP core and not a good overall solution. Your best bet is to write a rounding routine yourself that can return the precision you're looking for. In the project I work on, that was what we did. We wrote our own rounding function and it fixes the problems you'll run into. Without knowing the specifics of what you're trying to do it's hard to say if that's what you need but it's how we did it without bcmath.

Machavity
  • 30,841
  • 27
  • 92
  • 100
  • I have a gut feeling that using `round()` will cause more problems than it will solve... – bvpx Dec 28 '13 at 22:24
  • I wish I could tell you differently but there's no other way to get a clean number than to round it. bcmath is a hard truncation to ensure you don't get those weird precision problems. Do you know how much precision you need? – Machavity Dec 28 '13 at 22:30
  • I'm a bit unsure how to answer "how much precision I need", but the numbers I am working with will range between 10000000 and 0.00000001, which all need to be multiplied by 100000000 and stored into a database correctly. I don't want to use bcmath if it has to be installed on the server, but it's extremely important that no incorrect values are stored into the database. – bvpx Dec 28 '13 at 22:37
  • The base problem is your number is being stored in memory as something like `1020636.99999992734834`. There's just no other options available. I still think you could write a function that would do what you want but it's your program. Also, if precision is that important, going to a 64 bit build of PHP would afford you less errors in the first place. – Machavity Dec 28 '13 at 23:13
  • I like the idea of writing my own routine to do this calculation, since it only happens in two or three places and it is the same every time. I'm assuming the routine you wrote didn't involve `round` - what methods did you use instead? – bvpx Dec 28 '13 at 23:31
  • We actually had need of something that would always round up properly, even with negative numbers. It involved `ceil` and `floor`, which are close cousins of `round`. I don't think there's anything from it you could apply here, sadly, or I would post it. – Machavity Dec 29 '13 at 00:24
  • Where `bcmul` is not available this works (in my experience) – Matthew Savage Nov 26 '14 at 01:01
0

The problem you're seeing is the following: When multiplying two numbers like this:

$mulitply = 0.1 * 100;

You are not multiplying exactly 100 with 0.1, but with with 0.09999999998... And when it comes to (int), it converts numbers like 4.999 to 4, so your result 1020636.999999999 becomes 1020636 when counting with (int).

display-name-is-missing
  • 4,424
  • 5
  • 28
  • 41
  • How can I solve this when precision is of utmost importance? – bvpx Dec 28 '13 at 22:21
  • @bvpx I would recommend `BC Math Functions` (http://www.php.net/manual/en/ref.bc.php, in your case you want to look specifically for the `bcmul()` function) or `gmp_mul()` (http://www.php.net/manual/en/function.gmp-mul.php). – display-name-is-missing Dec 28 '13 at 22:24
  • @bvpx I noticed you asked about a solution without the need to install something else to PHP. I don't know of any functions like that included in PHP from start, I'm affraid. – display-name-is-missing Dec 28 '13 at 22:31
  • @bvpx Also, if it's not necessary to do this calculation server-side, I would strongly recommend using plain JavaScript. Using somehting like simply: `var multiplied = var_1 * var_2` doesn't give a rounded up answer automatcally and it gives you a whole 10 decimals. – display-name-is-missing Dec 28 '13 at 22:36
  • The application is a small PHP program that is distributed to many computers and runs server-side only, so I need to make it as "bare-bones" as possible to ensure no server it runs on has the issue of not having bcmath installed but also is able to store these values correctly. – bvpx Dec 28 '13 at 22:41
0

bcmul allows for higher precision

$test =  (int) bcmul('100000000', '0.01020637');
echo $test

returns the correct answer.

Vasilis
  • 2,721
  • 7
  • 33
  • 54
-1

To round floats in PHP you should use the round() function. Just casting to an integer does not round the value correctly.

First argument is which float (the result of your calculation in this case) to be rounded, second is optional, and specifies the amount of decimals (aka precision) being returned. There is also a third argument, controlling the mode. These can be PHP_ROUND_HALF_UP, PHP_ROUND_HALF_DOWN, PHP_ROUND_HALF_EVEN or PHP_ROUND_HALF_ODD.

Example from php.net/round:

<?php
    echo round(3.4);         // 3
    echo round(3.6);         // 4
    echo round(3.6, 0);      // 4
    echo round(1.95583, 2);  // 1.96

    // With the third element, "mode"
    echo round(9.5, 0, PHP_ROUND_HALF_UP);   // 10
    echo round(9.5, 0, PHP_ROUND_HALF_DOWN); // 9
    echo round(9.5, 0, PHP_ROUND_HALF_EVEN); // 10
    echo round(9.5, 0, PHP_ROUND_HALF_ODD);  // 9
?>

An example for your code (live example):

<?php
    $multiplier = 100000000;
    $value = 0.01020637;
    echo intval(round($value*$multiplier)); // Returns 1020637
?>
Jeremy Karlsson
  • 1,059
  • 3
  • 17
  • 27