Incorrect results obtained in simple arithmetic using php - php

Here is a simple PHP script:
<?php
$a = 100;
$b = 91.51;
$c = 8.49;
$d = $a - $b - $c;
echo $d;
?>
It outputs -5.3290705182008E-15 which is ugly
With minor change as follows:
$d = $a - ($b + $c);
echo $d;
?>
The output is 0 which is correct. Why is this happening?

Floating point maths is actually very inaccurate. It's a "best guess" value ;)
In floating point (single precision) 100 - 91.51 is not 8.49 as you would expect, but 8.4899978638 since the value 8.49 cannot be exactly expressed in floating point. With double precision it gets better, as it equates to 8.48999999999999488409 which is a little closer. Still not exact though.
Take the following code example:
echo (100 - 91.51) . "\n";
echo number_format(100 - 91.51, 20) . "\n";
The output of that you would expect to be
8.49
8.49000000000000000000
But in fact it is:
8.49
8.48999999999999488409
It defaults to rounding to 2 decimal places for printing floating point values.
Floating point is very much a trade-off. It is a numerical representation system that provides increased resolution and range at the cost of accuracy. For accuracy, but with limited resolution and range, fixed point arithmetic is often used. For instance, if you were storing voltage values between 0V and 5V and you wanted precise measurements at 1µV resolution (i.e., 0.000001V divisions) you may choose to instead represent your voltages in microvolts not volts, so a value of 100000 would actually be 0.1V, and 3827498 would be 3.827498 volts. The mathematics at your prescribed resolution become precise, however you lack the ability to then represent 287x1036V without having massive variables to store the value.

Try to use number_format like this:
$a = 100;
$b = number_format(91.51, 0, ".", "." );
$c = number_format(8.49, 0, ".", "." );
$d = $a - $b - $c;
echo $d;

Related

PHP. result of the subtraction of two floating point numbers

For example...
$aa = 10694994.89;
$bb = 10696193.86;
$ab = $aa - $bb;
// result is:-1198.9699999988 not the -1198,97
But in this exampe:
$cc = 0.89;
$dd = 0.86;
$cd = $cc - $dd;
//Result is: 0.03
Why the difference in to examples? Lacks precision?
None of the numbers in your code can be expressed exactly in binary floating point. They have all been rounded somehow. The question is why one of the results has been (seemingly) rounded to two decimal digits and not the other. The answer lies in the difference between the precision and accuracy of floating point numbers and the precision PHP uses to print them.
Floating point numbers are represented by a significand (or mantissa) in the range [1, 2), which is scaled by multiplying it by a power of two. (This is what the "floating" in floating point means). The precision of the number is determined by the number of digits in the significand. The accuracy is determined by how many of those digits are actually correct. See: How are floating point numbers stored in memory? for more details.
When you echo floating point numbers in PHP, they are first converted to string using the precision configuration setting, which defaults to 14. (In Zend/zend_operators.c)
To see what is really going on, you have to print the numbers using a larger precision:
$aa = 10694994.89;
$bb = 10696193.86;
$ab = $aa - $bb;
printf ("\$aa: %.20G\n", $aa);
printf ("\$bb: %.20G\n", $bb);
printf ("\$ab: %.20G\n\n", $ab);
$cc = 0.89;
$dd = 0.86;
$cd = $cc - $dd;
printf ("\$cc: %.20G\n", $cc);
printf ("\$dd: %.20G\n", $dd);
printf ("\$cd: %.20G\n", $cd);
Output:
$aa: 10694994.890000000596
$bb: 10696193.859999999404
$ab: -1198.9699999988079071
$cc: 0.89000000000000001332
$dd: 0.85999999999999998668
$cd: 0.030000000000000026645
The initial numbers have a precision of about 16 to 17 digits. When you subtract $aa-$bb, the first 4 digits cancel each other out. The result, (while still having a precision of about 16 to 17 digits), is now only accurate to about 12 digits. This lower accuracy shows up when the results is printed using a 14-digit precision.
The other subtraction ($cc-$dd) loses only a single digit of accuracy, which isn't noticable when printed with a 14-digit precision.
This should work for you:
(You have to round your result!)
$aa = 10694994.89;
$bb = 10696193.86;
echo $ab = round($aa - $bb, 2);

