-2

I am working on a PHP-script that handles monetairy amounts, and therefore needs to be exact with 2 decimals. To do this, I convert the user-input to a number by multiplying it with 100, and then casting it to int. This works fine, untill I recently discovered a number that increases by 1 when cast to int.

the malfunctioning code:

$number = (int)(str_replace(',','.',$_POST["input"])*100);

The number that gives problems is 2509,22 (I live in the Netherlands, so we use comma's for decimals, hence the str_replace in the above line of code). This value creates the integer $number 250921, which is obviously 1 too low. I know that int has limits, but this number is well within those limits as far as I'm aware...

Discoverer
  • 51
  • 1
  • 6
  • Why do you need to make it an int? What is the purpose of doing this? What happens next to this int? – Andreas Oct 17 '17 at 18:59
  • I use it to make sure the input does not contain more than 2 decimals. As stated, it is financial information, and this is to prevent the user from inputting fractions smaller than possible in financial systems. – Discoverer Oct 17 '17 at 19:02
  • 2
    Use bcmath instead. Never operate on floats unless you are absolutely sure of the output and judging by your code I’m sure you are not. You can read more about floating number precision here http://php.net/manual/en/language.types.float.php – Mike Doe Oct 17 '17 at 19:07
  • If you're working with financial values, then you need to be aware of the limitations of floating point representation on computers, and the differences between casting to int, floor, round and ceil – Mark Baker Oct 17 '17 at 19:08
  • Thanks Mark Baker! I guess I was not aware of the fact this first created a floating point number, and the exact implications of that. – Discoverer Oct 17 '17 at 19:11

2 Answers2

1

When you multiply the string by 100 you get a float and its representation is not always what you expect, in this case 250921.99999999997. See it with:

echo serialize(str_replace(',','.','2509,22')*100);

Then you cast to an integer which trucates the fraction to get 250921. See Is floating point math broken?.

The solution would be to remove the comma and use as is and optionally cast to an integer:

$number = (int)str_replace(',', '', '2509,22');

For the issue of users entering too many fractional numbers, you should either use two inputs, one for whole number and one for fraction and/or restrict/validate that the inputs are correctly formatted. However, you can format the number first:

echo $number = (number_format(str_replace(',', '.', '2509,22'), 2, '.', '')*100);
AbraCadaver
  • 78,200
  • 7
  • 66
  • 87
  • Okay, thank you! and integer just rounds it down I presume. Do you know what type of casting I should be using to get the right number? – Discoverer Oct 17 '17 at 19:04
  • 1
    Because when a user taps a button one too many times (so he puts 2509,222 in the input-box), the number that will be stored will be way too high. – Discoverer Oct 17 '17 at 19:10
  • @Discoverer 1) Reject the input if there is more than two digits after the decimal (or comma :) 2) Provide verification of the value to the client before proceeding and/or allow a method of undo. The user may have inadvertently used the "wrong" separator *and* typed in an extra number. – user2864740 Oct 17 '17 at 20:00
0

You can use regex to match the int and zero - two decimals.
This will not do any conversions and nothing is multiplied or casted.
It will be treated as a string and nothing changes but the number of decimals.

$input = "2509,2222222";
// Regex number comma number (zero - two)
Preg_match("/(\d+),*(\d{0,2})/", $input, $m);
Unset($m[0]); // remove full match

Echo implode("", $m); // implode parts

https://3v4l.org/MjcYV

Andreas
  • 23,610
  • 6
  • 30
  • 62
  • The problem is that figures should also be allowed to entered with a dot instead of a comma. Of course you can also remove that, but when a user enters a wrong amount (say 2509,222 by tapping the button one time too many) then the amount would be 2509222, which is way too high. – Discoverer Oct 17 '17 at 19:07
  • That could work indeed. Personally I think I will be using the solution David Bubenik suggested, as this also allows for figures to be entered without any decimals whatsoever. The users are not all very precise in their ways, so I am trying to make this as flexible as possible, but thanks for the suggestion! – Discoverer Oct 17 '17 at 19:14
  • @Discoverer dude. When you post a question post all information needed for us to help you. You keep adding information and it's really annoying. – Andreas Oct 17 '17 at 19:17
  • I did not edit the question, so I think you are referring to the concerns I have with how numbers are (or can be) entered. Sorry about this, I'm oviously new, but since I used $_POST int the question, I think I thought that sanitation of the input was so standard in coding that I forgot to mention it earier. Sorry about that. – Discoverer Oct 17 '17 at 19:23
  • @Discoverer I have updated with a method that will always work no matter what you input (unless it's not a number). And it won't make any conversions that may screw up the numbers – Andreas Oct 17 '17 at 20:18