Question

As input, I want to accept any of the following: "$12.33", "14.92", "$13", "17", "14.00001". As output, I want 1233, 1492, 1300, 1700 and 1400 respectively. This is apparently not as easy as it looks:

<?php
$input = '$64.99';  // value is given via form submission
$dollars = str_replace('$', '', $input);  // get rid of the dollar sign
$cents = (int)($dollars * 100) // multiply by 100 and truncate
echo $cents;
?>

This outputs 6498 instead of 6499.

I assume this has to do with inaccuracies in floating point values, and avoiding these is the whole reason I'm converting to integer cents in the first place. I suppose I could use logic like "get rid of the $ sign, check if there's a decimal point, if so, check how many characters there are after it padding to two and truncating after that then remove the period, if there wasn't one append two zeros and hope for the best" but using string operations for this seems ridiculous.

Surely taking a monetary value from a form and storing it as cents in a database is a common use case. Surely there is a "reasonable" way of doing this.

Right? .....right? :<

Was it helpful?

Solution

Consider using the BC Math extension, which does arbitrary-precision math. In particular, bcmul():

<?php
$input = '$64.99';
$dollars = str_replace('$', '', $input);
$cents = bcmul($dollars, 100);
echo $cents;
?>

Output:

6499

OTHER TIPS

$input[] = "$12.33";
$input[] = "14.92";
$input[] = "$13";
$input[] = "17";
$input[] = "14.00001";
$input[] = "$64.99";

foreach($input as $number)
{
    $dollars = str_replace('$', '', $number);
    echo number_format((float)$dollars*100., 0, '.', '');
}

Gives:

1233
1492
1300
1700
1400
6499

Watch out for corner cases like "$0.125". I don't know how you would like to handle those.

Ah, I found out why. When you cast (int) on ($dollars*100) it drops a decimal. I'm not sure WHY, but remove the int cast and it's fixed.

Do not directly convert float to integer.
Convert float to string, then convert string to integer.

Solution:

<?php
$input = '$64.99';
$dollars = str_replace('$', '', $input);
$cents = (int) ( (string) ( $dollars * 100 ) );
echo $cents;
?>

Explained:

<?php
$input = '$64.99';  // value is given via form submission
$dollars = str_replace('$', '', $input);  // get rid of the dollar sign
$cents_as_float = $dollars * 100;  // multiply by 100 (it becomes float)
$cents_as_string = (string) $cents_as_float;  // convert float to string
$cents = (int) $cents_as_string;  // convert string to integer
echo $cents;
?>
$test[] = 123;
$test[] = 123.45;
$test[] = 123.00;
$test[] = 123.3210123;
$test[] = '123.3210123';
$test[] = '123,3210123';
$test[] = 0.3210;
$test[] = '00.023';
$test[] = 0.01;
$test[] = 1;


foreach($test as $value){
    $amount = intval(
                strval(floatval(
                    preg_replace("/[^0-9.]/", "", str_replace(',','.',$value))
                ) * 100));
    echo $amount;
}

Results:

12300
12345
12300
12332
12332
12332
32
2
1
100

Remove the dollar sign and then use bcmul() to multiply.

The issue arises because casting to an int performs truncation instead of rounding. Simple fix: round the number before casting.

<?php
$input = '$64.99';
$dollars = str_replace('$', '', $input);
$cents = (int) round($dollars * 100);
echo $cents;
?>

Output: 6499

Longer explanation:

When PHP sees the string "64.99" and converts it to a (double-precision) floating point, the actual value of the floating point is:

64.9899999999999948840923025272786617279052734375

This is because the number 64.99 is not exactly representable as a floating point, and the above number is the closest possible floating point to 64.99. Then, you multiply it by 100 (which is exactly representable), and the result becomes:

6498.9999999999990905052982270717620849609375

If you cast it to an int, it would truncate this number, and therefore you get the integer 6498 which is not what you want. But if instead you round the floating point first, you exactly get 6499 as a floating point, and then casting this to an int gives you the expected integer.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top