The Accuracy of PHP Float Calculate

The accuracy of PHP float calculate

Float is an inexact datatype (as all floating-point datatypes are), because you may lose precision when converting to and from binary. This is why you shouldn't use floating-point arithmetic when you need high (exact) precision.

In PHP, check out BC Math or the GMP library. The latter will only work with integers, but it has high performance and sometimes it's possible to convert to and from integers without losing precision.

php float calculation 2 decimal point

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.

PHP - Floating Number Precision

Because floating point arithmetic != real number arithmetic. An illustration of the difference due to imprecision is, for some floats a and b, (a+b)-b != a. This applies to any language using floats.

Since floating point are binary numbers with finite precision, there's a finite amount of representable numbers, which leads accuracy problems and surprises like this. Here's another interesting read: What Every Computer Scientist Should Know About Floating-Point Arithmetic.


Back to your problem, basically there is no way to accurately represent 34.99 or 0.01 in binary (just like in decimal, 1/3 = 0.3333...), so approximations are used instead. To get around the problem, you can:

  1. Use round($result, 2) on the result to round it to 2 decimal places.

  2. Use integers. If that's currency, say US dollars, then store $35.00 as 3500 and $34.99 as 3499, then divide the result by 100.

It's a pity that PHP doesn't have a decimal datatype like other languages do.

Can I rely on PHP php.ini precision workaround for floating point issue


Introduction

Floating-point arithmetic is considered an esoteric subject by many people. This is rather surprising because floating-point is ubiquitous in computer systems. Most fractional numbers don't have an exact representation as a binary fraction, so there is some rounding going on. A good start is What Every Computer Scientist Should Know About Floating-Point Arithmetic

Questions

Question 1

Can I rely on this solution if I need just precise 2 digits calculations (money)?

Answer 1

If you need need precise 2 digits then the answer is NO you can not use the php precision settings to ascertain a 2 digit decimal all the time even if you are not going to work on numbers higher than 10^6.

During calculations there is possibility that the precision length can be increased if the length is less than 8

Question 2

If not can you provide me a clear example when this solutions fails?

Answer 2

ini_set('precision', 8); // your precision
$a = 5.88 ; // cost of 1kg
$q = 2.49 ;// User buys 2.49 kg
$b = $a * 0.01 ; // 10% Discount only on first kg ;
echo ($a * $q) - $b;

Output

14.5824 <---- not precise 2 digits calculations even if precision is 8

Question 3

Which php.ini.precision value suits best two digits, money calculations?

Answer 3

Precision and Money calculation are 2 different things ... it's not a good idea to use PHP precision for as a base for your financial calculations or floating point length

Simple Test

Lest Run some example together using bcmath , number_format and simple minus

Base

$a = 342349.23;
$b = 341765.07;

Example A

ini_set('precision', 20); // set to 20 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Output

584.15999999997438863
584.15999999999996817 <----- Round having a party
584.16
584.15 <-------- here is 15 because precision value is 20

Example B

ini_set('precision', 14); // change to  14 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Output

584.15999999997
584.16
584.16
584.16 <-------- at 14 it changed to 16

Example C

ini_set('precision', 6); // change to  6 
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Output

584.16
584.16
584.16
584.00 <--- at 6 it changed to 00

Example D

ini_set('precision', 3); // change to 3
echo $a - $b, PHP_EOL;
echo floatval(round($a - $b, 2)), PHP_EOL;
echo number_format($a - $b, 2), PHP_EOL;
echo bcsub($a, $b, 2), PHP_EOL;

Output 

584
584
584.16 <-------------------------------- They only consistent value
0.00  <--- at 3 .. everything is gone

Conclusion

Forget about floating point and just calculate in cents then later divided by 100 if that is too late just simply use number_format it looks consistent to me .

Update

Question 1: Is precision workaround gonna fail for numbers between 0..999999.99, where A and B is a number with decimal places? If so please provide me an example

Form 0 to 999999.99 at increment of of 0.01 is about 99,999,999 the combination possibility of your loop is 9,999,999,800,000,000 I really don't think anyone would want to run such test for you.

Since floating point are binary numbers with finite precision trying to set precision would have limited effect to ensure accuracy Here is a simple test :

ini_set('precision', 8);

$a = 0.19;
$b = 0.16;
$c = 0.01;
$d = 0.01;
$e = 0.01;
$f = 0.01;
$g = 0.01;

$h = $a + $b + $c + $d + $e + $f + $g;

echo "Total: " , $h , PHP_EOL;


$i = $h-$a;
$i = $i-$b;
$i = $i-$c;
$i = $i-$d;
$i = $i-$e;
$i = $i-$f;
$i = $i-$g;

echo $i , PHP_EOL;

Output

Total: 0.4
1.0408341E-17 <--- am sure you would expect 0.00 here ;

Try

echo round($i,2) , PHP_EOL;
echo number_format($i,2) , PHP_EOL;

Output

0
0.00 <------ still confirms number_format is most accurate to maintain 2 digit

Question 2: How to estimate/calculate when precision workaround fails? Without such crazy tests? Is there any mathematical*, straight answer for it? How to calculate is gonna to fail or not?

The fact sill remains Floating Point have Accuracy Problems but for mathematical solutions you can look at

  • Machine precision and backward error analysis
  • Minimizing the effect of accuracy problems

i don't need to know floating point calculations works, but when workaround fails if you know precision, and range of A and B

Sample Image

Not sure what that statement means :)

Why does PHP calculate -2.27373675443E-13 for (89.99*12) - 1079.88?

floating point calculations are complicated and potentially inaccurate. this problem appears in every programming-language because decimal numbers can't be stored perfectly in binary representation.

to quote PHPs documentation about this:

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.

php substraction 145.48 - 80.26 != 65.22

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.

Best way to calculate machine epsilon in PHP?

Thanks to @EricPostpischil, main cause of error was printing first value of x for which x+1=1 and instead of that I should be printing last value of x for which x+1≠1. Fixed code :

// Standard way
$x=1.0;
$dx=2.0;
while (true) {
$px = $x;
$x/=$dx;
if ($x+1.0==1.0)
break;
}
printf ("ε = $x ≈ %.1f ε₀ \n", $px/PHP_FLOAT_EPSILON);

// Asymptotically approaching
$x=1.0;
$dx=2.0;
while (true) {
$px = $x;
$x/=$dx;
$dx/=1.0+10**-5;
if ($x+1.0==1.0)
break;
}
printf ("ε = $x ≈ %.1f ε₀ \n", $px/PHP_FLOAT_EPSILON);

Reports =>

ε = 1.1102230246252E-16 ≈ 1.0 ε₀

ε = 5.6311222663283E-17 ≈ 0.5 ε₀

It's good enough now, because first -standard case- reports epsilon to be the same as in PHP documentation.As about second algo, it maybe due to rounding issues and can be interpreted somewhat differently. I.e. if ±ε gets you to the next representable float number, then representation error is half epsilon, because any change greater than |0.5 ε₀| will be rounded to next representable number. It's similar like physicists counts for measurement errors. Say that you have a ruler with smallest measurement unit of 1 mm. Then adding ±0.5 mm to the current reading and rounding will get you to the next representable reading on the ruler. Thus some says that real measurement error is 0.5 mm, and some that it's 1 mm, depends on definition. Same here.



Related Topics



Leave a reply



Submit