Php precision php ini

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;

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

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;
584.15999999997438863
584.15999999999996817 584.16
584.15
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;
584.15999999997
584.16
584.16
584.16
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;
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;

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.

Читайте также:  Задача 3 диагональная матрица python

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;
echo round($i,2) , PHP_EOL;
echo number_format($i,2) , PHP_EOL;

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

Using PHP floatval adds undesired trailing numbers behind decimal places

By using number_format will help to resolve your issue

 $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

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 = '';
$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 = '';
$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 = '';
$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

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.

Читайте также:  METANIT.COM

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

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:

//$total = 79.9501234576908988888;
$total = 79;

echo "parsing 79";
echo "
";

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

";

$total = 79.123;

echo "parsing 79.123";
echo "
";


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

";



?>

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!

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

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

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.

Читайте также:  Java язык программирования какого уровня

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

Источник

PHP RFC: More precise float value handling

This RFC is based on the discussion about displaying float values in json_encode and proposes more precise float value handling overall.

JSON is used to exchange data between systems. Although JSON RFC «6 Numbers» does not require specific implementation for float/int type, a float value should be handled as precise as possible by default.

Currently json_encode() uses EG(precision) which is set to 14. That means that 14 digits at most are used for displaying (printing) the number. IEEE 754 double supports higher precision and serialize()/var_export() uses PG(serialize_precision) which set to 17 be default to be more precise. Since json_encode() uses EG(precision), json_encode() removes lower digits of fraction parts and destroys original value even if PHP’s float could hold more precise float value.

 $j = '< "v": 0.1234567890123456789 >'; var_dump(json_decode($j)); var_dump(json_encode(json_decode($j))); ini_set('precision', 20); var_dump(json_decode($j)); var_dump(json_encode(json_decode($j))); var_dump(0.1234567890123456789); ?>
object(stdClass)#1 (1) < ["v"]=>float(0.12345678901235) > string(22) "" object(stdClass)#1 (1) < ["v"]=>float(0.12345678901234567737) > string(28) "" float(0.12345678901234567737)

PHP’s float type stores “raw” IEEE 754 double and could display accurate fraction value up to 17 digits.

Current PHP outputs meaningless values for oversized EG(precision)/PG(serialize_precision).

 $v = 0.12345678901234567890; var_dump($v); ini_set('precision', 100); var_dump($v); ?>
float(0.12345678901235) float(0.12345678901234567736988623209981597028672695159912109375)

That is caused by used mode for double to string conversion.

Proposal

This RFC proposes to introduce a new setting EG(precision)=-1 and PG(serialize_precision)=-1 that uses zend_dtoa()’s mode 0 which uses better algorigthm for rounding float numbers (-1 is used to indicate 0 mode).

The RFC also proposes changing ini for JSON precision to PG(serialize_precision).

Followings are sample codes and outputs of the proposed patch.

 $v = 10.0000000000001; ini_set('precision', -1); ini_set('serialize_precision', -1); var_dump($v); echo var_export($v, true), PHP_EOL; echo json_encode($v), PHP_EOL; echo $v, PHP_EOL; ?>
float(10.0000000000001) 10.0000000000001 10.0000000000001 10.0000000000001
 $v = 10.00000000000001; ini_set('precision', 14); ini_set('serialize_precision', 17); var_dump($v); echo var_export($v, true), PHP_EOL; ini_set('serialize_precision', 14); echo json_encode($v), PHP_EOL; ini_set('serialize_precision', 17); echo $v, PHP_EOL; ?>
float(10) 10.000000000000011 10 10

Please note that IEEE float cannot store exactly precise values. e.g. Result of “10/3” — see phpt of the patch. Even with this proposal, there will be rounding errors, but the behavior becomes similar to other languages and values are more precise in many cases.

Backward Incompatible Changes

Setting mode 0 as default can mean that the rounding will be more precise which also means that the rounding might be different in var_export()/serialize().

The BC break could happen only if someone would rely on exact output but that shouldn’t be the case. All our existing tests passes when 0 mode is used.

None when old INI value is used.

Источник

Оцените статью