arnaslu
arnaslu

Reputation: 1054

Sanitize currency input

I'm working with currency input. Only two digits after decimal mark should be used. I tried casting input to float and multiplying by 100, which works fine until someone enters more than two digits after decimal mark:

// Returns 6999.8 instead of 6999
$cents = floatval('69.998') * 100;

Then I tried casting result to int, so sequential digits after decimal point are ignored. It solves above problem ('69.998' becomes 6999), but creates a new one with float to integer conversion:

// Returns 6998 instead of 6999
$cents = intval(floatval('69.99') * 100);

I also considered floor(), but it triggers the same float issue as intval().

This is what I'm thinking about using:

$cents = intval((string)(floatval('69.99') * 100));

It works in both cases, but feels like a hack and it's late and my head hurts so maybe I'm missing something obvious here. Is there a better way to do this?

Upvotes: 0

Views: 1595

Answers (4)

pozs
pozs

Reputation: 36244

It's because 69.99 * 100 has a floating-point representation of 6998.9999999* (off: you can check it at a javascript console too). If you want to be precise, you should use a fixed-point number with a php-extension, like BCMath - or, you can write a simple regexp for this specific problem

$amount = '69.99';

if (preg_match('/^(-?\d+)(\.(\d{1,2}))?/', $amount, $matches))
{
    $amount = (int) ($matches[1] . (isset($matches[3]) ? str_pad($matches[3], 2, '0') : '00'));
}
else
{
    $amount = ((int) $amount) * 100;
}

Upvotes: 2

user1726343
user1726343

Reputation:

Is

$cents = intval(round(floatval('69.99') * 100));

what you need?

You can also specify the precision. For example, in your case you mentioned you would like to round the original to two decimal places:

$twodecimal = round(floatval('69.998'),2);//returns a float representation of 70

Be sure to have a look at the big red notice in these docs

Upvotes: 4

Richard Chambers
Richard Chambers

Reputation: 17593

I would recommend that you use an integer to contain a currency value. Using floats can rapidly lead to rounding errors.

In the applications that I have seen, an integer is used with an assumed decimal point. All values are held to the nearest unit of currency such as the cent for US dollars or the Euro. There are currencies which do not have a decimal point and there are a couple that rather than two decimal places have three decimal places.

By manipulating with integers with an assumed decimal place, you can really reduce rounding errors and other issues that can be seen with floating point.

To do conversion, I recommend using string manipulation and removing the decimal point with string manipulation as well as performing a check to ensure that only the desired number of places are entered.

Upvotes: 0

Bob Fincheimer
Bob Fincheimer

Reputation: 18066

$cents = intval(round(floatval('69.99') * 100));

This would get it to the nearest number correctly, this is because of floating pointer precision problems as 69.99 is probably represented in memory to be something like 69.9899999

intval just truncates the remaining parts of the decimal, so 69.989999 * 100 becomes 6998.9999 and gets truncated to 6998

Upvotes: 0

Related Questions