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

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

Questions:

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!
``````

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.

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

# 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)?

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?

``````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?

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

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 🙂

Questions:

I just quote this interesting site to the problem. (No reputation expected 🙂 but it should being mentioned:

What can I do to avoid this (floating point) problem?

That depends on what kind of calculations you’re doing.

• If you really need your results to add up exactly, especially when you work with
money:use a special decimal datatype.

• If you just don’t want to see all those extra decimal places: simply format your result rounded to a fixed number of decimal places when displaying it.

• If you have no decimal datatype available, an alternative is to work with integers, e.g. do money calculations entirely in cents. But this is more work and has some drawbacks.

The site contains also some basic tips for PHP

I would use integers or create a special `Decimal` type for it.

If you decide to use bcmath: Be careful if you pass that values to SQL queries or other external programs. It can lead to unwanted side effects if they are not aware of the precision. (What is likely)

Questions:

According to the docs, the precision directive just changes the digits shown when casting numbers to strings:

precision `integer`
The number of significant digits displayed in floating point numbers.

So it’s basically a very convoluted alternative to number_format() or money_format(), except that it has less formatting options and it can suffer from some other side effects you might not be aware:

``````<?php

\$_POST['amount'] = '1234567.89';

\$amount = floatval(\$_POST['amount']);
var_dump(\$amount);

ini_set('precision', 5);
\$amount = floatval(\$_POST['amount']);
var_dump(\$amount);
``````

``````float(1234567.89)
float(1.2346E+6)
``````

Edit:

I insist: this setting does not alter the way PHP makes mathematical calculations with numbers. It’s just a magical way to change format options when converting from floating point numbers (not even integers!) to strings. Example:

``````<?php

ini_set('precision', 2);

\$amount = 1000;
\$price = 98.76;
\$total = \$amount*\$price;

var_dump(\$amount, \$total);

ini_set('precision', 15);
var_dump(\$amount, \$total);
``````

… prints:

``````int(1000)
float(9.9E+4)
int(1000)
float(98760)
``````

Which illustrates that:

1. Floating point calculations are unaffected, only the display changes
2. Integers are unaffected in all cases
Questions:

I believe if you simply round your result you come up with, then that would take care of your floating point problem for you without having to make a server-wide change to your configuration.

``````round(342349.23 - 341765.07, 2) = 584.16
``````

Questions:

If you use precision=8, if you use an 8 digit number, you can’t be certain of the 8th digit. This could be off by 1 from rounding the 9th digit.

Eg

``````12345678.1 -> 12345678
12345678.9 -> 12345679
``````

This may not seem so bad, but consider

``````   (11111111.2 + 11111111.2) + 11111111.4
-> (11111111)                + 11111111.4
-> 22222222.4
-> 22222222
``````

Whereas, if you were using precision=9, this would be `22222222.8` which would round to `22222223`.

If you’re just doing additions and subtractions, you should use at least 2 or so more digits of precision than you need to avoid rounding in these sorts of calculations. If you’re doing multiplication or division, you may need more. Using the bare minimum necessary can lead to lost digits here and there.

So, to answer your question, you might get away with it if you’re lucky and php uses high precision in calculations and only then stores the result in a lower precision (and you don’t then use that number to go on and do other calculations), but in general, it is a very bad idea since (at least) the last digit in your calculation is completely unreliable.