How to Rely on PHP PHP.Ini Precision Workaround For Floating Point Issue

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 setting 'precision' break php round()?

php's round() function still returns a floating point number, not a string, so it might be inexact.

0.5469 is presented as the following 8 bytes 0x3FE180346DC5D639 encoded double precision IEEE754.

Which is not the exact representation of 0.5469 but a closest representible number, which actually is 5.46900000000000052757798130187E-1

References:

  • 0.5469 to bin
  • 0x3FE180346DC5D639 to dec

Using PHP floatval adds undesired trailing numbers behind decimal places

By using number_format will help to resolve your issue

<?php
$number = 8.800000000000001;
$precision = 1;
$number = intval($number * ($p = pow(10, $precision))) / $p;
echo number_format((float) $number, $precision, '.', '');
?>

Sample Image

PHP 7.2.14 json_decode function rounds off the floating point value

welcome to StackOverFlow


if you wanna only set precision to 16 or more, you should set 16 for precision ini. but pay attention if you set specif precision more than 16 and your floating numbers be lower than 17, php make sured to your float numbers convert to 17 digit numbers. such as:

ini_set('precision', 17);
$json = '{"value":16.1}';
$json = json_decode($json, true);
var_dump($json['value']); // value convert from float(16.1) to float(16.100000000000001)

the best solution for this problem is disable precision with putting -1 to precision ini:

ini_set('precision', -1);
$json = '{"value":18.4446359017678}';
$json = json_decode($json, true);
var_dump($json['value']); // float(18.4446359017678)

but there is an another simple solution and it is convert your float number type to string. when your number types be string, php processes for float numbers will be disable:

$json = '{"value":"18.4446359017678"}';
$json = json_decode($json, true);
var_dump($json['value']); // string(16) "18.4446359017678"

How can use big float number in PHP?

You may use ini_set function here.

ini_set('precision', 16);
$appo = 0.953163394286767;
echo $vari = (float)$appo;//prints 0.953163394286767

Hope it helps! :)

Floating point numbers in PHP -- strange behaviour

Floating point numbers have specific, 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.

In your case, if you use var_export, it will output (or return) a parsable string representation of a variable, which in this case will also return all the data when your data is stored as a float variable.

That's why when you execute the following

var_export(round((float)$total,2));

The system will first round up the $total to 79.95, but since you have specified the float cast, it will store it to the system's data precision and so when you use the var_export to faithfully return the data, it will give you something like

79.9500000000000028421709430404007434844970703125

On the other hand, the PHP var_export function is intelligent enough to distinguish the type of data you are trying to parse. (even if you do not use the float cast). Hence if you parse "79", the value will be regarded as an integer, if you parse "79.123", the value will be regarded as float.

Say for the following codes:

<?php
//$total = 79.9501234576908988888;
$total = 79;

echo "parsing 79";
echo "<br>";

var_export((float)$total);
echo " is rounded to ";
var_export(round((float)$total,2));
echo "<br><br>";

$total = 79.123;

echo "parsing 79.123";
echo "<br>";


var_export($total);
echo " is rounded to ";
var_export(round($total,2));
echo "<br><br>";



?>

The result will be :

parsing 79

79 is rounded to 79

parsing 79.123

79.1230000000000046611603465862572193145751953125 is rounded to 79.1200000000000045474735088646411895751953125

Why is PHP typecasting behaving like this? (int) mis-rounding numbers?

from manual:

When converting from float to integer, the number will be rounded towards zero.

If the float is beyond the boundaries of integer (usually +/- 2.15e+9 = 2^31 on 32-bit platforms and +/- 9.22e+18 = 2^63 on 64-bit platforms), the result is undefined, since the float doesn't have enough precision to give an exact integer result. No warning, not even a notice will be issued when this happens!

Warning

Never cast an unknown fraction to integer, as this can sometimes lead to unexpected results.

<?php
echo (int) ( (0.1+0.7) * 10 ); // echoes 7!
?>

Read here

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....

in your case may be 18.99*100=1898.999999999999772626324556767940521240234375 so int truncated it to 1898.



Related Topics



Leave a reply



Submit