Understanding floating point numbers in php - php

I know these questions may get asked a lot but from my reading and testing it had me confused a bit and a lot of the reading I have done has just confused me more as it is quite complex.
Some people seem to have issues with simple comparisons, however I have had no issues myself.
For example...
$num1 = 27.64;
$num2 = 27.64;
if ($num1 == $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's "Good!"
...and
$num1 = 27.60;
$num2 = 27.6;
if ($num1 == $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's Good
...and
$num1 = 27.60;
$num2 = 57.60;
if ($num1 <= $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's Good
...and
$num1 = 25.00;
$num2 = 12.50 + 12.5;
if ($num1 == $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's Good
Then I see pages like http://patchlog.com/php/comparing-float-values-in-php/ that seem to have simple issues and I don't get it.
I just want to understand how he is getting problems with his simple code but I am not with mine.

Example 1
Those values will be the same -- you assign the same decimal literal to each variable. Compare that to this code:
$num1 = 27.64;
$num2 = 10.0 + 2.88 + 2.88 + 2.88 + 9.0; //In decimal arithmetic adds to 27.64
if ($num1 == $num2) {
echo 'Good!';
} else {
echo 'Bad!';
}
// Echo's "Bad!"
$num2 looks like it should be 27.64, but it really adds to something like 27.639999999999997015720509807579219341278076171875 (that's what I get when I do that calculation in Visual C++ on my machine). $num1 = 27.6400000000000005684341886080801486968994140625 (on my machine), so they differ.
Example 2
The trailing 0 makes no difference.
Example 3
The numbers are not within the floating-point "tolerance" so of course will differ.
Example 4
12.5 is exactly representable in floating point, so 12.5 + 12.5 is too (0.5 is 2^-1).

Here is a clear example:
$a = 0;
for ($i = 0; $i < 100000; $i++) {
$a += 0.00001;
}
print("$a\n");
You would expect you'll get 1 printed, but actually the output is 0.99999999999808.
(result is on an x86_64 architecture)

The bigger (or smaller) the floating point number, the less precise it is. Exactly how precise will vary based on processor architecture.
Try doing all your tests at 1E30 or 1E-30...

The first two have the value provided by the compiler, which is resolving both numbers to the same bit pattern.
I'm not going to touch the third since it should be obvious why it works.
For the fourth, the values used have well defined, fully accurate bit patterns. Try using numbers a little more off the beaten path.

You can try
$a = '12.30';
as string to get exact match otherwise floatbox by default remove ending '0'.

Floating-point errors occur only when there are operations whose mathematical results cannot be exactly represented in floating point. The errors are precisely defined; they are not random or arbitrary, so identical results are produced when identical operations are performed.
In your first example, you assign “27.64” to $num1 and to $num2. There is an operation being performed here: The parser must interpret the character string “27.64” and produce a floating-point result. Likely, the parser produces the floating-point number that is closest to 27.64. (As a hexadecimal floating-point numeral, that number is 0x1.ba3d70a3d70a4p+4. The part before the “p” is a hexadecimal numeral, with a fractional part. The “p4” means multiply by 24. As a decimal numeral, it is 27.6400000000000005684341886080801486968994140625.) And it produces the same number in both cases, so the comparison of $num1 to $num2 indicates they are equal to each other, although neither is equal to 27.64 (because 27.64 cannot be exactly represented in floating point).
In your second example, the floating-point number that is closest to the value of the numeral “27.60” is the same as the floating-point number that is closest to the value of the numeral “27.6”, since the two numerals represent the same value. So, again, you get identical results.
In your third example, the values of the two numerals are far apart, so you get different floating-point numbers, and the comparison indicates they are unequal.
In your fourth example, all of the values are exactly representable in floating-point, so there is no error. 25, 12.50, and 12.5 are all small multiples of a power of two (includes powers with a negative exponent, such as .5 = 2-1, within the range of the floating-point type, so they are representable. Further, the sum of 12.50 and 12.5 is exactly representable, so there is no rounding error when adding them. Thus, all the results are exact, and the comparison indicates the sum equals 25.
Problems arise when people expect identical results from two different calculations that would have the same mathematical result. A classic example is comparing “.3” to “.1+.2”. Converting the numeral “.3” to floating-point yields the closest representable value, which is 0x1.3333333333333p-2 (0.299999999999999988897769753748434595763683319091796875), slightly under .3. Converting “.1” to floating-point yields the closest representable value, which is 0x1.999999999999ap-4 (0.1000000000000000055511151231257827021181583404541015625), slightly over .1. Converting “.2” to floating-point yields the closest representable value, which is 0x1.999999999999ap-3 (0.200000000000000011102230246251565404236316680908203125), slightly over .2. Adding the latter two values yields the representable value closest to their sum, which is 0x1.3333333333334p-2 (0.3000000000000000444089209850062616169452667236328125). As you can see, that sum is different from the value obtained by converting “.3” to floating-point, so comparing them indicates they are unequal.

Related

Why floor(0.99999999999999999) = 1 and floor(0.9999999999999999) = 0?

floor function in PHP behave weirdly.
For 16 decimal values it gives floor value but by increasing 1 decimal it round.
$int = 0.99999999999999999;
echo floor($int); // returns 1
$int = 0.9999999999999999;
echo floor($int); // returns 0
$int = 0.99999999999999994;
echo floor($int); // returns 0
Is it defined/explained somewhere, at which point it gives "round" value?
Is there any function which gives 0 anyhow how many 9 in decimals?
It's not floor that rounds, it's floating point math that does.
This line:
echo 0.99999999999999999;
Prints 1 (demo) because 0.99999999999999999 is too precise to be represented by a (64-bit?) float, so the closest possible value is taken, which happens to be 1.
0.99999999999999994 is also too precise to be represented exactly, but here the closest representable value happens to be 0.9999999999999999.
Is it defined/explained somewhere, at which point it gives "round" value?
It's complicated, but the numbers are rounded almost always.
I believe there is no definition of "from when values will be approximated", but that is a mathematical property that follows from the definitions in the IEEE 754 floating point standard.
To be safe, just assume everything is approximated.
Is there any function which gives 0 anyhow how many 9 in decimals?
No. The problem is that, for PHP, 0.99999999999999999 is literally the same as 1.
They're represented by exactly the same sequence of bits, so it can't distinguish them.
There are some solutions to work with bigger precision decimals, but that requires some major code changes.
Probably of interest to you:
Working with large numbers in PHP
Note that while you may get arbitrary precision, you will never get infinite precision, as that would require infinite amounts of storage.
Also note that if you actually were dealing with infinite precision, 0.999... (going on forever) would be truly (as in, mathematically provable) equal to 1, as explained in depth in this Wikipedia article.
$float_14_digits = 0.99999999999999;
echo $float_14_digits; // prints 0.99999999999999
echo floor($float_14_digits); // prints 0
$float_15_digits = 0.999999999999999;
echo $float_15_digits; // prints 1
echo floor($float_15_digits); // prints 1
exit;
on my development machine that behavior happens on digit '15' not '17' like yours. PHP rounds the last digit in the floating numbers. your floor() function has nothing to do with this behavior

PHP float calculation error when subtracting

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

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.

php float calculation 2 decimal point

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

Categories