How could two equal numbers not be equal in PHP - php

I have two numbers which are supposed to be equal to return a difference, I doesn't make sense...
The only way to be able to reproduce this problem here I had to base64_encode my arrays,
here is the script:
basically the script will fix numbers like "1 234,5" to "1234.5" and does calculations, but at the ends it returns
First Number: 4784.47
Second Number: 4784.47
Difference: 9.0949470177293E-13
I just don't understand????????
$aa = 'YTo3OntpOjA7YToxMDp7czoxMToiRGVzY3JpcHRpb24iO3M6MTI6IkvDqWsgc3rDoW1vayI7czo2OiJQZXJpb2QiO3M6MTI6IjA4LjEwLTA4LjEwLiI7czo2OiJBbW91bnQiO3M6MToiMSI7czoxMjoiRHVyYXRpb25UaW1lIjtzOjc6IjA6MDQ6MDAiO3M6MTI6Ik5ldFVuaXRQcmljZSI7czo1OiIzNSwyMCI7czo5OiJOZXRBbW91bnQiO3M6NjoiMTQwLDgwIjtzOjEwOiJWQVRQZXJjZW50IjtzOjM6IjI1JSI7czozOiJWQVQiO3M6NToiMzUsMjAiO3M6MTE6Ikdyb3NzQW1vdW50IjtzOjY6IjE3NiwwMCI7czo4OiJJdGVtQ29kZSI7czo0OiJCTFVFIjt9aToxO2E6MTA6e3M6MTE6IkRlc2NyaXB0aW9uIjtzOjE3OiJCZWxmw7ZsZGkgaMOtdsOhcyI7czo2OiJQZXJpb2QiO3M6MTI6IjA3LjIyLTA4LjExLiI7czo2OiJBbW91bnQiO3M6MjoiMTIiO3M6MTI6IkR1cmF0aW9uVGltZSI7czo3OiIwOjIxOjQzIjtzOjEyOiJOZXRVbml0UHJpY2UiO3M6NToiMTUsMjAiO3M6OToiTmV0QW1vdW50IjtzOjY6IjMzMCwxMCI7czoxMDoiVkFUUGVyY2VudCI7czozOiIyNSUiO3M6MzoiVkFUIjtzOjU6IjgyLDUyIjtzOjExOiJHcm9zc0Ftb3VudCI7czo2OiI0MTIsNjIiO3M6ODoiSXRlbUNvZGUiO3M6ODoiRklYX0ZMQVQiO31pOjI7YToxMDp7czoxMToiRGVzY3JpcHRpb24iO3M6MjM6IkVnecOpYiBtb2JpbCBlZ3lzw6lnw6FyIjtzOjY6IlBlcmlvZCI7czoxMjoiMDcuMjEtMDguMTEuIjtzOjY6IkFtb3VudCI7czoyOiI1MCI7czoxMjoiRHVyYXRpb25UaW1lIjtzOjc6IjE6MDE6MzgiO3M6MTI6Ik5ldFVuaXRQcmljZSI7czo1OiIxNSwyMCI7czo5OiJOZXRBbW91bnQiO3M6NjoiOTM2LDgzIjtzOjEwOiJWQVRQZXJjZW50IjtzOjM6IjI1JSI7czozOiJWQVQiO3M6NjoiMjM0LDIxIjtzOjExOiJHcm9zc0Ftb3VudCI7czo4OiIxIDE3MSwwNCI7czo4OiJJdGVtQ29kZSI7czo4OiJPRkZfRkxBVCI7fWk6MzthOjEwOntzOjExOiJEZXNjcmlwdGlvbiI7czoxOToiVm9kYWZvbmUgZWd5c8OpZ8OhciI7czo2OiJQZXJpb2QiO3M6MTI6IjA3LjE1LTA4LjEyLiI7czo2OiJBbW91bnQiO3M6MjoiMjAiO3M6MTI6IkR1cmF0aW9uVGltZSI7czo3OiIwOjU0OjM3IjtzOjEyOiJOZXRVbml0UHJpY2UiO3M6NToiMTUsMjAiO3M6OToiTmV0QW1vdW50IjtzOjY6Ijc2MywwNSI7czoxMDoiVkFUUGVyY2VudCI7czozOiIyNSUiO3M6MzoiVkFUIjtzOjY6IjE5MCw3NiI7czoxMToiR3Jvc3NBbW91bnQiO3M6NjoiOTUzLDgxIjtzOjg6Ikl0ZW1Db2RlIjtzOjc6Ik9OX0ZMQVQiO31pOjQ7YToxMDp7czoxMToiRGVzY3JpcHRpb24iO3M6MTU6Ik5lbXpldGvDtnppIFNNUyI7czo2OiJQZXJpb2QiO3M6MTI6IjA3LjEzLTA4LjEzLiI7czo2OiJBbW91bnQiO3M6MjoiNDIiO3M6MTI6IkR1cmF0aW9uVGltZSI7czoxOiItIjtzOjEyOiJOZXRVbml0UHJpY2UiO3M6NToiMzAsNDAiO3M6OToiTmV0QW1vdW50IjtzOjg6IjEgMjc2LDgwIjtzOjEwOiJWQVRQZXJjZW50IjtzOjM6IjI1JSI7czozOiJWQVQiO3M6NjoiMzE5LDIwIjtzOjExOiJHcm9zc0Ftb3VudCI7czo4OiIxIDU5NiwwMCI7czo4OiJJdGVtQ29kZSI7czo3OiJTTVNfSU5UIjt9aTo1O2E6MTA6e3M6MTE6IkRlc2NyaXB0aW9uIjtzOjIzOiJTTVMgaMOhbMOzemF0b24ga8OtdsO8bCI7czo2OiJQZXJpb2QiO3M6MTI6IjA3LjI1LTA4LjExLiI7czo2OiJBbW91bnQiO3M6MjoiMTUiO3M6MTI6IkR1cmF0aW9uVGltZSI7czoxOiItIjtzOjEyOiJOZXRVbml0UHJpY2UiO3M6NToiMTUsMjAiO3M6OToiTmV0QW1vdW50IjtzOjY6IjIyOCwwMCI7czoxMDoiVkFUUGVyY2VudCI7czozOiIyNSUiO3M6MzoiVkFUIjtzOjU6IjU3LDAwIjtzOjExOiJHcm9zc0Ftb3VudCI7czo2OiIyODUsMDAiO3M6ODoiSXRlbUNvZGUiO3M6NzoiU01TX09GRiI7fWk6NjthOjEwOntzOjExOiJEZXNjcmlwdGlvbiI7czoyMjoiU01TIGjDoWzDs3phdG9uIGJlbMO8bCI7czo2OiJQZXJpb2QiO3M6MTI6IjA3LjE3LTA4LjEyLiI7czo2OiJBbW91bnQiO3M6MjoiMTUiO3M6MTI6IkR1cmF0aW9uVGltZSI7czoxOiItIjtzOjEyOiJOZXRVbml0UHJpY2UiO3M6NToiMTUsMjAiO3M6OToiTmV0QW1vdW50IjtzOjY6IjE1MiwwMCI7czoxMDoiVkFUUGVyY2VudCI7czozOiIyNSUiO3M6MzoiVkFUIjtzOjU6IjM4LDAwIjtzOjExOiJHcm9zc0Ftb3VudCI7czo2OiIxOTAsMDAiO3M6ODoiSXRlbUNvZGUiO3M6NjoiU01TX09OIjt9fQ==';
$tt = 'YTozOntzOjE2OiJTdW1tYXJ5SXRlbU5ldHRvIjtzOjg6IjMgODI3LDU4IjtzOjE0OiJTdW1tYXJ5SXRlbVZBVCI7czo2OiI5NTYsODkiO3M6MTc6IlN1bW1hcnlJdGVtQnJ1dHRvIjtzOjg6IjQgNzg0LDQ3Ijt9';
$a = unserialize(base64_decode($aa));
$t = unserialize(base64_decode($tt));
function calculate_call_fees($a,$t){
$or_item = 0;
foreach($a as $k => $r) {
$or_item += fix_num($r['GrossAmount']);
}
$br = fix_num($t['SummaryItemBrutto']);
if($br>$or_item){
$diff = $br-$or_item;
} else {
$diff = 0;
}
echo 'First Number: ' . $br.'<br/>';
echo 'Second Number: ' . $or_item.'<br />';
echo 'Difference: ' . $diff.'<br />';
echo '<hr />';
echo '<pre>';
print_r($a);
echo '</pre>';
echo '<hr />';
echo '<pre>';
print_r($t);
echo '</pre>';
}
function fix_num($n){
return floatval(str_replace(Array(" ",","),array("","."),$n));
}
calculate_call_fees($a,$t);

