issue in double value comparison with php - 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";
}

Related

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)

Why does PHP appear to evaluate this condition incorrectly?

I have the following code in PHP, where I've attempted to overcome the stated issue by type-casting the variables into Integers and also avoiding floating-point errors by multiplying all values by 100 before comparison in order remove the 2 decimal places.
However, the following code still evaluates the expression to true and colours the text in red instead of green but when I echo the two values of $eq_left and $eq_right, they are identical with no decimal point.
Here's the code:
$eq_left = (int) ($eq_bal_CurrentAssets*100) + ($eq_bal_NonCurrentAssets*100) ;
$eq_right = (int) ($eq_bal_Liabilities*100) + ($eq_bal_Taxation*100) + ($eq_bal_Equity*100) ;
if ($eq_left !== $eq_right) {
$color = 'red';
$diff = abs($eq_left - $eq_right);
} else {
$color = 'green';
}
echo "<div style=\"color: $color; font-weight:bold;\">\n";
echo " " . number_format(($eq_left/100),2,".",",") . " = " . number_format(($eq_right/100),2,".",",") . "<br />\n";
if ($diff) {
echo " Difference = " . number_format(($diff/100),2,".",",") . "\n";
}
echo "</div>\n";
echo $eq_left . " | " . $eq_right
Any ideas?
I agree with the recommendation against floating point if you want exact decimal fraction representation.
The reason is that many decimal fractions can only be approximated in float or double. They are based on binary, not decimal, fractions. In general, a rational number a/b, with no common factors in a and b, can be expressed exactly in a radix r representation if, and only if, all prime factors of b are also prime factors of b. For example, in decimal 1/5 is 0.2, but 1/3 is 0.333333333... In a binary system, 1/5 causes the same problem as 1/3 in decimal.
In your code, I suggest rounding to zero decimal places after doing the multiplication by 100. The (int) cast rounds towards zero, which is not what you need. If the input is even slightly less than a positive integer n, the result of the cast is n-1. The result of round is n.
The floating point representation of a decimal fraction that cannot be represented exactly may be either slightly lower or slightly higher than the original decimal fraction. If you start with e.g. 0.29, convert it to the nearest IEEE 754 64 bit float, and multiply by 100 you will actually get the floating point equivalent of 28.999999999999996447286321199499070644378662109375
Converting that to int with rounding towards zero gives 28, not 29. Rounding it to the nearest int would give 29.
Never use floating point numbers for money. Always store monetary values as integer cents. You store $5.40 as 540 and divide by 100 when you want to display. Floating point numbers cannot accurately represent the decimals that you think they are.
Here are some pages that discuss why floats as money is a terrible idea:
Why not use Double or Float to represent currency?
storing money amounts in mysql
If dealing with money in a float is bad, then why does money_format() do it?
The problems that you are having are inherent with float representations of decimals. The only way to reliably get around them is to use integers.

Strange behaviour of conditions in 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.

Strange addition of numeric strings in PHP

I'm adding together two numerical strings $a and $b and then comparing the result against another numerical string $c. All three numbers are stored as strings, and being converted to floats by PHP at the comparison step.
For some reason, the test $a+$b == $c does not evaluate as true, even though it should.
You can recreate the problem with this script:
<?php
$a = "-111.11";
$b = "-22.22";
$c = "-133.33";
echo '$a is '.$a."\n";
echo '$b is '.$b."\n";
echo '$c is '.$c."\n";
echo '$a + $b is '.($a+$b). "\n";
if ($a + $b == $c) {
echo 'a + b equals c'."\n";
} else {
echo 'a + b does not equal c'."\n";
}
?>
Weirdly, if I change the values slightly so that $a=-111.11, $b=-22.23 and $c=-133.34 it works as expected.
Am I missing something obvious, or is this a bug with PHP?
From the large red box on this page: http://php.net/manual/en/language.types.float.php
never compare floating point numbers for equality.
Basically, you're not getting the correct numbers, because they are saved in a slightly different format, so when you compare, it gets screwed.
That link of #Corbin is really good, So I'm adding it just for the love :)
http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html
What Every Computer Scientist Should Know About Floating-Point Arithmetic
This paper presents a tutorial on those aspects of floating-point that
have a direct impact on designers of computer systems. It begins with
background on floating-point representation and rounding error,
continues with a discussion of the IEEE floating-point standard, and
concludes with numerous examples of how computer builders can better
support floating-point.
You're running into a limitation of floating point arithmetic. Just as there are certain numbers you can't represent exactly in decimal (1/3 for instance), so there are certain numbers you can't represent exactly in floating point binary.
You should never try and compare floating point numbers for equality, as the limitations of floating point make it unlikely that the variables you're comparing have an actual value that matches exactly the value you think they have. You need to add a "fudge factor", that is if the two numbers are similar to within a certain tolerance, then you should consider them to be equal.
You can do this by subtracting one number from another and seeing if the absolute result is below your threshold (in my example, 0.01):
if (abs ($someFloatingPointNumber - $someOtherFloatingPointNumber) <= 0.01)
{
// The values are close enough to be considered equal
}
Of course, this combined with rounding errors that can creep in with successive mathematical operations mean that floating point numbers are often not the best choice anyway, and should be avoided where possible. For example, if you're dealing with currency, store your values as integers in the minor unit (pennies for GBP, cents for USD, etc), and only convert to the major unit by dividing by 100 for display.
Do your number have always two decimal positions?
If so, you can try this:
$aDec = round($a * 100);
$bDec = round($b * 100);
$cDec = round($c * 100);
if ($aDec + $bDec == $cDec) {
...
}

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