PHP. result of the subtraction of two floating point numbers - php

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);

Related

is possible to less float values..?

$quantity = 20;
$product_rate = 66.79;
$total = $quantity * $product_rate;
echo $total;
Output is showing 1335.8000000000002
is there possible to show 1335.8 using php..?
You can use the number_format() function like this:
$firstNum = 1335.8000000000002;
$number = number_format($firstNum, 1, '.', '');
echo $number;
outputs:
1335.8
more on number_format() here: http://php.net/number-format.
You can also multiply the number by 10, then use intval() to convert it to an integer (that way stripping out the decimals) and then divide by 10 like this:
$firstNum = 1335.8000000000002;
$number = 10 * intval($firstNum)/10;
echo $number;
outputs:
1335.8
Note: when using the methods above there will be no rounding, for rounding you would use something like this:
$number = round($firstNum, 1);
echo $number;
which in this case also outputs:
1335.8
Do you really use these variable values? I'm using PHP7 and the output for your given values is 1335.8. If you do a manual calculation it is the same result. It should be 1335.8. Anyway if you need to roundup the value you can use below.
round($total,1);
Please refer the below link and you will be able to grab more details.
http://php.net/manual/en/function.round.php
Because how floating point numbers work, they cannot represent every numbers exactly, so approximations are made.
The closest representation of 20 is 20, it can represent 20 exactly, but 66.79 for instance is approximated to 66.7900000000000062527760746889, that times 20 is 1335.800000000000125055521493778 that again cannot be represented and is approximated to 1335.80000000000018189894035459.
Depending on how you choose to print this number, it may round different ways, in your case for some reason you decided to print 13 decimal places so it rounded to 1335.8000000000002, but if you print only 1 or 2 decimal places it will print as 1335.8 or 1335.80. Just be mindful about that when printing floating point numbers, you may want to specify how many decimal places are relevant to you. For that, use number_format().
Example:
echo number_format($number, 2); // prints 2 decimal places
You can do this simply using echo echo round($total, 1) instead of doing round($total)

Incorrect results obtained in simple arithmetic using 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;

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

want to display exactly 2 digits after floating point

I want to convert floating value of 8 digits after floating point in to 2 digits after floating point ..
Eg. $a = 2.200000 ==> 2.20
I am using round function of php. Problem with round is if my number is 2.200000 it converts number in 2.2 . I want output as 2.20
Can any one suggest the possible way?
Actual code
$price = sprintf ("%.2f", round(($opt->price_value + ($opt->price_value * $this->row->prices[0]->taxes[0]->tax_rate)), 2));
i want out put like if my floating number is 2.2000000. then it should return me 2.20. but right now it is returning me 2.2
This does what I think you are asking for:
<?php
$a = 2.20032324;
$f = sprintf ("%.2f", $a);
echo "$a rounded to 2 decimal places is '$f'\n";
$a = 2.2000000;
$f = sprintf ("%.2f", $a);
echo "$a rounded to 2 decimal places is '$f'\n";
results:
[wally#lenovoR61 ~]$ php t.php
2.20032324 rounded to 2 decimal places is '2.20'
2.2 rounded to 2 decimal places is '2.20'
I added two test cases
i was working on it :D
$number='49.099998479854654656516198498465465465465';
function pure_decimal($number){
if(is_numeric($number) and floor($number)!=$number){
//decimal detected
$explode=explode('.',$number);
$first_no=$explode['0'];
$second_no=$explode['1'];
$get_only_two=substr($second_no,0,2);
$final_no=$first_no.'.'.$get_only_two;
$final_no=($final_no*1);
}else{
//normal
$final_no=$number;
}
return $final_no;
}
//result is 49.09
regards.
try $val = round($a * 100)/100;
Try the below code, You can get your result.
$a = 2.200000;
echo number_format((float)$a,2,'.','');

sum of numbers returns odd low decimals?

When summing a group of numbers sometimes I end up with some low decimals? Why can it happen when the numbers are parsed as strings? I know there is some %"&! about floats
function parse(){
foreach($_SESSION['import_csv_posts']['result']['csv'] as $key => $post){
$amount = $this->parse_amount($post[$this->param['amount']]);
if($this->param['vat_amount']){
$amount += $this->parse_amount($post[$this->param['vat_amount']]);
}
$this->balance += $amount;
echo "$amount\n";
}
echo "\nbalance = ".$this->balance;
}
function parse_amount($amount){
$amount = strval($amount);
if(strstr($amount, '.') && strstr($amount, ',')){
preg_match('/^\-?\d+([\.,]{1})/', $amount, $match);
$amount = str_replace($match[1], '', $amount);
}
return str_replace(',', '.', $amount);
}
result
-87329.00
-257700.00
-11400.00
-9120.00
-47485.00
-15504.00
122800.00
1836.00
1254.00
200.00
360.00
31680.00
361.60
1979.20
1144.00
7520.00
6249.49
balance = -399.00000000003
The "%"&! about floats" is that floats are simply not precise. There's a certain inaccuracy inherent in how infinite numbers are stored in finite space. Therefore, when doing math with floats, you won't get 100% accurate results.
Your choice is to either round, format numbers to two decimal places upon output, or use strings and the BC Math package, which is slower, but accurate.
Floating point arithmetic is done by the computer in binary, while the results are displayed in decimal. There are many numbers that cannot be represented equally precisely in both systems, therefore there is almost always some difference between what you as a human expect the result to be and what the result actually is when seen as bits (this is the reason that you cannot reliably compare floats for equality).
It does not matter that your numbers are produced through parsing strings, as soon as PHP sees an arithmetic operator it internally converts the strings to numbers.
If you do not require absolute precision (which it looks like you do not, as you are simply displaying stuff) then simply use printf with a format string such as %.2f to limit the number of decimal places.

Categories