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)
Related
I have a very strange issue. If I subtract 2 float vars where one is the result of a mathematical operation I get a wrong value.
Example:
var_dump($remaining);
var_dump($this->hours_sub['personal']);
echo $remaining-$this->hours_sub['personal'];
This it the output:
float 5.4
float 1.4
5.3290705182008E-15
5.4-1.4 should be 4
If I add the two values the result is correct.
Where is my mistake?
It can not be a rounding issue.
If still somebody hits this page with similar problems where floating number subtraction causes error or strange values.
Below I will explain this problem with a bit more details.
It is not directly related to PHP and it is not a bug.
However, every programmer should be aware of this issue.
This problem even took many lives two decades ago.
On 25 February 1991 an incorrect floating-point arithmetic (called rounding error) in a MIM-104 Patriot missile battery prevented it from intercepting an incoming Scud missile in Dhahran, Saudi Arabia, killing 28 soldiers and injuring near 100 servicemen from the U.S. Army's 14th Quartermaster Detachment.
But why it happens?
The reason is that floating point values represent a limited precision. So, a value might
not have the same string representation after any processing (chopped off). It also
includes writing a floating point value in your script and directly
printing it without any mathematical operations.
Just a simple example:
$a = '36';
$b = '-35.99';
echo ($a + $b);
You would expect it to print 0.01, right?
But it will print a very strange answer like 0.009999999999998
Like other numbers, floating point numbers double or float is stored in memory as a string of 0's and 1's. How floating point differs from integer is in how we interpret the 0's and 1's when we want to look at them. There are many standards how they are stored.
Floating-point numbers are typically packed into a computer datum as the sign bit, the exponent field, and the significand or mantissa, from left to right....
Decimal numbers are not well represented in binary due to lack of enough space. So, you can't express 1/3 exactly as it's 0.3333333..., right? Why we can't represent 0.01 as a binary float number is for the same reason. 1/100 is 0.00000010100011110101110000..... with a repeating 10100011110101110000.
If 0.01 is kept in simplified and system-truncated form of 01000111101011100001010 in binary, when it is translated back to decimal, it would be read like 0.0099999.... depending on system (64bit computers will give you much better precision than 32-bits). Operating system decides in this case whether to print it as it sees or how to make it in more human-readable way. So, it is machine-dependent how they want to represent it. But it can be protected in language level with different methods.
If you format the result using
echo number_format(0.009999999999998, 2);
it will print 0.01.
It is because in this case you instruct how it should be read and how precision you require.
Note number_format() is not the only function, a few other functions and ways can be used to tell the programming language about the precision expectation.
References:
https://sdqweb.ipd.kit.edu/publications/pdfs/saglam2016a.pdf
https://en.wikipedia.org/wiki/Round-off_error
This worked for me:
<?php
$a = 96.35;
$b = 96.01;
$c = ( ( floor($a * 100) - floor($b * 100) ) / 100 );
echo $c; // should see 0.34 exactly instead of 0.33999999999999
?>
Since the problem occurs with floating point subtraction operation I decided to eliminate that by transforming it into an integer operation, then backing up the result into a floating point again.
I much prefer that solution because basically it does prevent the error on calculation rather than rouding up the result with other functions.
In addition to using number_format(), there are three other ways to obtain the correct result. One involves doing a little math, as follows:
<?php
$a = '36';
$b = '-35.99';
$a *= 100;
$b *= 100;
echo (($a + $b)/100),"\n";
See demo
Or, you could simply use printf():
<?php
$a = '36';
$b = '-35.99';
printf("\n%.2f",($a+$b));
See demo
Note, without the precision specifier, the printf() result will contain trailing zero decimals, as follows: 0.010000
You also could also utilize the BC Math function bcadd(), as follows:
<?php
$a = '36';
$b = '-35.99';
echo "\n",bcadd($a,$b,2);
See demo
I wrote a simple function to deal with this.
It works similarly to the bcadd function from the bcmath extension of php.
You pass it 2 decimal numbers in string form, $a and $b, and specify how many decimals should be used which must match the number of decimals in both $a and $b.
As you can see it will use integers to do the math, then convert back to string without using floating point operations at any point.
function decimalAdd($a,$b,$numDecimals=2) {
$intSum=(int)str_replace(".","",$a)+(int)str_replace(".","",$b);
$paddedIntSum=str_pad(abs($intSum),$numDecimals,0,STR_PAD_LEFT);
$result=($intSum<0?"-":"").($intSum<100&&$intSum>-100?"0":"").substr_replace($paddedIntSum,".",-$numDecimals,0);
return $result;
}
Sample usage:
echo decimalAdd("36.00","-35.99");
0.01
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.
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) {
...
}
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.
Got a math calculation problem.
$a = 34.56
$b = 34.55
$a do some calculation to get this figure
$b is doing rounding to the nearest 0.05 to get this figure
what happens is
$c = $b - $a
supposedly it be -0.01, but I echo out the $c, which shows -0.00988888888888
I try to use number_format($c, 2), but the output is 0.00,
how can I make sure $a and $b is exactly 2 decimals, no hidden number at the back.
in my php knowledge, number_format is only able to format the display, but the value is not really 2 decimal,
I hope I can get help from here. This really frustrated me.
Try sprintf("%.2f", $c);
Floating point numbers are represented in IEEE notation based on the powers of 2, so terminating decimal numbers may not be a terminating binary number, that's why you get the trailing digits.
As suggested by Variable Length Coder, if you know the precision you want and it doesn't change (e.g. when you're dealing with money) it might be better to just use fixed point numbers i.e. express the numbers as cents rather than dollars
$a = 3456;
$b = 3455;
$c = $b - $a;
sprintf ("%.2f", $c/100.0);
This way, you won't have any rounding errors if you do a lot of calculations before printing.
Use round():
$c = round($b - $a, 2);
Note: you can also choose the rounding mode as appropriate.
Edit: Ok I don't get what sprintf() is doing that number_format() isn't:
$c = number_format($b - $a, 2);
vs
$c = sprintf("%.2f", $b - $a);
?
You can very neatly sidestep all of these issues simply by using the bcmath library.
Just remember to read the documentation and be careful whether you are passing arguments as strings or as numeric datatypes.
You've run into one of the traps of floating point numbers; that they cannot always represent exact decimal fractions. If you want exact decimal values, you're better off using integers and then dividing by the precision you want at the end.
For example, if you're doing calculations in floats represeting Dollars (or your favorite currency), you may want to actually do your calculations in integer cents.
Native Calculation:
$a = 34.56;
$b = 34.55;
$c = $b - $a; // -0.010000000000005
Works as expected (! use always BC functions for real number calculations, the issue is for all C based platforms):
$a = '34.56';
$b = '34.55';
$c = bcsub($b, $a, 4); // -0.0100
I also ran into this issue recently when doing calculations with floats. For example, I had 2 floats that when subtracted and formatted, the value was -0.00.
$floatOne = 267.58;
$floatTwo = 267.58;
$result = number_format($floatOne - floatTwo, 2);
print $result; //printed a string -0.00
What I did was:
$result = abs($floatOne - $floatTwo);// Made the number positive
print money_format('%i', $result); // printed the desired precision 0.00
In my solution I know that floatOne will never be less than floatTwo.
The money_format function is only defined if the system has strfmon capabilities, Windows does not.
If still somebody reach this page with similar problems where floating number subtraction causes error or strange values.
I want to explain this problem with a bit more details. The culprit is the floating point numbers.
And different operating systems and different versions of programming languages can behave differently.
To illustrate the problem, I will explain why with a simple example below.
It is not directly related to PHP and it is not a bug.
However, every programmer should be aware of this issue.
This problem even took many lives two decades ago.
On 25 February 1991 this problem in floating number calculation in a MIM-104 Patriot missile battery prevented it intercepting an incoming Scud missile in Dhahran, Saudi Arabia, contributing to the death of 28 soldiers from the U.S. Army's 14th Quartermaster Detachment.
But why it happens?
The reason is that floating point values represent a limited precision. So, a value might
not have the same string representation after any processing. It also
includes writing a floating point value in your script and directly
printing it without any mathematical operations.
Just a simple example:
$a = '36';
$b = '-35.99';
echo ($a + $b);
You would expect it to print 0.01, right?
But it will print a very strange answer like 0.009999999999998
Like other numbers, floating point numbers double or float is stored in memory as a string of 0's and 1's. How floating point differs from integer is in how we interpret the 0's and 1's when we want to look at them. There are many standards how they are stored.
Floating-point numbers are typically packed into a computer datum as the sign bit, the exponent field, and the significand or mantissa, from left to right....
Decimal numbers are not well represented in binary due to lack of enough space. So, uou can't express 1/3 exactly as it's 0.3333333..., right? Why we can't represent 0.01 as a binary float number is for the same reason. 1/100 is 0.00000010100011110101110000..... with a repeating 10100011110101110000.
If 0.01 is kept in simplified and system-truncated form of 01000111101011100001010 in binary,when it is translated back to decimal, it would be read like 0.0099999.... depending on system (64bit computers will give you much better precision than 32-bits). Operating system decides in this case whether to print it as it sees or how to make it in more human-readable way. So, it is machine-dependent how they want to represent it. But it can be protected in language level with different methods.
If you format the result, echo number_format(0.009999999999998, 2); it will print 0.01.
It is because in this case you instruct how it should be read and how precision you require.
Now you can use this 'round' function to get 2 decimal values.
$a = '34.5638';
$b = round($a,2)
34.56 // result