Floor function not working - php

Given the following cod:
$number = 1050.55;
var_dump($number - floor($number));
Why does the above code returns the following result?
float(0.54999999999995)
I want a fixed value like 0.55 in this case. Can you help me please?

Floating point operations are not precise and the remainder errors are common.
If you know, what is your desired precission (eg. two digits after the dot), you can use round() function on the result.
In this case this will be:
$number = 1050.55;
var_dump(round($number - floor($number), 2));

For most floats, binary can only approximately represent the correct number. The rule is to perform floor(), ceil() or fmod() last in a series of calculations. At least only do integer math after you use them. If you cast an int to a float, as in your code, then floor() is not going to behave has you expect.
Use printf() when printing floats. Its conversion routines usually do a much better job and give you the answer you expect when truncating floats.
EDIT: Or, to be more exact, printf() works on the decimal character representation of the number when deciding where to truncate so you don't get any weird, unspecified, binary/decimal conversion artifacts.

See this question. While that is about java and you're asking about PHP the math is the same.

Related

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?

Is this floating point behavior or a bug in PHP?

CLARIFYING: This isn't asking why I'm getting rounding errors. I understand this is a mistake or an oversight. The question asks why it prints as whole in the first var_dump, but casting acts as if it were 57916.9repeating and truncates said .9repeating.
The following occurs:
You take a string (or float -- does not matter) that contains the value 579.17 and multiply it 100. It var_dumps the expected 57917. Not 57916.99999999999999999999999 or similar. var_dump should not be rounding anything as a debugging function in my opinion. It may have to truncate, but rounding is unexpected in a debugging function.
However, if one then casts that to an integer, you get an unexpected 57916 from var_dump.
I'm aware of issues with floating point numbers, but the act of casting a floating point number that prints as exactly 57917 in PHP apparently effectively subtracts 1. This is a very small number.
This only appears to happen for some numbers, such as 579.17. It does not occur for others I've tested. All we're doing is multiplying a number by 100 to send to an API that expects cents. The API library understandably casts to integer since the API doesn't accept fractional cents.
Test case:
php -r '$n = ("579.17" * 100); var_dump($n, (int)$n);'
Output:
float(57917)
int(57916)
Environment:
x86-32,
x86-64 both.
var_dump uses precision from php.ini to display float value. You could raise it to see what happens.
php -r 'ini_set("precision", 20); $n = ("579.17" * 100); var_dump($n, (int)$n);'
// double(57916.999999999992724)
// int(57916)
Also. There is no matter x86 or x64. PHP uses 64 bits for floats.
http://php.net/manual/en/language.types.float.php
Use round() instead of int(). The actual value of 579.17 * 100 is something like 57916.99999. var_dump() shows this as 57917, but when you use int() it truncates the fraction. Using round() will go to the nearest integer, rather than always truncating down.
I believe this is because hardware cannot truly and accurately express floating point numbers. So what appears as 579.17 is actually more like 579.16999999. So when you multiply it and cast it as an int it truncates the decimal leaving you with 57916.

Strange number conversion error in PHP

How come the result for
intval("19.90"*100)
is
1989
and not 1990 as one would expect (PHP 5.2.14)?
That's because 19.90 is not exactly representable in base 2 and the closest approximation is slightly lower than 19.90.
Namely, this closest approximation is exactly 2^-48 × 0x13E66666666666. You can see its exact value in decimal form here, if you're interested.
This rounding error is propagated when you multiply by 100. intval will force a cast of the float to an integer, and such casts always rounds towards 0, which is why you see 1989. Use round instead.
You can also use bc* function for working with float :
$var = bcmul("19.90", "100");
echo intval($var);
intval converts doubles to integers by truncating the fractional component of the number. When dealing with some values, this can give odd results. Consider the following:
print intval ((0.1 + 0.7) * 10);
This will most likely print out 7, instead of the expected value of 8.
For more information, see the section on floating point numbers in the PHP manual
Why are you using intval on a floating point number? I agree with you that the output is a little off but it has to do with the relative inprecision of floating point numbers.
Why not just use floatval("19.90"*100) which outputs 1990
I believe the php doc at http://de2.php.net/manual/en/function.intval.php is omitting the fact that intval will not deliver "the integer value" but the integer (that is non-fractional) part of the number. It does not round.

Is casting to float destructive?

In PHP, I know we shouldn't do math on floats without things like bcmath, but is the mere act of casting a string to float destructive?
Will expressions like (float)'5.111' == '5.111', always be true? Or will the cast itself change that to something like 5.1110000000000199837 as the number is converted?
The main reason is, just as I use (int) to escape integer values going into a database, I would like to use (float) in the same way, without having to rely on quotes and my escape function.
NO, Casting to a float is almost always destructive.
In your example, 5.111 represented in binary is:
101.00011100011010100111111011111001110110110010001011010000111001...
A float would store 23 digits:
101.0001110001101010011
(5.1109981536865234375)
A double would store 52 digits:
101.0001110001101010011111101111100111011011001000101
(5.1109999999999988773424774990417063236236572265625)
In this case, there wouldn't be a difference. However, in larger numbers, it can affect what you display.
For example:
1025.4995
double:
10000000001.011111111101111100111011011001000101101
(1025.499499999999898136593401432037353515625)
float:
10000000001.011111111101
(1025.499267578125)
You can see the precision starts to drop off dramatically after around 8 digits.
The double would round to 1025.4995 whereas the float would be 1025.4993
You shouldn't use (int) to escape integer values. Use a parametrized query and set the type of your input to 'int'. A much better way!
for an example in mysql/php see:
http://us.php.net/manual/en/mysqli.prepare.php
It depends on whether or not the fractional part can be represented exactly in binary (see Fractions in binary). For example, 0.5 has an exact binary representation but 0.1 does not. If the number does not have an exact representation, you are likely to see a different result when printing it again.

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