Strange behaviour of conditions in PHP - php

I'm wondering why is output of the following code:
$a = log(5, 5);
$b = round(log(5, 5));
echo 'a: ';
var_dump($a);
echo '<br>';
echo 'b: ';
var_dump($b);
echo '<br>';
echo ($a == $b) ? 'equal' : 'not equal';
this
a: float(1)
b: float(1)
not equal
Thanks to anyone who can explain this to me.

Just to quote the chapter Floating point numbers from the PHP manual:
Warning Floating point precision
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 propagation 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 do not
compare floating point numbers directly for equality. If higher
precision is necessary, the arbitrary precision math functions and gmp
functions are available.

Related

PHP incorrect precision while rounding off number

I need to round some numbers and get modifying value.
I've tried:
$Num=12.456;
$RoundDiff=$Num-round($Num,2);
$Num=round($Num,2);
echo $Num.'<br/>'.$RoundDiff;
but I've got:
12.46
-0.0040000000000013
while my expected result was:
12.46
-0.004
What's wrong?
How can I get what I need?
Try below code. It will give you the expected result. I have just rounded off the difference.
$Num = 12.456;
$RoundDiff = $Num-round($Num,2);
$Num = round($Num,2);
echo $Num.'<br/>'.round($RoundDiff,3);
CodePad
There is issue in precision of floating point values. Refer this article for reference - The PHP floating point precision is wrong by default
If you want exact precision you can use bcmath or gmp.
Because internally, computers use a format (binary floating-point)
that cannot accurately represent a number like 0.1, 0.2 or 0.3 at all.
When the code is compiled or interpreted, your “0.1” is already
rounded to the nearest number in that format, which results in a small
rounding error even before the calculation happens. — floating point
guide
Another Reference :
Given that the implicit precision of a (normal) IEEE 754 double
precision number is slightly less than 16 digits 3, this is a
serious overkill. Put another way, while the mantissa is composed of
52 bits plus 1 implicit bit, 100 decimal digits can carry up to
100*log2(10) =~ 332 bits of information, around 6 times more.
Given this, I propose changing the default precision to 17 (while the
precision is slightly less than 16, a 17th digit is necessary because
the first decimal digit carries little information when it is low). —
source
BCMATH : As requested in comments
$a = 12.456;
$b = round($a,2);
echo 'a ='.$a.'<br>';
echo 'b ='.$b.'<br>';
echo bcsub($a, $b, 3);

How are floating point operations handled in php

I'm aware of the errors with loss of precision that can happen with floating point errors. But I'm not really sure of when and how it's using floating point representations. I feel like my question is best represented by the following shell session:
php > $a = 819.94 - 61.67;
php > $b = 758.27;
php > echo $a;
758.27
php > echo $b;
758.27
php > echo $a == $b;
php > echo (string)$a == (string)$b;
1
php > var_dump($a);
double(758.27)
php > var_dump($b);
double(758.27)
How is this even possible?
Specifically, how are the string representations being determined? == is supposed to be typeless compare. I don't see how casting before comparing should change the values at all.
Also, the var_dumps are showing me an identical value. It doesn't seem unreasonable for me to expect that if the value returned by 2 var_dumps are identical, that the 2 values should be equal.
Two base facts:
Numbers in one base do not necessarily have an exact representation in another base.
Floating point numbers are specifically designed to lose precision in order to accomplish wider ranges.
So when you have a base 2 IEEE floating point number ($b = 758.27) and you want to convert it to a finite base 10 representation ((string)$b) it's obvious that you have to implement some rules.
In PHP, one of the rules is to only display up to precision digits:
precision integer
The number of significant digits displayed in floating point numbers. -1 means that an enhanced algorithm for rounding such numbers
will be used.
var_dump(ini_get('precision'), 758.27, 819.94 - 61.67);
ini_set('precision', 20);
var_dump(ini_get('precision'), 758.27, 819.94 - 61.67);
string(2) "14"
float(758.27)
float(758.27)
string(2) "20"
float(758.26999999999998181)
float(758.2700000000000955)

floor() in php not working

<?php
echo gettype ( 5.00 ); // return double
echo gettype((5)); // return integer
echo gettype(((167.00-158.65)/167.00*100)); // return double
echo floor(5.00); // return 5
echo floor(5); // return 5
echo ((167.00-158.65)/167.00*100); // return 5
echo floor(((167.00-158.65)/167.00*100)); // return 4
var_dump(5.00); // return float(5)
var_dump(5); // return int(5)
var_dump((167.00-158.65)/167.00*100); // return float(5)
var_dump(intval(5)); // return int(5)
var_dump(intval((167.00-158.65)/167.00*100)); // return int(4)
echo gettype(intval(((167.00-158.65)/167.00*100))); // return integer
echo floor(intval((167.00-158.65)/167.00*100)); // return 4
?>
Why floor function in php not working in last case?
How to get 5 from last statement?
Is there any other function or method in php to get exact least amount?
That behaviour is caused by limited precision of floating point numbers. The last case is of type float (check it with var_dump), and the Manual says:
Warning
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 propagation 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....
Source:
http://php.net/manual/en/language.types.float.php

