fastsol Posted January 5 Share Posted January 5 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? Quote Link to comment Share on other sites More sharing options...
Solution requinix Posted January 5 Solution Share Posted January 5 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. Quote Link to comment Share on other sites More sharing options...
mac_gyver Posted January 5 Share Posted January 5 instead of doing math, just explode the string on the '.' character, fill the cents part to two places with trailing '0' characters, and concatenate the two pieces together. Quote Link to comment Share on other sites More sharing options...
fastsol Posted January 5 Author Share Posted January 5 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? Quote Link to comment Share on other sites More sharing options...
fastsol Posted January 5 Author Share Posted January 5 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. Quote Link to comment Share on other sites More sharing options...
mac_gyver Posted January 5 Share Posted January 5 you can also use the BC math functions, which expects strings as input and operates on each character as a bcd digit, like a hand held calculator works. Quote Link to comment Share on other sites More sharing options...
fastsol Posted January 5 Author Share Posted January 5 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. Quote Link to comment Share on other sites More sharing options...
kicken Posted January 6 Share Posted January 6 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; 1 Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.