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

floating-point, php

Solution

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

Not sure what that statement means :)

Problem

I've found some workaround for floating point problem in PHP: php.ini setting `precision = 14` ``` 342349.23 - 341765.07 = 584.15999999992 // floating point problem ``` php.ini setting, let's say `precision = 8` ``` 342349.23 - 341765.07 = 584.16 // voila! ``` Demo: http://codepad.org/r7o086sS How bad is that? 1. Can I rely on this solution if I need just precise 2 digits calculations (money)? 2. If not can you provide me a clear example when this solutions fails? Edit: 3. Which php.ini.precision value suits best two digits, money calculations - Please mind I can't use integer calculations (float*100 = cents), it's far too late for that. - I am not going to work on numbers higher than 10^6 - I don't need to compare numbers UPDATE @Baba answer is good, but he used `precision=20`, `precision=6` in his tests... So still i am not sure is it gonna work or not. Please consider following: Let's say `precision = 8` and only thing I do is addition `+` and subtraction `-` `A + B = C` `A - B = C` 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. Simple test would do the job: ``` // if it fails what if I use 9,10,11 ??? // **how to find when it fails??? ** ini_set('precision', 8); for($a=0;$a<999999.99;$a+=0.01) { for($b=0;$b<999999.99;$b+=0.01) { // mind I don't need to test comparision (round($a-$b,2) == ($a-$b)) echo ($a + $b).','.($a - $b)." vs "; echo round($a + $b, 2).','.round($a - $b, 2)."\n"; } } ``` but obviously `99999999 * 2` is too big job so I can't run this test Question 2: How to estimate/calculate when precision workaround fails? Without such crazy tests? Is there any mathematicial*, straight answer for it? How to calculate is gonna to fail or not? *i don't need to know floating point calculations works, but when workaround fails if you know precision, and range of A and B Please mind I really know cents and bcmath are best solution. But still I am not sure is workaround gonna fails or not for substraction and addition

Original source