php substraction 145.48 - 80.26 != 65.22 [duplicate] - php

This question already has an answer here:
The accuracy of PHP float calculate
(1 answer)
Closed 8 years ago.
I got a problem with PHP.
This exact substraction below is wrongly computed.
<?php
$test = 145.48 - 80.26;
if($test != 65.22)
echo 'not good !';
else
echo 'good';
?>
This echoes "not good" !!!
Why ?

Computers aren't very good at storing floating point (decimal) numbers since representing a base 10 decimal number in binary is hard. For instance, if you try to store the number 0.2 in binary, the computer will store a series following the pattern 0.00110011… . Depending on the size of the floating point number (i.e. how many bits have been allocated for it in memory), the precision will vary, but more important, it will never accurately store exactly 0.2.
There are several ways to fix this, one is using the BC Math library and do something like:
bcsub("145.48", "80.26");
But sometimes the better solution is to just acknowledge that the numbers won't be accurate and account for the error, i.e.
if (abs($x - $y) < $e)
where e is some very small number, e.g. 10^(-5). This is common practice when working with physics calculations and similar, but of course you should never attempt this when working with discrete numbers, e.g. currencies.

To do exact floating number arithmetic you can use bc_math:
$test = bcsub("145.48", "80.26");
assert ($test == "65.22");

Related

PHP Float Subtract [duplicate]

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 5 years ago.
I have this simple substraction code:
<?php
$n1 = 257931.076;
$n2 = 257930;
echo $n1 - $n2;
?>
Why i got 1.0760000000009 instead of 1.076
Where did the 0000000009 came from? i need precise result and i don't want to use round() or number_format() because sometime i have more than 3 decimal, for example: 12345.678912, anyone know?
I have tried to use round() or number_format() but it only for fixed decimal point, not dynamic
As #GordonM perfectly explained it, you cannot expect exact results when using floating point values.
You can use a library such as brick/math to perform exact calculations on decimal numbers of any size:
use Brick\Math\BigDecimal;
$n1 = BigDecimal::of('257931.076'); // pass the number as a string to retain precision!
$n2 = 257930;
echo $n1->minus($n2); // 1.076
The library uses GMP or BCMath internally when available, but can work without these extensions as well (with a performance penalty).
For technical reasons that every programmer should be aware of, IEEE floating point numbers simply can't represent numbers precisely and will use the closest approximation they can when storing them (In fact the only fractions that can be stored perfectly have denominators that are powers of 2 (1/2, 1/4, 1/8, 1/16, etc. All other values are approximations). PHP has an ini value called "precision", which controls how many digits are considered significant WHEN OUTPUTTING floating point values. It defaults to 14, with any digits after that hidden.
However, the actual value stored may try to approximate the desired value with far more digits than that. If you change precision, you'll see what is really being stored.
php > $test = 0.1;
php > var_dump ($test);
php shell code:1:
double(0.1)
php > ini_set("precision", 100);
php > var_dump ($test);
php shell code:1:
double(0.1000000000000000055511151231257827021181583404541015625)
php > var_dump (0.25);
php shell code:1:
double(0.25)
php > var_dump (0.4);
php shell code:1:
double(0.40000000000000002220446049250313080847263336181640625)
What can you actually do about this? Not a great deal, this is just a consequence of how floating point works. You can try to avoid using floating point if you need exact values (for example when dealing with money amounts, store 3.99 as 399 pennies/cents instead of 3.99 pounds/dollars), or you can use the "bugnum" libraries that are available in PHP, GMP and BC_Math, but these are both tricky to use and have their own sets of gotchas. They can also be hard on storage and/or processor time. In most cases it's best to just live with it and be aware that when you're dealing with floating point you're not dealing with an exact representation.

Reliable Margin of Error for Float -> String -> Float Conversion?

