1

Im attempting to convert my dollar amount to cents, but it is off by one cent each time. What's the best way to do this without using any php extension?

$amount = 129.95;

$amountInCents = intval(floatval($amount) * 100);

echo $amountInCents; //output: 12994
Parampal Pooni
  • 2,958
  • 8
  • 34
  • 40
  • 1
    Do not use floating point data types to store money values if it is important that the value be accurate. See http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html for more information – Patashu Jun 14 '13 at 02:51
  • To correct the problem just remove intval and floatval and let PHP coerce the types on its own .... the result will be what you expect. – Orangepill Jun 14 '13 at 02:55
  • computers cant even do basic math properly, there's no hope for this world ;) –  Jun 14 '13 at 03:06
  • 1
    `str_replace('.','', (string)$amount);` No one likes you anyway, Math. :-P – Jacob S Jun 14 '13 at 04:21

3 Answers3

4

Because of the inaccuracy of floating point arithmetic and value storage ( http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html ) , floating points cannot exactly represent many values, such as 129.95 - in this case, it is something like 129.9499999, since it cannot exactly store 192.95.

Thus, when you round it down using intval, it sees that you have 12994.999... and rounds down to 12994.

To solve this, you could try

1) if you get the dollars and cents amount as a string such as "$129.95", using string parsing to figure out the dollars and cents as integers

2) adding a very small value like 0.00001 to the float value before converting it to an int, so that if it is only barely under an integer value it is now only barely over it and rounds correctly

3) using round http://php.net/manual/en/function.round.php before using intval so that it is rounded to the nearest integer.

Patashu
  • 21,443
  • 3
  • 45
  • 53
4
$amount = 129.95;
echo (string)(floatval($amount) * 100); // cast it to string
Roman Newaza
  • 11,405
  • 11
  • 58
  • 89
  • Can i then convert it to an int? – Parampal Pooni Jun 14 '13 at 03:05
  • You don't really even need to do the cast, echo will do it for you and you will get the same results because echo coerces the type to a string. As long as you aren't doing any high precision math, php is going to give you sane results. – Orangepill Jun 14 '13 at 03:12
  • Yes, but you don't need to - PHP will it convert automatically for you – Roman Newaza Jun 14 '13 at 03:12
  • If he does not cast it and later use this variable, he might end up with 12994 again: `echo (int)(floatval($amount) * 100)` – Roman Newaza Jun 14 '13 at 03:17
  • 1
    The root of the problem here is that the number at the time of assignment is never truely 129.95. The first statement is where the precision is lost because the parser cannot produce that number accurately in binary. – Orangepill Jun 14 '13 at 03:17
  • im thinking of doing this `intval((string)(floatval($amount) * 100));` – Parampal Pooni Jun 14 '13 at 03:38
1

Not all numbers can be represented with floating point. That's obvious since (for example) there are only 264 possible 64-bit values (less actual values since the encoding calls for things like NaNs and infinities) but an infinite number of floats.

See this answer for further explanation.

So a number like 129.95 may _actually be something like 129.949999999923234 and, when you work out intval(val * 100), you end up with 12994.

One solution is to round the number instead of truncating and most languages will give you numerous ways to do that. Alternatively, you can emulate it with:

intval(val * 100 + 0.5);

Alternatively, you can avoid the problems of IEEE754 floats by not using them. Integer types are probably big enough nowadays that you can just store integral cents directly, where these sorts of errors do not occur (there may be other errors depending on the complexities of your calculations but they'll almost certainly be less than those involved in floating point).

And, if you need the larger range, you may have to opt for a BigInteger-type package.

Community
  • 1
  • 1
paxdiablo
  • 854,327
  • 234
  • 1,573
  • 1,953