32

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

nneonneo
  • 171,345
  • 36
  • 312
  • 383
Peter
  • 16,453
  • 8
  • 51
  • 77
  • 9
    If you're dealing with 2-digit precision (money), why not use integers instead and divide the final result by 100? – Wiseguy Jan 29 '13 at 16:19
  • Probably the main reason i would'nt use php's precision is if you have shared server. The PHP precision would be shared accross all website hosted on the server. – poudigne Jan 29 '13 at 16:21
  • 2
    @PLAudet It could be set in an apache vhost with `php_value` for example... – Michael Berkowski Jan 29 '13 at 16:22
  • @PeterSzymkowski Then I don't see any problem, except if you need more precision somewhere else in your code. – poudigne Jan 29 '13 at 16:24
  • 1
    What about using [`bcmath` functions](http://php.net/bcmath)? Or, for that matter, just rounding the result. But I guess the issue is you want to change as little existing code as possible? – Wiseguy Jan 29 '13 at 16:25
  • 2
    what about simply using `round($value,2)` or `number_format($value,2)` – Pitchinnate Jan 29 '13 at 16:27
  • @Wiseguy The implication is that the OP can't or doesn't want to use anything besides floats. – NullUserException Jan 29 '13 at 16:28
  • @Pitchinnate That doesn't really solve the problem. – NullUserException Jan 29 '13 at 16:28
  • The truth is you should've used integers. It's not too late for that, you'd probably be better off fixing your code to do the right thing than to use a hack to mask the problem and have it come back to bite you in a silent, sneaky and hard to debug way later on. – NullUserException Jan 29 '13 at 16:30
  • @NullUserException That's what I figured, but I'd still ask because oftentimes such restrictions are self-imposed assumptions and are not inherently restrictions. – Wiseguy Jan 29 '13 at 16:31
  • @NullUserException Why? Rounding prices is the common way to. Also in 'real life' – hek2mgl Jan 29 '13 at 16:33
  • @hek2mgl what if I want to `floor` the number? `floor($x)` problem. `floor(round($x,2))` problem. Integers or `bcmath` it's the only way, and I gonna do it sooner or later – Peter Jan 29 '13 at 16:35
  • @PeterSzymkowski In 'real life' you would round. Especially with prices. If floor is really requeried then using integers or bcmath would be the solution – hek2mgl Jan 29 '13 at 16:37
  • @hek2mgl [been there, using round() and it's even worse](http://codepad.viper-7.com/3Ut8gr) as it's VERY hard to find the bug – Peter Jan 29 '13 at 16:46
  • @PeterSzymkowski Thanks for the link. I'll read. Yesterday an answer to a related problem pointed to page that tells the difference (when I understand that right). It may be 'problem depended' to answer the question. I'll verify that page again, and after that I'm hopefully more wise :) – hek2mgl Jan 29 '13 at 16:55
  • 4
    Using integers doesn't help if your app does any division. (Like offering percentage-based price discounts or calculating cost per item for items purchased in bulk.) – Alex Howansky Jan 29 '13 at 17:00
  • @AlexHowansky true. So the only way is bcmath? – Peter Jan 29 '13 at 17:04
  • @Peter. Just a question: Why don't you : http://codepad.viper-7.com/9iQ4cZ – hek2mgl Jan 29 '13 at 17:06
  • Btw here is the site from yesterday. http://floating-point-gui.de/basic/ – hek2mgl Jan 29 '13 at 17:07
  • 1
    I should had read it at least the start page completely :) - the answer / an answer is on first page. And its especially for financiel applications – hek2mgl Jan 29 '13 at 17:08

5 Answers5

51

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

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

enter image description here

Not sure what that statement means :)

Community
  • 1
  • 1
