Round to nearest whole number PHP - php

I figure this is easy enough but I am missing something here. Im using Stripe Connect and trying to calculate an application fee (in cents). The problem is my application fee sometimes has a decimal so it is throwing an error. I have tried using round() which gives me and ceil() but Im still getting a decimal and trailing zero in my answer so it returns an error.
$payment = bcmul($request->amount, 100); //112.00 - Convert to cents for stripe becomes 11200
$applicationFee = $payment * 0.021; //235.2 but should be just 235
print_r($applicationFee); //Should be whole number, round and ceil still provide me with decimal number IE 235.0 instead of 235
How can I make sure applicationFee is always a whole number with no decimal and rounded to nearest whole number?

You are correct as is described in the docs for ceil:
The return value of ceil() is still of type float
The method explanation also tells you that a float is returned:
float ceil ( float $value )
The return type for round() is also shown as a float in the docs for round as below:
float round ( float $val [, int $precision = 0 [, int $mode = PHP_ROUND_HALF_UP ]] )
You could use intval() instead as below:
intval($applicationFee);
Or try casting to an int as below:
(int) $applicationFee
If you want to keep your ceil or rounding you can call these first and then cast it as below:
intval(ceil($applicationFee));
(int) ceil($applicationFee);

This should work! (using round() function)
Documentation: Round()
$payment = bcmul($request->amount, 100);
$applicationFee = $payment * 0.021;
print_r(round($applicationFee));

round() should do the trick
$applicationFee = round($payment * 0.021);
You could add more parameters to round and so allow some decimals sill present in the result.
You say you tried it, but I wonder how.
Note
21 is is some countries the VAT percentage (taxes). Be careful how you round those numbers, as the tax office can be strickt
question: according to the docs round() returns a float, as it can also round to 1 or more decimals. But if the 2nd parameter is omitted you should get a whole number. How does a float equal to 235.0 influence the rest of the script?

Php automatically assigns a data type when you don't specifically tell it. For this to work you have to assign a data type that allows you to do the calculations you are trying to do.
Insure you dimension them with enough space for your calculations.
Then as a last step round it off to the precision that you desire and print it out.
It is highly unlikely that if you let PHP select the data type that you will get what you want. You have to carefully select for PHP what data type your variables are instantiated as.

Related

Unnatural round prices

I need to round prices for my client in a non-common way :
120.28 => 120.2
130.23 => 130.2
150.64 => 150.6
The rule is to always keep the nearest lowest 1 precision.
I try to use the round() method but all my tries are useless since this method handle half which i have to ignore.
ceil() and floor() will not work out of the box.
Is a combination of those 3 methods would work ?
I think the solution
0.1 * intval(10 * $price) causes problems when prices are the result of calculations.
$price = 0.70 + 0.10; //0.80 expected
print_r(0.1 * intval(10 * $price)); //0.7
The following solution avoids this error by rounding to 2 decimal places and truncating using a string function.
$price = 0.70 + 0.10; //0.80 expected
$newPrice = substr(sprintf('%0.2f',$price),0,-1);
print_r($newPrice); //0.8
Demo: https://3v4l.org/hFjDL
phpHow about 0.1 * intval(10 * $price)?
What it does is multiply the price by 10, so 120.28 becomes 1202.8. Then it takes integer part using intval(), which is 1202, and then divides it by ten giving 120.2.
See: https://3v4l.org/3qMhd
I think the problem indicated by jspit is relevant, we could compensate for prices that are just a hair under their expected value. Something like this:
0.1 * (int)(10 * $price + 1E-6))
It works, see https://3v4l.org/5YCEu.
The value 1E-6 is chosen with care, not too much or too little.
floating points in PHP have roughly 16 digits of precision and money is usually not more than a few billion. Billions use 9 or 10 digits, subtract that from the 16 we have and you get the 6.
Also, 6 digits should be ample. If an amount is 10.599999 it will still be converted to 10.5. Only when it is even closer, say 10.5999999 will it become 10.6.
You may want to put this point somewhere else. You could decide that 10.555 should be 10.6. Then you choose a value of 0.45. See; https://3v4l.org/YWVI0. The point is that you can exactly choose where you want this point to be, no more surprises.

Safely round numbers in php using strings to avoid evil float

I use bcmath for my calculations and I want to round some numbers to n decimal places. Now, I know enough to avoid floats, but what I'm wondering is if the following example is safe and/or if there are better ways to do it?
$number = '123.456'; // number to round as string
$roundedNumber = (string) round($number, 2); // round and cast
// calculations using bcmath continue here...
I think it is, I've ran some experiments and so far it always returned expected result but I'd like second opinion as I'm not 100% positive that in some particular case casting string to float and then float back to string will not output undesired result.
Or is there a better way to do this?
EDIT: before you answer:
bc* functions do not round when third parameter is specified, they just trim the output.
number_format does not allow selection of rounding mode, so it's out
EDIT: What do I consider safe?
Given the number as string and rounding mode, will the function always output correct/expected result and not be affected by casting to float?
I guess that what I'm being afraid of is following:
I provide number say 12.345 as string to round function, it gets casted as float and then my number isn't 12.345 anymore, it may be 12.345xxxx because we all know how float can be represented internally. I'm afraid of that affecting the rounding output. I believe there will be no harm when I cast to 12.345 to string, it will always be '12.345', not '12.345....' right?

