Is this floating point behavior or a bug in PHP? - 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.

Related

Problem with a floating point value in PHP

I'm having a problem with a floating point value conversion. I have a private property in a class with a value of 317.46. If I multiply this value by 100, I should have the value of 317.46, and that's what seems to be happening, but when i use json_encode it returns another value, slightly smaller. This is the result from the debug console:
$this->valor
317.46
$this->valor * 100
31746
json_encode($this->valor * 100)
"31745.99999999999996"
I known this is due to the way floating points are stored, but in this case I need it to be 'rendered' as a integer, otherwise it will generate an error in the API I'm calling. In other languages I would convert it to a int value, or use a decimal datatype, like in c#, but it's not available in PHP as far as I know.
Use round() to remove the round-off error from floating point representation.
echo json_encode(round($this->valor * 100))
If this is money, it's a good idea to use pennies as the representation in the first place, to avoid problems from converting to and from decimal fractions.

Floor function not working

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.

How to emulate single precision float operations in PHP?

I need to port a simple C program to PHP. Currently we have to start the process and parse it's output. The program is very trivial but it is important for the algorithm to use float as the errors will sum up and the result will be way off.
C example:
#include <stdio.h>
int main( void ) {
printf("%f\n", 123456 * (float)0.99524);
printf("%f\n", 123456 * (double)0.99524);
return 0;
}
PHP example:
<?php
printf("%f\n", 123456 * 0.99524);
?>
The C example will result in 122868.343750 and 122868.349440 while PHP will end up with 122868.349440.
How do I get the C float result in PHP?
There is no way you can do this using built in php functions.
The one using "double" gives you the real result, 100% precise. The float one is wrong.
In PHP float and double are the same type, which is double.
If you need high precision results, that always give the same results, try using BC Math module: http://php.net/bcmath
Example code using BC Math:
$result = bcmul("123456", "0.99524", 6); // gives 122868.34944
$result = number_format($result, 6, ".", ""); // 122868.349440 - appending zeros
echo $result;
Output:
122868.349440
If you really, really want the same result as in the C program, then you have 2 options:
Create your own c-like function by writing a php extension: http://www.google.com/search?q=writing+php+extensions
Talk to your C-program from PHP via function proc_open():
http://www.php.net/manual/en/function.proc-open.php (see also popen(), exec() or shell_exec())
You could always create a PHP module.
Here are a list of resources that I've compiled over time...
http://www.delicious.com/homer6/php+extension
Also, I'd highly recommend reading Sara Goleman's book:
http://blog.simonholywell.com/post/1156691738/15-excellent-resources-for-php-extension-development
Hope that helps...
Floating point numbers have limited precision. Although it depends on
the system, PHP typically uses the IEEE 754 double precision format,
which will give a maximum relative error due to rounding in the order
of 1.11e-16. Non elementary arithmetic operations may give larger
errors, and, of course, error progragation must be considered when
several operations are compounded.
Additionally, rational numbers that are exactly representable as
floating point numbers in base 10, like 0.1 or 0.7, do not have an
exact representation as floating point numbers in base 2, which is
used internally, no matter the size of the mantissa. Hence, they
cannot be converted into their internal binary counterparts without a
small loss of precision. This can lead to confusing results: for
example, floor((0.1+0.7)*10) will usually return 7 instead of the
expected 8, since the internal representation will be something like
7.9999999999999991118....
So never trust floating number results to the last digit, and never
compare floating point numbers for equality. If higher precision is
necessary, the arbitrary precision math functions and gmp functions
are available.
Quoted from : http://php.net/manual/en/language.types.float.php
To change the precision level of PHP , change the precision settings in php.ini

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.

php intval() and floor() return value that is too low?

Because the float data type in PHP is inaccurate, and a FLOAT in MySQL takes up more space than an INT (and is inaccurate), I always store prices as INTs, multipling by 100 before storing to ensure we have exactly 2 decimal places of precision. However I believe PHP is misbehaving. Example code:
echo "<pre>";
$price = "1.15";
echo "Price = ";
var_dump($price);
$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);
$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);
echo "</pre>";
Produced output:
Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)
I was surprised. When the final result was lower than expected by 1, I was expecting the output of my test to look more like:
Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)
which would demonstrate the inaccuracy of the float type. But why is floor(115) returning 114??
Try this as a quick fix:
$price_int = intval(floor($price_corrected + 0.5));
The problem you are experiencing is not PHP's fault, all programming languages using real numbers with floating point arithmetics have similar issues.
The general rule of thumb for monetary calculations is to never use floats (neither in the database nor in your script). You can avoid all kinds of problems by always storing the cents instead of dollars. The cents are integers, and you can freely add them together, and multiply by other integers. Whenever you display the number, make sure you insert a dot in front of the last two digits.
The reason why you are getting 114 instead of 115 is that floor rounds down, towards the nearest integer, thus floor(114.999999999) becomes 114. The more interesting question is why 1.15 * 100 is 114.999999999 instead of 115. The reason for that is that 1.15 is not exactly 115/100, but it is a very little less, so if you multiply by 100, you get a number a tiny bit smaller than 115.
Here is a more detailed explanation what echo 1.15 * 100; does:
It parses 1.15 to a binary floating point number. This involves rounding, it happens to round down a little bit to get the binary floating point number nearest to 1.15. The reason why you cannot get an exact number (without rounding error) is that 1.15 has infinite number of numerals in base 2.
It parses 100 to a binary floating point number. This involves rounding, but since 100 is a small integer, the rounding error is zero.
It computes the product of the previous two numbers. This also involves a little rounding, to find the nearest binary floating point number. The rounding error happens to be zero in this operation.
It converts the binary floating point number to a base 10 decimal number with a dot, and prints this representation. This also involves a little rounding.
The reason why PHP prints the surprising Corrected price = float(115) (instead of 114.999...) is that var_dump doesn't print the exact number (!), but it prints the number rounded to n - 2 (or n - 1) digits, where n digits is the precision of the calculation. You can easily verify this:
echo 1.15 * 100; # this prints 115
printf("%.30f", 1.15 * 100); # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different"; # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less"; # this prints `less'
If you are printing floats, remember: you don't always see all digits when you print the float.
See also the big warning near the beginning of the PHP float docs.
The other answers have covered the cause and a good workaround to the problem, I believe.
To aim at fixing the problem from a different angle:
For storing price values in MySQL, you should probably look at the DECIMAL type, which lets you store exact values with decimal places.
Maybe it's another possible solution for this "problem":
intval(number_format($problematic_float, 0, '', ''));
PHP is doing rounding based on significant digits. It's hiding the inaccuracy (on line 2). Of course, when floor comes along, it doesn't know any better and lops it all the way down.
As stated this is not a problem with PHP per se, It is more of an issue of handling fractions that can't be expressed as finite floating point values hence leading to loss of character when rounding up.
The solution is to ensure that when you are working on floating point values and you need to maintain accuracy - use the gmp functions or the BC maths functions - bcpow, bcmul et al. and the problem will be resolved easily.
E.g instead of
$price_corrected = $price*100;
use $price_corrected = bcmul($price,100);

Categories