Floating point rounding error php ...how can I make sure it works correctly?

I have the following function that determines if I sale is fully paid for. I don't remember why I did it this way, but it has been working so far and I don't remember why I had to do it this way.
function _payments_cover_total()
{
//get_payments is a list of payment amounts such as:
//10.20, 10.21, or even 10.1010101101 (10 decimals max)
$total_payments = 0;
foreach($this->sale_lib->get_payments() as $payment)
{
$total_payments += $payment['payment_amount'];
}
//to_currency_no_money rounds total to 2 decimal places
if (to_currency_no_money($this->sale_lib->get_total()) - $total_payments ) > 1e-6 ) )
{
return false;
}
return true;
}
I am wondering if there is ever a case where due to a rounding error that this function would return false when it shouldn't.
The main part I have a question about is:
> 1e-6
I think before I had, but it was causing problems in some cases.
> 0
I think you are doing what is mentioned on php floating help page. To quote it directly :
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.
$a and $b are equal to 5 digits of precision.
<?php
$a = 1.23456789;
$b = 1.23456780;
$epsilon = 0.00001;
if(abs($a-$b) < $epsilon) {
echo "true";
}
?>
So in your case:
(to_currency_no_money($this->sale_lib->get_total()) - $total_payments) > 1e-6
relative error due to rounding should not be great than 1e-6 or 0.000001
if you are not sure about left operand being greater than right 100% time,then you should add abs() e.g for correctness.
$relative_error=to_currency_no_money($this->sale_lib->get_total()) - $total_payments;
if(abs($relative_error) > 1e-6){
return false
}
return true;
$x = (1.333-1.233)-(1.334-1.234);
echo $x;
//result = $x = -2.2204460492503E-16 - close to zero
//but (1.333-1.233)-(1.334-1.234) = 0.1 - 0.1 = 0 (in calculator)
if($x === 0){
echo "|zero";
}
else {
echo "|non zero"; //<== this is result
}
//screen = -2.2204460492503E-16|non zero
//how to get to zero?
if($x > 1e-6){//1e-6 mathematical constant
echo "|non zero";
}
else {
echo "|zero";//this is result
}
//screen -2.2204460492503E-16|non zero|zero
if ($x > 1e-6 )
{
echo " false";
//echo "|non zero";
//return false;
}
else{
echo " true";//<== this resut
//echo "|zero";
//return true;
}
//screen -2.2204460492503E-16|non zero|zero true
printf("%.1f<br />", 1e-1);
printf("%.2f<br />", 1e-2);
printf("%.3f<br />", 1e-3);
printf("%.4f<br />", 1e-4);
printf("%.5f<br />", 1e-5);
printf("%.6f<br />", 1e-6);
printf("%.7f<br />", 1e-7);
printf("%.8f<br />", 1e-8);
printf("%.9f<br />", 1e-9);
printf("%.10f<br />", 1e-10);
printf("%.11f<br />", 1e-11);
printf("%.12f<br />", 1e-12);
printf("%.29f<br />", -2.2204460492503E-16);
//0.1
//0.01
//0.001
//0.0001
//0.00001
//0.000001
//0.0000001
//0.00000001
//0.000000001
//0.0000000001
//0.00000000001
//0.000000000001
//-0.00000000000000022204460492503
I am sorry, but when dealing with currency, you shouldn't really be using PHP floats, as IMSoP stated. The reason is also from PHP float help pages:
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.
Note that the help page specifically says you can't trust float results to the last digit, no matter how short (after comma) it is.
So while you do have very short floats (just 2 digits after comma), the float precision (1e-6) doesn't enter into it really, and you can't trust them 100%.
Since it is a question of money, in order to avoid angry customers and lawsuits accusing of penny shaving (https://en.wikipedia.org/wiki/Salami_slicing), the real solutions are:
1) either use PHP BC math, which works with string representation of numbers
http://php.net/manual/en/book.bc.php
2) as IMSoP suggested, use integers and store the amounts in smallest denomination (cents, eurocents or whatever you have) internally.
First solution might be a bit more resource intense: I haven't used BC math myself much, but storing strings and doing arbitrary precision math (which might be a bit of an overkill in this case) are by definition more RAM and CPU intense than working with integers.
It might, however, need less changes in other parts of the code.
The second solution requires changes to represenation, so that wherever user sees the amounts, they are normalized to dollars,cents (or whatever have you).
Note, however, that in this case also you run problems with rounding risks at some point, as when you do:
float shown_amount; // what we show to customer: in dollars,cents
int real_amount; // what we use internally: in cents
shown_amount = cent_amount / 100;
you may reintroduce the rounding problems as you have floats again and possibilities for rounding errors, so tread carefully and be sure to make calculations and roundings only on real_amount, never on shown_amount

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.