issue in double value comparison with php

I try to create unit test cases to check my table values are correct or wrong.
This is my codes
echo $a = 2/9;
echo "<br>".$b=0.22222222222222;
echo "<br>".gettype($a);
echo "<br>".gettype($b);
if($a==$b){
echo "<br>". "equal";
}else echo "<br>". "Not equal";
if((string)$a==(string)$b){
echo "<br>". "equal";
}else echo "<br>". "Not equal";
Why my first if condition not working? I can't find the reason. Please help me.
The test violates a cardinal rule of floating point programming: never do equality comparisons.
There are a number of problems that stem from the fact that floating point fractions have a large yet finite number of bits. These problems are commonly called "rounding errors" although for the most part they are not errors but format limitations.
For example, because of the way we write numbers when programming ... as decimal strings ... most of the numbers we can write do not have a corresponding representation in the floating point format if they have a decimal fraction. The fractional part repeats in base two.
This largely rules out comparing floating point numbers exactly, except, ironically, between integral values. You need to implement a fuzzy comparison such as abs(a - b) < epsilon.
And actually, your 2/9 is a jackpot case that doesn't have a finite representation as either a decimal string or a binary string!1
To compare 2/9 successfully for equality with a constant places more requirements for perfection on the program, the interpreter, and library than can be counted on.
For example, you would have to type more 2s than you need and the interpreter would have to round the constant's low order bits with knowledge of more precision than the format has. The machine actually has a few bits of extra knowledge when performing the operation but the interpreter may not when converting the constant. Also, the runtime rounding is subject to various options and a language like PHP may not even specify exactly how unrepresentable constants are rounded from source code to internal form.
And actually it's worse than that, because the individual 0.2 / 10n components in the decimal string also do not have exact binary equivalents. So, it's quite likely that a really perfect and faithful conversion of 0.22222222222222 does not actually equal a best-effort representation of the actual 2/9. You cannot express as a finite decimal string the exact base-2 fraction that most closely represents 2/9 in any specific (finite) number of bits.
(We must have somewhere a standard answer about not doing equality comparisons with floating point numbers.)
1.
Every machine fraction is a rational number of the form x/2n. Now, the constants are decimal and every decimal constant is a rational number of the form x/(2n * 5m). The 5m numbers are odd, so there isn't a 2n factor for any of them. Only when m == 0 is there a finite representation in both the binary and decimal expansion of the fraction. For example, 1.25 is exact because it's 5/(22*50) but 0.1 is not because it's 1/(20*51). And for the rational number 2/9, there is neither a 2n or a 5m factor.
Floats are tricky, you need to limit the number of decimal points.
$a = 2/9;
$b=0.22222222222222;
$a = number_format($a, 9);
$b = number_format($b, 9);
echo "a = " . $a . " and b = " . $b;
echo "<br>".gettype($a);
echo "<br>".gettype($b);
if($a==$b){
echo "<br>". "equal";
}else echo "<br>". "Not equal";
if((string)$a==(string)$b){
echo "<br>". "equal";
}else echo "<br>". "Not equal";
If you have a look at the PHP documentation of floating point numbers (which includes doubles), you'll quickly see that it's incredible difficult to compare because of the nature of floating point numbers.
So never trust floating number results to the last digit, and do not compare floating point numbers directly for equality.
The documentation provide an example as well:
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}

php float math operation bug?

the summary here:
$a = 213480.7-190.46;
exit($a-213290.24);
# 2.9103830456734E-11
the result output suppose to be 0. but it output
the story of the operation result :
$b is : 213480.7
-190.46
$b is : 213290.24
now the balance looks correct. but when use comparison operator.. the result is weird
here is the var_dump and compare result
var_dump($b);
# float 213290.24
if ($b==213290.24) {
exit('same');
} elseif ($b>213290.24) {
exit('larger '.($b-213290.24));
} else {
exit('smaller '.($b-213290.24));
}
#larger 2.9103830456734E-11
can anyone tell me how to solve it??
See here: http://php.net/manual/en/language.types.float.php
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.
The common method of dealing with float comparisons is to add an allowable epsilon, or small difference in floating point values, so anything within a small tolerance is considered equivalent.
if (abs(213290.24 - $b) < .001) {
exit('same')
}
Computations performed on floating point numeric values always have inherent error resulting from their machine representation. For this reason, you should not use the equality operator == to compare floating point values.
The typical approach is to decide on a minimum allowable error, and check if the difference between the values you want to compare is less than the desired error.
$min_error = 0.00001;
if (abs($a - $b) < $min_error)
{
exit("same");
}
This is not problem of php, it's connected with the nature of binary float.
You can't represent all rational number accurately with float. For example, you might try to compare 0.1 + 0.2 == 0.3, it will be failse, because 0.3 is not represented accurately.

Categories