Using "equals" comparison with floating point numbers is dangerous because of floating point limited precision - you're liable to get small differences due to the rounding involved.
Instead, if you want to see if two floating point numbers are "the same", just see if their difference is below a certain threshold:
if( abs($a - $b) < 0.00000001) {
// a and b are "equal"
}

It is not just PHP. There is a general problem of representing fractional numbers in the computer. It's subject for various types of overflows, underflows, precision issues and so on. PHP's manual shed some light on the topic.
The general rule - if you demand for two 'seem-equal' numbers to be guaranteed equal - don't use floating point data types (float, double), but fixed point (decimal, numeric)

It appears as this is "Machine epsilon" issue:
http://en.wikipedia.org/wiki/Machine_epsilon
Try to compare the difference between them with 0.000001 instead of comparing them directly.

Related

PHP modulo vs substract at PHP_INT_MAX

At some point i had this block of code:
while( $i> $l-1 )
{
$x= fmod($i,$l);
$i= floor($i/$l);
}
I decided to get rid of the modulo operation and wrote this block:
while( true )
{
$d= floor( $i/$l );
if( $d>= 1 )
{
$x= $i - ($d*$l);
$i= $d;
}
else
{
break;
}
}
The $x is used for indexing an array of length $l. The $i is in question here.
While for some relatively small initial $i, both blocks give the same $x over all iterations, when initialized with something close to PHP_INT_MAX the two blocks do not give the same $x.
Unfortunately $l cannot become a power of 2 in order to use bit operators so i am stuck with this.
I am guessing it has something to do with the inner roundings that take place. Could fmod be so optimized for this case? Is there something i am not seeing?
Additional Comment after accepting #trincot 's answer.
One thing i should have mentioned is that although one would expect the second method to produce better results, due to using simple subtraction, it did not. Possibly because of the division taking place at the beginning of the loop.(that is why i asked "Could fmod be so optimized).
According to the documentation, fmod works on floats:
fmod — Returns the floating point remainder (modulo) of the division of the arguments
Instead, the modulo operator (%) would be more suitable for what you need:
Operands of modulus are converted to integers (by stripping the decimal part) before processing.
fmod will become inaccurate for large integers as the floating point representation does not have the same precision.
Examples of some oddities that happen:
$l=3;
$i=9223372036854775295;
echo is_int($i) . "<br>"; // 1 (true)
echo (9223372036854775295==$i) . "<br>"; // 1 (true)
echo number_format($i, 0, ".", "") . "<br>"; // 9223372036854774784
echo fmod($i,$l) . "<br>"; // 1
echo fmod($i-1,$l) . "<br>"; // 1
echo fmod($i-2,$l) . "<br>"; // 1
echo ($i % $l) . "<br>"; // 2
echo (($i-1) % $l) . "<br>"; // 1
echo (($i-2) % $l) . "<br>"; // 0
Notice how a simple number_format already destroys the precision of the integer; it returns a different number because of floating point conversion.
Notice also that this lack of precision makes fmod return 1 for three consecutive numbers, while the modulo operator does what you would want.
So you seem much better of with %.
Alternative
Your function seems to break down a number into its "digits" in an L-basis. For instance, when $l=2, your $x-sequence produces the binary representation of the number, except for the last digit which you leave out.
In that respect, you might have a look at the function call base_convert($i,10,$l), which produces one digit corresponding to a value of $x in your code, with letters for digits above 9. The function can accept $l values up to 36.

Php comparison integer with double

I got a problem with this script
$total = 0;
$expected_total = 1111;
$i = [85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.46,85.48];
foreach ($i as $item) { $total += $item; }
if($total != $expected_total) {
echo json_encode([$total,$expected_total]);
}
The problem is that at the end of the sum the $total should be equal to the $expected_total.
I thought about different number types, I printed the type of the two vars and I was right, one was double and the other was integer, so I converted the integer to double
$expected_total = 1111.00;
but the result is still the same.
The only solution that I could find was comparing the rappresentation of the two numbers, casting them to a string.
if((string)$total != (string)$expected_total) {
echo json_encode([$total,$expected_total]);
}
But obviously this is kind of a hack.
Have you ever had a similar problem? How have you solved it?
PHP version : 5.5.9
Many Thanks
This is not only PHP problem. It is about representation of floating point numbers in memory. Floating numbers has limited precision. PHP uses IEEE 754. Read carefully the manual page and you will understand.
You can find in manual code snippet of how to do it.
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001; //very small number
if(abs($a-$b) < $epsilon) {
echo "true";
}
If you want to check them as integers you could round both values.
You should change the if-statement to the following:
if(round($total) != round($expected_total)) {
echo json_encode([$total,$expected_total]);
}
If you do it like this you will compare the rounded values, which will be the same.
There is a big red label in the PHP Manual, that says:
Floating point numbers have limited precision…
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.
In this specific case, you could add the floating numbers using bcadd() and compare the totals using bccomp(). Both functions are provided by the BC Math extension:
foreach ($i as $item) {
$total = bcadd((string) $total, (string) $item, 2);
}
if (bccomp((string) $total, (string) $expected_total, 2) == 0) {
echo json_encode([$total,$expected_total]);
}

PHP : The awk subtraction giving exponential values

I am getting awk result when I am subtracting two values, the error is I am getting exponent value 2.7755575615629E-17 instead of 0. Anything I am missing to apply, please suggest. These is happening with some cases only like 0.66, 0.67, 0.33,
The prototype of the code I am using is given below,
$_SESSION['x'] = 1;
$_SESSION['x'] = $_SESSION['x'] - 0.83;
echo ( $_SESSION['x']- 0.17) ;
echo '<br>';
But on reversing the values It all fine with 0
$_SESSION['x'] = 1;
$_SESSION['x'] = $_SESSION['x'] - 0.17;
echo ( $_SESSION['x']- 0.83) ;
echo '<br>';
This is because its the floating point numbers. And as per the manual
"The size of a float is platform-dependent, although a maximum of ~1.8e308 with a precision of roughly 14 decimal digits is a common value (the 64 bit IEEE format). "
http://php.net/manual/en/language.types.float.php
Now there are 2 things which could be done by using the type cast your result to (int) or round up the result.
The other option is to use the sprintf
Here is an example
$a = 0.00001234;
echo $a ;
The output will be as
1.234E-5
Now if we do
echo (int)$a ;
The output is 0
or
echo round($a) ;
output will be 0
And finally if we do
echo sprintf('%f', $a);
We will get 0.000012
It is a common problem in computer languages - float values aren't represented exactly. See also http://en.wikipedia.org/wiki/Floating_point#Accuracy_problems. If you have a particular amount of decimal places you want to exactly calculate with, you can use the bcmath functions in PHP:
$_SESSION['x'] = 1;
$_SESSION['x'] = bcsub($_SESSION['x'], 0.83, 10);
echo bcsub($_SESSION['x'], 0.17, 10);
echo '<br>';
Otherwise you can simply use your calculation and add an round($result, $numberOfDecimalPlaces) to you calculated result.

Error With Using (int) and (double) together to Cut off Decimals

When I am using (int) with (double) some times it is not working correct.
Look At The PHP Code Example:
I Need To LEAVE 2 Decimals And REMOVE Other...
I Know number_format(); function But I Cannot Use It. Because It Is Rounding Number
number_format(24.299,2);
Output: 24.30
I Need: 24.29
<?php
$str="158.2";
echo (double)$str; // Output: 158.2
echo (double)$str*100; // Output: 15820
echo (int)((double)$str*100); // Output: 15819 <-WHY? It Must To Be 15820, Why 15819?
echo ((int)((double)$str*100)/100); // Output: 158.19
?>
I need To leave two decimals in the number and cut other WITHOUT rounding.
Because of floating point precision (see for example this question: PHP - Floating Number Precision), 158.2 * 100 is not exactly 15820 but something like 15819.99999999.
Now (int) is for type conversion, not for rounding, and any digits after the point are cut of.
I need To leave two decimals in the number and cut other WITHOUT rounding.
This is easy:
number_format($str, 2);
Update
number_format does round, so it is a bit more complicated:
bcmul($str,100,0)/100
bcmul multiplies with arbitrary precision, in this case 0. Results:
bcmul(158.2,100,0)/100 == 158.2
bcmul(24.299,100,0)/100 == 24.29
This doesn't answer the question of why that happens (it could be a precision bug), but to solve your problem, try using $foo = sprintf("%.2f", (float)$str);.
Example:
$str = "158.2";
$num = (double)$str;
print sprintf("%.2f", $num);
EDIT: Infact, yes, this is a precision issue. (in C++) by printing 158.2 to 20 decimal places, I get the output of "158.19999999999998863132". This is an inherent problem with floating point/double precision values. You can see the same effect by using echo sprintf("%.20f", $var); in PHP.
First off, PHP is a language that allows you to type juggle. Which means you do not need the (int) or the (double) to do what you're trying to do.
<?php
$str="158.2"; //could also do $str = 158.2
echo $str; // Ouput: 158.2
echo $str * 100; //Output: 15820
echo number_format($str, 2); //Output: 158.20
echo number_format(($str*100)/100, 2); //Output: 158.20
?>
Use the number_format command to format your numbers how you want.
More here
Never cast an unknown fraction to integers, see the manual on http://www.php.net/manual/en/language.types.integer.php.
(int) ( (0.1+0.7) * 10 ); will result in 7, not 8 as one might expect. Casting from float to integer will always round down - and you may also want to check the operator precedence http://php.net/manual/en/language.operators.precedence.php.
Solution: calculate your fraction before you cast it. $fStr = (float) $str; $iStr = (int) $fStr;
Fixed.
function cutDecimals($number,$decimal){
$_str=(string)$number;
if(strpos($_str,".")!==false){
$dotPosition=strpos($_str,".")+1;
$_numCount=strpos($_str,".");
$_decimal=strlen($_str)-$dotPosition;
if($_decimal<$decimal) return (double)$_str;
else return (double)substr($_str,0,$_numCount+$decimal+1);
}else return (double)$_str;
}
echo cutDecimals("158.099909865",2)."<br />";
echo cutDecimals("14.02",2)."<br />";
echo cutDecimals("41.12566",2)."<br />";
echo cutDecimals("1.981",2)."<br />";
echo cutDecimals("0.4111",2)."<br />";
echo cutDecimals("144.2",2)."<br />";
echo cutDecimals("55.000000",2)."<br />";
echo cutDecimals("1456115.499811445121",2)."<br />";
?>

Error when subtracting two floating point values in PHP [duplicate]

This question already has answers here:
Closed 10 years ago.
Possible Duplicate:
Compare floats in php
I have the following piece of code:
$a = 1.49;
$b = 1.50;
echo $b - $a; // Outputs 0.01, which is ok
if (($b - $a) != 0.01) {
echo "Not ok";
} else {
echo "Ok";
}
The problem is that if statement echoes "Not ok", although the subtract result is 0.01.
Any idea why?
The PHP doc on floating point numbers shows how to compare them
As noted in the warning above, testing floating point values for
equality is problematic, due to the way that they are represented
internally. However, there are ways to make comparisons of floating
point values that work around these limitations.
To test floating point values for equality, an upper bound on the
relative error due to rounding is used. This value is known as the
machine epsilon, or unit roundoff, and is the smallest acceptable
difference in calculations.
<?php
// $a and $b are equal to 5 digits of precision.
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}
?>
Applied to your example:
$c = $b - $a;
$epsilon = 0.00001;
if (abs($a-$b-0.01) < $epsilon) {
echo "Not ok";
} else {
echo "Ok";
}
Outputs OK
it's because some floating point operations. the result isn't exactly 0.01. your output is rounded by the system.
just try var_dump(($b-$a)-0.01). this should be float(8.673617379884E-18)
a solution would be if (round($b - $a,2) != 0.01)

Categories