Jump to content

Integer precision issue


fastsol
Go to solution Solved by requinix,

Recommended Posts

So I have this very simple integer/float conversion method to convert basically a money value like 573.06 to cents.  The 573.06 would come in as a string from a submitted form value.  I would run it through the following code and it was coming out the wrong value as 57305 not 57306 as expected.

function convertToMoneyInteger($input)
{
	return (int)($input * 100);
}

After some fiddling, I found a way to make it work but I'm not sure it's the best or most accurate way even after running multiple tests on random values. This is the new method that seems to work.

function convertToMoneyInteger($input)
{
	return (int)(string)($input * 100);
}

All I did was convert it back to a string before ultimately converting back to an integer.  It just feels wrong or incorrect. 

I have an automated test that runs 106 assertions against the result of the convertToMoneyInteger method and it always comes back green.

public function returns_correct_integer_for_convertToMoneyInteger_function()
    {
        $input = '573.06';
        $this->assertEquals(57306, convertToMoneyInteger($input));

        $input = '573.05';
        $this->assertEquals(57305, convertToMoneyInteger($input));

        $input = '573.07';
        $this->assertEquals(57307, convertToMoneyInteger($input));

        $input = '22.91';
        $this->assertEquals(2291, convertToMoneyInteger($input));

        $input = '34.54';
        $this->assertEquals(3454, convertToMoneyInteger($input));

        $input = '50.00';
        $this->assertEquals(5000, convertToMoneyInteger($input));

        for ($x = 0; $x <= 100; $x++){
            $value = $this->rand_float(20, 600);
            $result = (string)($value * 100); // Only convert to string so it's not identical to the convert function as it would be pointless because it would always return true.
            $this->assertEquals($result, convertToMoneyInteger($value));
        }
    }

    function rand_float($st_num=0,$end_num=1,$mul=100)
    {
        if ($st_num>$end_num) return false;
        return mt_rand($st_num*$mul,$end_num*$mul)/$mul;
    }

Is this accurate enough or is there a better way?

Link to comment
Share on other sites

  • Solution

That's floating-point arithmetic: the computer can't calculate 573.06 * 100 exactly so it comes up with something close. Something like 57305.9999999999986. And if you truncate that, like with an int cast, then you'll just get 57305. In other cases it might come up with 57306.00000000000005.

The solution is super simple: round the number instead of truncating it.

function convertToMoneyInteger($input)
{
	return round($input * 100);
}

Be careful that your $input is never going to have fractional cents or you'll lose them by rounding to 0 digits. If you're concerned that might be possible, round to an appropriate number of decimal places.

Link to comment
Share on other sites

1 hour ago, requinix said:

That's floating-point arithmetic: the computer can't calculate 573.06 * 100 exactly so it comes up with something close. Something like 57305.9999999999986. And if you truncate that, like with an int cast, then you'll just get 57305. In other cases it might come up with 57306.00000000000005.

The solution is super simple: round the number instead of truncating it.

function convertToMoneyInteger($input)
{
	return round($input * 100);
}

Be careful that your $input is never going to have fractional cents or you'll lose them by rounding to 0 digits. If you're concerned that might be possible, round to an appropriate number of decimal places.

Is there a php method I can use in the future to see that decimal precision so I can understand more quickly why it's not doing what I want?

Link to comment
Share on other sites

I won't ever use anything except standard US currency and it will always only be 2 decimal points in cents, so if that means I don't have to worry about "fractional cents" then great.  Otherwise please explain more as to what you mean by that term.

Link to comment
Share on other sites

Using number_format() allowed me to see the precision of decimals but I had to go out to 11+ decimals or it would only show zeros.  Once I hit the 11 decimal all the sudden it showed multiple 9's and other numbers.  So weird how that crap works.

Link to comment
Share on other sites

There are some ini settings that control how PHP rounds the precision of floating point numbers.  There is a higher precision for serialization, so a quick and dirty way of seeing the "raw" numbers would be to serialize it.

echo serialize('573.06'*100);

shows

d:57305.99999999999;

 

  • Like 1
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.