I have a float value that I need to store as a string in PHP and then compare later after casting back into a float.
Due to the conversion I know that relying on equality would be a mistake, as there's potential for a loss of precision, so I'm doing something like the following:
if (abs((float)$string_value - $float_value) < 0.001) { echo "Values are close enough\n"; }
Now, while a margin for error of 0.001 should be fine for my immediate purposes, it got me wondering; what is the smallest margin of error that I can reliably/safely use?
I realise that the safe margin of error will change with the size of the float (i.e- larger values have less or even no fractional precision), so an answer should probably account for this.
So to put it another way; given a float value that I want to store in base 10 and read back, how can I reliably decide what my margin of error should be such that I can reasonably confirm that the two values are the same?
Unfortunately the values I'm handling must be stored in plain decimal form, so my usual go-to of packing them as a network order 64-bit integer is not an option here ☹️
EDIT: To clarify; please assume that my question is about handling arbitrarily sized floats; the example code I've given is for a recent case where I'm handling floats within a limited range, so setting the margin of error manually is fine, but I'd like to be able to handle floats of any magnitude in future.
As mentioned in Mark Dickinson's comment, it is possible to convert a floating-point number to a string and back without losing precision. This only works if
you use enough significant decimal digits (17 for IEEE doubles)
the conversions are accurate (i.e. they're guaranteed to convert to the nearest number)
From a quick look, it seems that casting a double $f to a string in PHP, either implicitly or with (string) $f, only uses 14 significant digits, so this method isn't accurate enough. But you can use sprintf with a %.16e conversion specifier to get 17 significant digits. So after the following roundtrip
$s = sprintf("%.16e", $f);
$f2 = (double) $s;
$f2 should equal $f exactly unless PHP uses suboptimal algorithms internally.
Note that the %e conversion specifier uses scientific (exponential) notation. If you need plain decimal strings, you can use the %f specifier and calculate the required number of digits after the decimal point using log10:
if ($f != 0) {
$prec = 16 - floor(log10(abs($f)));
if ($prec < 0) $prec = 0;
}
else {
$prec = 0;
}
$s = sprintf("%.${prec}f", $f);
This can produce extremely long strings for very small or large numbers, though.
It would probably require a huge amount of research to tell the whether these methods are completely reliable, and if not what the maximum error is. It all depends on several implementation details like PHP version, underlying C library, etc.
Another idea is to compare the string representations instead of floating-point values:
# Assuming $string_value was also converted with float_to_string
if ($string_value == float_to_string($float_value)) {
echo "Values are close enough\n";
}
This should be reliable as long as you stick to the same PHP version.
If you must compare floating-point numbers, it often makes more sense to compare the relative error. See Bruce Dawson's excellent blog for more details.

Why is this condition false? [duplicate]

This question already has answers here:
Is floating point math broken?
(31 answers)
Closed 6 years ago.
Some strange things are going on in my code. Could someone explain please why is this condition false?
When I output object's members with php's function var_dump(), I get this:
string(6) "105.63"
float(105.63)
from one object, and this is output from another object:
string(6) "667.69"
float(667.69)
Then I do comparison like this:
if(105.63 == "105.63"){
echo "true";
} else {
echo "false";
}
if(667.69 == "667.69"){
echo "true";
} else {
echo "false";
}
it outputs 2 times true for sure, if we write code like in my example above. But in my class, it behaves differently. In first if I get false and in second if I get true. Then I looked to the data more deeply with var_export() function and it seems like I actually have following data:
'105.63'
105.6300000000000096633812063373625278472900390625
and
'667.69'
667.69000000000005456968210637569427490234375
So I decide to check wether my conditions work with that data... And they dont. Actually, only first one doesn't. Why do I get output false and true in following code?
if(105.6300000000000096633812063373625278472900390625 == "105.63"){
echo "true";
} else {
echo "false";
}
if(667.69000000000005456968210637569427490234375 == "667.69"){
echo "true";
} else {
echo "false";
}
I know how to fix this. But I'm interested why conditions fails in first if.
EDIT
Php does type juggling when we use == comparison operator!
Take a look at this:
var_export((float) "105.63");
var_export((string) 105.6300000000000096633812063373625278472900390625);
var_export((float) "667.69");
var_export((string) 667.69000000000005456968210637569427490234375);
outputs:
105.63
'105.63'
667.69000000000005
'667.69'
Should not this mean that the first condition be true and the second false? But I get false in first and true in second. Sorry if I was unclear.
Check the manual: http://php.net/manual/en/language.types.float.php
There is a big warning section about comparing floating point numbers:
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. For a "simple" explanation, see the » floating point guide
that's also titled "Why don’t my numbers add up?"
Why dont my numbers add up?

PHP float and int return a different number [duplicate]

This question already has answers here:
Why does floating-point arithmetic not give exact results when adding decimal fractions?
(31 answers)
Closed 6 years ago.
I have been scratching my head at this VERY odd problem. I do some calculations in PHP and the end result is a number. This is a whole number, but because calculations are done, PHP considers this a float. However, when I typecast it as an integer, it magically gets subtracted one. As in 1. A whole integer down. I really am at a loss. Try for yourself.
<?php
$number_of_rows = 10;
$number_of_columns = 19;
$active = array();
$tile = 160;
$column = $tile/$number_of_columns; // 8.42105263158
$rounded_down = floor($column); // 8
$column = $column-$rounded_down; // 0.42105263158
$column = $column*$number_of_columns; // 8
var_dump($column); // 8 -> that is great
var_dump((int)$column); // 7 -> WTF?!!!
?>
PHP 7.0.12 on Linux 64 bit.
See the Warning in PHP manual for an explanation.
Excerpt that talks about precision and a floor() example:
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....

PHP floating point error with basic arithmetic operation [duplicate]

This question already has answers here:
Compare floats in php
(17 answers)
Closed 8 years ago.
I am new to PHP and trying to perform a simple arithmetic addition and comparison. I have an array with some decimal values and after adding all the values, I am trying to compare it to 1. My array is:
$myArray=[0.2,0.7,0.1]
and my code is:
$sum=0;
foreach($myArray as $val){
$sum+=$val;
}
Here, the sum comes out to be 1. But when I compare it using the following code:
if($sum!=1)
{
echo "Good";
}
else
{
echo "Bad";
}
it echoes "Good".
However, when my array contains values 0.8 and 0.2, it echoes "Bad". Can anyone help me on this?
If you check the PHP docs on floating-point numbers: php.net/manual/en/language.types.float.php, there is a huge warning on the page.
"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."
Thus, you could get unexpected results such as 0.2 + 0.7 = 0.900000000001. It's simply because computers have a hard time representing decimals. Binary was really built for integers.
Careful in your logic; I think you meant if($sum == 1) echo "Good", etc. In that case, 0.2+0.7+0.1 would echo "Bad", because it's totally possible that 0.2+0.7+0.1 = 1.00000001 or 0.99999999. On the other hand, adding a smaller number of floats together decreases the error, so 0.8+0.2 is more precise than 0.2+0.7+0.1.

Categories