Baba
  • 94,024
  • 28
  • 166
  • 217
  • Nice one, thank you. But regarding **Answer 2** - still there is no example when solution fails on 2 digits number add/substract calculations like `342349.23 - 341765.07 = 584.15999999992`. And in **Example C** precision workaround is acctionaly.. doing the proper job :) – Peter Feb 03 '13 at 18:25
  • My Belive is that you need exactly 2 digit `584.15999999992` does not look like it – Baba Feb 04 '13 at 07:48
  • Ok in simple words: Can you provide example where FPP happens (with precision=8) on equations `A+B=C` and `A-B=C` where A,B are numbers between 0.00 and 99999.99? – Peter Feb 05 '13 at 03:34
  • If financial calculations there is not way it would be just `+` and `-` if you consider Vat , Discount , Volume , Size , Quantity etc. you would have to use `*` and `/` .... Answer 2 give a typical use case – Baba Feb 05 '13 at 07:52
  • Thing is I am using my own written function which works like `number_format` every time for `*` and `/` I am just worried for `+` and `-` – Peter Feb 05 '13 at 08:50
  • Are you saying your calculation would only be `+` and `-` and it would always be in `0 - 999999.99` ?? I hope you know `0.0000001` fits that range ? – Baba Feb 07 '13 at 15:48
  • 2
    This answer shows a lot of effort (my kudos to you for that). However, after reading it you get the impression that PHP's `precision` directive somehow affects mathematical calculations and that's misleading at its best, given that it's just a parameter for string conversion. If you have a number and you convert it to string *before* you do math with it (the only case where `precision` can act) you should probably start by tweaking your code's logic. – Álvaro González Feb 07 '13 at 17:23
  • I am really grateful for your answer, and I already rewarded you bounty +250, lovely job. However I am still curious and I need to know is it possible to use precision 8-10 for numbers with 2 decimal places less than 10^6. Please see "test" in my question (update section). And in near future I am going to tweak my php logic using making own class with `bcmath`. Answers here are great especially yours, but you guys are tellimng me "use cents/bcmath/round" and "precision 2 will fail, precision 20 will fail" – Peter Feb 07 '13 at 17:30
  • After testing 429,496,729 combinations my server freezed and it could no longer hold up ... The log is too big here is [`0 - 659748.4`](http://oleku.rs.af.cm/log.dat) about 2GB in size. In conclusion if you are dealing with + and - there is possibility of never getting a 3 digit .. an not sure if y system would be able to test all 9,999,999,800,000,001 possibility this year – Baba Feb 07 '13 at 20:28
  • Sorry I wasn't clear - My test was an example what I am looking for. `$x = (($a + $b).','.($a - $b)); $y = (round($a + $b, 2).','.round($a - $b, 2)."\n"); if(strlen($x!=$y)) die('error')` woudl do the job. Still I don't want you to use your machine and do the tests but I am looking for clear, straight, mathematical answer. Thanks again for you effort! – Peter Feb 07 '13 at 21:37
  • ITs does not change the fact you want to loop over `9,999,999,800,000,001` possibilities – Baba Feb 09 '13 at 02:38
  • 2
    Thank you so much. Really great answer. And I am more than happy to click +500 button, as this is exactly what I was looking for. Thanks to you now I have no single doubt that precise workaround is not an option in any possible scenario. Thanks again. – Peter Feb 12 '13 at 20:20
5

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
Álvaro González
  • 142,137
  • 41
  • 261
  • 360
  • So `1234567.89` is quite good for "precision=5"? What if I use precision `10`? (edited my question) Which php.ini.precision value suits best for two digits calculations? If precision just `changes the digits shown when casting numbers` why FP issue dissapeared? – Peter Jan 29 '13 at 16:49
  • 1
    @PeterSzymkowski - Not sure I understand all your questions. Precision takes into account all digits, not just decimals. And your FP issue "disappears" because the difference is tiny and it's gone with rounding. You are simply rounding in a very complicate way. – Álvaro González Jan 29 '13 at 17:08
  • Let's says I am using precision=10. So any substraction between numbers `1..10^10-2` gonna give me good results without FP Problem? – Peter Jan 29 '13 at 17:16
  • I suggest you forget about `precision`. You have many functions that provide full control and don't have size effects. – Álvaro González Jan 29 '13 at 17:56
  • Yeah you are 100% right, just wondering can I rely on precision. – Peter Jan 29 '13 at 17:57
  • @PeterSzymkowski - Same question again... I've added a clarification to my answer, I hope that helps. – Álvaro González Feb 07 '13 at 13:28
  • but why u make example with too low or too high precision? I know if I use precision 1 or 200 it gonna fails. Please see **upodate** section in my question – Peter Feb 07 '13 at 13:49
  • Still you answer is not enough, I still think if I use precision let's say 10 it gonna work OK – Peter Feb 07 '13 at 13:52
  • @PeterSzymkowski - Well, you've already decided that `precision` is a workaround and we need to explain why. That's not what my answer is about, sorry. – Álvaro González Feb 07 '13 at 15:02
  • No I just want to get clear answer is it gonna works or not and when it fails. I know if I use precision 1 or 30 it gonna fails – Peter Feb 07 '13 at 17:02
4

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)

hek2mgl
  • 152,036
  • 28
  • 249
  • 266
  • Yeah , they are right - `bcmath` is the best option. I found that [rounding is producing very unexpected results](https://gist.github.com/4666002) and it's very easy to make bugs. Rounding is OK only if you want to display data. – Peter Jan 29 '13 at 17:31
  • Read your gist. Without doing some calculations I cannot say if the pseudo example is possible with computers. It is in question if 0.1349999999 is a possible floating point value. – hek2mgl Jan 29 '13 at 17:31
  • [And round bugs pops up just everywhere, and it's a horror to debug it](https://gist.github.com/4666136) – Peter Jan 29 '13 at 17:51
  • OK i think you are right anyway. [I should use round for every simple equation in every single php project where i am working with money](http://codepad.org/ioxzmL3P) – Peter Jan 29 '13 at 18:17
  • @PeterSzymkowski Btw, how many different code paste sites are you using in parallel?? :) Do you know about http://phpfiddle.org/ and related? May be your number of sites used in parallel can grow more? :) – hek2mgl Jan 29 '13 at 18:21
  • i like pastebin.com,gist.github.com,codepad.org,codepad.viper-7.org,sqfiddle.com and jsfiddle.net. IMO phpfiddle looks to big, I like simiplicity :) And usally I use that kind of sites to show code to others, not test it, but I'll make a bookmark to it – Peter Jan 29 '13 at 18:24
  • Ok. Note that there are others that work like it. Sometimes its cool to share how code 'runs' – hek2mgl Jan 29 '13 at 18:29
  • @PeterSzymkowski No. But I found bugs on related sites too ;) I would not launch such a project. But to do it, and do it (really) secure and stable is certainly an interesting project. – hek2mgl Jan 29 '13 at 18:47
  • already deleted other people fiddles there :) send message via contact form about bug :) – Peter Jan 29 '13 at 19:09
  • You should not delete other peoples fiddles. ;) – hek2mgl Jan 29 '13 at 19:14
1

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
Jeff Lambert
  • 24,395
  • 4
  • 69
  • 96
0

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.

P O'Conbhui
  • 1,203
  • 1
  • 9
  • 16
  • Yep. This is why i asked for numbers up to 10^6, and what about precision 8,9,10,11,12? – Peter Feb 09 '13 at 12:16