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
Related
I need to round some numbers and get modifying value.
I've tried:
$Num=12.456;
$RoundDiff=$Num-round($Num,2);
$Num=round($Num,2);
echo $Num.'<br/>'.$RoundDiff;
but I've got:
12.46
-0.0040000000000013
while my expected result was:
12.46
-0.004
What's wrong?
How can I get what I need?
Try below code. It will give you the expected result. I have just rounded off the difference.
$Num = 12.456;
$RoundDiff = $Num-round($Num,2);
$Num = round($Num,2);
echo $Num.'<br/>'.round($RoundDiff,3);
CodePad
There is issue in precision of floating point values. Refer this article for reference - The PHP floating point precision is wrong by default
If you want exact precision you can use bcmath or gmp.
Because internally, computers use a format (binary floating-point)
that cannot accurately represent a number like 0.1, 0.2 or 0.3 at all.
When the code is compiled or interpreted, your “0.1” is already
rounded to the nearest number in that format, which results in a small
rounding error even before the calculation happens. — floating point
guide
Another Reference :
Given that the implicit precision of a (normal) IEEE 754 double
precision number is slightly less than 16 digits 3, this is a
serious overkill. Put another way, while the mantissa is composed of
52 bits plus 1 implicit bit, 100 decimal digits can carry up to
100*log2(10) =~ 332 bits of information, around 6 times more.
Given this, I propose changing the default precision to 17 (while the
precision is slightly less than 16, a 17th digit is necessary because
the first decimal digit carries little information when it is low). —
source
BCMATH : As requested in comments
$a = 12.456;
$b = round($a,2);
echo 'a ='.$a.'<br>';
echo 'b ='.$b.'<br>';
echo bcsub($a, $b, 3);
The problem is I don't know about the number of digits in floating point part in the output.. I can't assign 4 for example.. there might be 100 digits in floating point for instance n. How can I solve it?
bcdiv('50','3',4); //16.6666
bcdiv('50','3'); // correct output => 16.66666666666667
Thank you so much...
In floating point math you don't have exact precision. It's limited precision, because computers use a binary system instead of the decimal system we humans are used to. So while you and I can easily see that 10 / 1 is exactly 0.1, a computer sees it as 0.1000000000000000055511151231257827021181583404541015625. This is why we just pick some limited precision that we feel will be sufficient and ignore all of that other stuff at the end there.
See this answer to: Is floating point math broken? for more details.
The code is:
# Suppose the value is 125.987654321012354.
$num = 1250.987654321012354;
echo $num;
$num_array = explode(".", $num);
# Floating point count.
$fCountDigit = strlen(#$num_array[1]); # Use the # sign if there is no floating point. When the value is integer 123351
$intNumber = $num_array[0];
Problem is: float has a specific limit of bytes of 4 bytes. So do keep that in mind.
For example:
# Suppose the value is 1250000000000.987654321012354.
$num = 1250000000000.987654321012354;
echo $num;
Output: 1250000000001 # no floating point.
When I run:
for($o=1;$o<=655;$o++){
$r = $r+0.01;
echo $r." ";}
at some point I get:
...4.29 4.3 4.31 4.32 4.33 4.34 4.35 4.36 4.37 4.38 4.39 4.4
4.41 4.42 4.4299999999999 4.4399999999999 4.4499999999999
4.4599999999999 4.4699999999999 ...
But when I run:
for($o=1;$o<=5;$o+=0.01){
echo $o." ";
}
anomaly starts at:
4.34 4.35 4.36 4.37 4.38 4.3899999999999 4.3999999999999
Why is there a difference between the addition being part of the for loop, or within the for loop?
Welcome to the wonders of rounding errors.
0.01 is not exactly expressible as a finite binary fraction, so you'll pick up rounding errors eventually if you add it repeatedly to another floating point number.
The specific point at which you see the rounding error depends on how the binary expansion of the numbers involved play out to produce the rounding error.
Floating point math can't represent the values exactly. See here.
When the numeral “.01” is converted to 64-bit IEEE 754 binary floating point, the result is 0.010000000000000000208166817117216851329430937767028809. When $r is set to 0, and this approximation of .01 is added to it 100 times, the result is 1.0000000000000006661338147750939242541790008544921875, due to additional rounding errors during the additions.
Now consider that $r is further incremented from this value slightly over 1, while $o is incremented from its starting value of exactly 1. First, since $r and $o start with different values, they will generally have different values later. Second, because they are different, as the increment is added to each of them, they encounter different errors from rounding in the additions. This errors can happen to reinforce or to cancel, causing $r or $o to be more or less different.
Finally, PHP has some criterion for deciding whether to print a number with two decimal points or more. Presumably, at some point the value of $r or of $o is sufficiently far away from the nearest numeral with two decimal points that PHP decides it should print more digits. As explained above, this happens at different times for $r and $o.
You can use the Round() function. Here is a link to it:
http://php.net/manual/en/function.round.php
I am confused as to why:
echo log10(238328) / log10(62);
results in 3
but
echo floor(log10(238328) / log10(62));
results in 2
I know floor rounds down but I thought it was only for decimal numbers.
How can I get an answer of 3 out of the latter statment whilst still normally rounding down?
PHP uses double-precision floating point numbers. Neither of the results of the two logarithms can be represented exactly, so the result of dividing them is not exact. The result you get is close to, but slightly less than 3. This gets rounded to 3 when being formatted by echo. floor, however returns 2.
You can avoid the inexact division by taking advantage of the fact that log(x, b) / log(y, b) is equivalent to log(x, y) (for any base b). This gives you the the expression log(238328, 62) instead, which has a floating point result of exactly 3 (the correct result since 238328 is pow(62, 3)).
It's due to the way floating point numbers are polished in PHP.
See the PHP Manual's Floating Point Numbers entry for more info
A workaround is to floor(round($value, 15));. Doing this will ensure that your number is polished quite accurately.
If you var_dump you'll see that the "3" is actually a float. Which means its probably close to 3 and rounded up. If you wanted 3, you would have to use the sister function, ceil.
You might get better results using the round() function and/or explicitly casting it to an int rather than relying on ceil(). Look here for more information: http://php.net/manual/en/language.types.integer.php
At the cost of a little performance, you could coerce it, reducing the precision to a more useful range by rounding or string formatting the number:
echo floor(round(log10(238328)/log10(62), 4));
echo floor(sprintf('%.4f', log10(238328)/log10(62)));
// output:
// 3
// 3
You should go with the minimum precision that you need. More precision is not what you want. Rounding without flooring might be more correct, the results are different depending on precision.
echo floor(round(log10(238328)/log10(62), 16));
echo round(log10(238328)/log10(62), 16);
// output:
// 2
// 3
there three functions for doing nearly the same:
ceil --> ceil(0.2)==1 && ceil(0.8)==1
floor --> floor(0.2)==0 && floor(0.8)==0
round --> round(0.2)==0 && round(0.8)==1
Because the float data type in PHP is inaccurate, and a FLOAT in MySQL takes up more space than an INT (and is inaccurate), I always store prices as INTs, multipling by 100 before storing to ensure we have exactly 2 decimal places of precision. However I believe PHP is misbehaving. Example code:
echo "<pre>";
$price = "1.15";
echo "Price = ";
var_dump($price);
$price_corrected = $price*100;
echo "Corrected price = ";
var_dump($price_corrected);
$price_int = intval(floor($price_corrected));
echo "Integer price = ";
var_dump($price_int);
echo "</pre>";
Produced output:
Price = string(4) "1.15"
Corrected price = float(115)
Integer price = int(114)
I was surprised. When the final result was lower than expected by 1, I was expecting the output of my test to look more like:
Price = string(4) "1.15"
Corrected price = float(114.999999999)
Integer price = int(114)
which would demonstrate the inaccuracy of the float type. But why is floor(115) returning 114??
Try this as a quick fix:
$price_int = intval(floor($price_corrected + 0.5));
The problem you are experiencing is not PHP's fault, all programming languages using real numbers with floating point arithmetics have similar issues.
The general rule of thumb for monetary calculations is to never use floats (neither in the database nor in your script). You can avoid all kinds of problems by always storing the cents instead of dollars. The cents are integers, and you can freely add them together, and multiply by other integers. Whenever you display the number, make sure you insert a dot in front of the last two digits.
The reason why you are getting 114 instead of 115 is that floor rounds down, towards the nearest integer, thus floor(114.999999999) becomes 114. The more interesting question is why 1.15 * 100 is 114.999999999 instead of 115. The reason for that is that 1.15 is not exactly 115/100, but it is a very little less, so if you multiply by 100, you get a number a tiny bit smaller than 115.
Here is a more detailed explanation what echo 1.15 * 100; does:
It parses 1.15 to a binary floating point number. This involves rounding, it happens to round down a little bit to get the binary floating point number nearest to 1.15. The reason why you cannot get an exact number (without rounding error) is that 1.15 has infinite number of numerals in base 2.
It parses 100 to a binary floating point number. This involves rounding, but since 100 is a small integer, the rounding error is zero.
It computes the product of the previous two numbers. This also involves a little rounding, to find the nearest binary floating point number. The rounding error happens to be zero in this operation.
It converts the binary floating point number to a base 10 decimal number with a dot, and prints this representation. This also involves a little rounding.
The reason why PHP prints the surprising Corrected price = float(115) (instead of 114.999...) is that var_dump doesn't print the exact number (!), but it prints the number rounded to n - 2 (or n - 1) digits, where n digits is the precision of the calculation. You can easily verify this:
echo 1.15 * 100; # this prints 115
printf("%.30f", 1.15 * 100); # you 114.999....
echo 1.15 * 100 == 115.0 ? "same" : "different"; # this prints `different'
echo 1.15 * 100 < 115.0 ? "less" : "not-less"; # this prints `less'
If you are printing floats, remember: you don't always see all digits when you print the float.
See also the big warning near the beginning of the PHP float docs.
The other answers have covered the cause and a good workaround to the problem, I believe.
To aim at fixing the problem from a different angle:
For storing price values in MySQL, you should probably look at the DECIMAL type, which lets you store exact values with decimal places.
Maybe it's another possible solution for this "problem":
intval(number_format($problematic_float, 0, '', ''));
PHP is doing rounding based on significant digits. It's hiding the inaccuracy (on line 2). Of course, when floor comes along, it doesn't know any better and lops it all the way down.
As stated this is not a problem with PHP per se, It is more of an issue of handling fractions that can't be expressed as finite floating point values hence leading to loss of character when rounding up.
The solution is to ensure that when you are working on floating point values and you need to maintain accuracy - use the gmp functions or the BC maths functions - bcpow, bcmul et al. and the problem will be resolved easily.
E.g instead of
$price_corrected = $price*100;
use $price_corrected = bcmul($price,100);