PHP Simple Math Calculation

can help me to see this calculation? It suppose echo "equal"... but it give me "not equal"
<?php
$tl_pax = 1;
$ct_pax = 2;
$at_pax = 2;
$a = 0.5;
$b = 0.2;
$c = 0.2;
$d = 0.2;
$e = 0.2;
$f = 0.2;
$g = 0.2;
$h = 0.9;
$sum = $a + $b + $c + $d + ($e * $tl_pax) + ($f * $ct_pax) + ($g * $at_pax) + $h;
$total = 3;
if($total == $sum){
echo 'equal: ' . $sum . ' - ' . $total;
}
else{
echo 'not equal: ' . $sum . ' - ' . $total;
}
?>
This is the usual case of the rounding error associated with binary floating point numbers. There are numbers that can't be represented exactly in binary, and thus the result will be of by some margin. To read up on it, the wikipedia article about floating point numbers is great.
The usual pattern found in this case is to pick a delta and compare against it:
if(abs($total - $sum) < 0.01)
echo "equal";
You'll have to pick your delta appropiately according to the usecase.
Check if they have difference less than 0.00001
if(abs($total - $sum) < 0.00001){
http://sandbox.phpcode.eu/g/56905/6
This article shows you why is this happening
It's because your sum is really something like 2.9999999999999999999, due to floating pont arithmetic. PHP just hides that from you when you print it. See the example on floor((0.1+0.7)*10) here: http://php.net/manual/en/language.types.float.php
You should never compare a floating point number for equality. The proper way to compare floats is using a range like:
if($total-0.0000001 <= $sum && $sum <= $total+0.0000001){
You can see it in action here: http://codepad.org/kaVXM5g0
That line just means that $total must be within 0.0000001 of $sum to be considered equal. You can pick the number yourself, depending on the amount of precision you need.
Alternatively you can just round $sum in this case, but then you're basically doing the same thing just with a range from 2.5 - 3.499... instead of 2.9999999 - 3.0000001
The difference is due to the limits of floating point precision.
Values like 0.9 (9/10) can't be written exactly as binary floating point numbers, just like 0.3333... (1/3) can't be written exactly as a decimal fraction. This means that e.g. $h holds an inexact, rounded representation of 0.9. As a result, your calculation yields something very close to 3, but not exactly 3.
Floats are evil.
Quote from http://php.net/float
"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."

php calculate float

I have a weird math calculation here. I hope someone will explain.
$a = 1.85/100;
$b = 1.5/100;
$c = 1.1/100;
$d = 0.4/100;
$e = 0.4/100;
$f = 0.4/100;
$g = 0.4/100;
$h = $a + $b + $c + $d + $e + $f + $g;
echo $h*100 ."<br>";
$i = $h-$a;
$i = $i-$b;
$i = $i-$c;
$i = $i-$d;
$i = $i-$e;
$i = $i-$f;
$i = $i-$g;
echo $i;
The last $i value should be 0 but it returns 6.93889390391E-18.
You should read this article:
What Every Computer Scientist Should Know About Floating-Point Arithmetic
Floating point arithmetic is not exact. You should expect small errors. The answer is correct to within a small rounding error. If you need to check if a floating point number is zero it is best not to check that it is exactly equal to zero but instead to check if it is sufficiently close to zero.
If you really need exact arithmetic, don't use floating point types. In your example you could multiply all your numbers by 100 and use integer arithmetic to get an exact answer.
any idea to correct my equation to show final result 0.00 ?
Yeah, round($i, 2)
The "discrepancies" are usually so small, that rounding it to 2 decimals will almost always solve the problem.
There is nothing wrong going there, there are simply rounding approximation.
In this very cas you should multiply all your values for 1000 and do a division at the end of the calculation or, better, resort to precise calculation extension

Categories