Float to int error

Yesterday I was helping some one and got a weird error which I could not explain to him how it worked.
The code (tested on 3 machines (PHP 5.3 and PHP 5.4))
// (float)65.35
$percentage = round(65.351, 2);
// (float) 6535
$total = $percentage * 100;
// (int) 6534
$int = (int) $total;
What is suspected was that the int value would be 6535 but it ended up being 6534.
Could some one explain what is going on?
You don't actually have 65.35 after the first operation.
>>> '%.20f' % (65.351 - 0.001,)
'65.34999999999999431566'
Either start with an integral value scaled appropriately in the first place, don't attempt to convert the value to an integer, or add a small value before taking the integer value.
This has to do with how floating point (read the warning in this link!) values are stored in memory. Indeed after the first operation you don't have an exact decimal value, but a rounded value. Probably 65.34999999 or so. (The value is stored as a list of bits (0/1))
This is why when talking about money, developers don't store dollars/euros but rather the amount of cents. This way they avoid working with floats that are less precise for decimals, but rather work with integers, that are precise.
Use round instead of int
round($total)
$r=$explode('.',$total);
debug($r);

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?
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
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;
}
$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
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.

PHP money string conversion to integer error

I have a small financial application with PHP as the front end and MySQL as the back end. I have ancient prejudices, and I store money values in MySQL as an integer of cents. My HTML forms allow input of dollar values, like "156.64" and I use PHP to convert that to cents and then I store the cents in the database.
I have a function that both cleans the dollar value from the form, and converts it to cents. I strip leading text, I strip trailing text, I multiply by 100 and convert to an integer. That final step is
$cents = (integer) ($dollars * 100);
This works fine for almost everything, except for a very few values like '156.64' which consistently converts to 15663 cents. Why does it do this?
If I do this:
$cents = (integer) ($dollars * 100 + 0.5);
then it consistently works. Why do I need to add that rounding value?
Also, my prejudices about storing money amounts as integers and not floating point values, is that no longer needed? Will modern float calculations produce nicely rounded and accurate money values adequate for keeping 100% accurate accounting?
If you want precision, you should store your money values using the DECIMAL data type in MySQL.
Your "prejudices" about floats will never be overcome - it's fundamental to the way they work. Without going into too much detail, they store a number based on powers of two and since not all decimal number can be presented this way, it doesn't always work. Your only reliable solution is to store the number as a sequence of digits and the location of the decimal point (as per DECIMAL type mentioned above).
I'm not 100% on the PHP, but is it possible the multiplication is converting the ints to floats and hence introducing exactly the problem you're trying to avoid?
Currency/money values should never be stored in a database (or used in a program) as floats.
Your integer method is fine, as is using a DECIMAL, NUMERIC or MONEY type where available.
Your problem is caused by $dollars being treated as a float and PHP doesn't have a better type to deal with money. Depending on when $dollars is being assigned, it could be being treated as a string or a float, but is certainly converted to a float if it's still a string for the * 100 operation if it looks like a float.
You might be better off parsing the string to an integer "money" value yourself (using a regex) instead of relying on the implicit conversions which PHP is doing.
The code you posted does the multiplication first, forcing a floating point calculation that introduces error, before converting the value to an integer. Instead, you should avoid floating point arithmetic entirely by reversing the order. Convert to integer values first, then perform the arithmetic.
Assuming previous code already validated and formatted the input, try this:
list($bills, $pennies) = explode('.', $dollars);
$cents = 100 * $bills + $pennies;
Your prejudice against floating point values to represent money is well founded because of truncation and because of values being converted from base-10 to base-2 and back again.
Casting does not round() as in round-to-nearest, it truncates at the decimal: (int)3.99 yields 3. (int)-3.99 yields -3.
Since float arithmetic often induces error (and possibly not in the direction you want), use round() if you want reliable rounding.
You should never ever store currency in floating point, because it always get results you don't expect.
Check out php BC Maths, it allow you to store your currency as string, then perform very high precision arithmetic on them.
Instead of using
$cents = (integer) ($dollars * 100);
you may want to try to use:
$cents = bcmul($dollars, 100, 2);
When converting from float to integer, the number will be rounded towards zero (src).
Read the Floating point precision warning.
There's no point in storing money as integer if you enter it through a floating point operation (no pun intended). If you want to convert from string to int and be consistent with your "prejudice" you can simply use string functions.
You can use an arbitrary precision library to divide by 10 (they handle numbers internally as strings), e.g. bcdiv() or gmp_div_q(), but of course, you could have also used it from the beginning for all the math.
Or you can use plain string functions:
<?php
// Quick ugly code not fully tested
$input = '156.64';
$output = NULL;
if( preg_match('/\d+(\.\d+)?/', $input) ){
$tmp = explode('.', $input);
switch( count($tmp) ){
case 1:
$output = $tmp[0];
break;
case 2:
$output = $tmp[0] . substr($tmp[1], 0, 2);
break;
default:
echo "Invalid decimal\n";
}
}else{
echo "Invalid number\n";
}
var_dump($output);
?>

Categories