PHP - Floating Number Precision

PHP - Floating Number Precision

Because floating point arithmetic != real number arithmetic. An illustration of the difference due to imprecision is, for some floats a and b, (a+b)-b != a. This applies to any language using floats.

Since floating point are binary numbers with finite precision, there's a finite amount of representable numbers, which leads accuracy problems and surprises like this. Here's another interesting read: What Every Computer Scientist Should Know About Floating-Point Arithmetic.


Back to your problem, basically there is no way to accurately represent 34.99 or 0.01 in binary (just like in decimal, 1/3 = 0.3333...), so approximations are used instead. To get around the problem, you can:

  1. Use round($result, 2) on the result to round it to 2 decimal places.

  2. Use integers. If that's currency, say US dollars, then store $35.00 as 3500 and $34.99 as 3499, then divide the result by 100.

It's a pity that PHP doesn't have a decimal datatype like other languages do.

Set precision for a float number in PHP

You can use number_format() to achieve this:

echo number_format((float) $number, $precision, '.', ''); 

This would convert 1518845.756789 to 1518845.757.

But if you just want to cut off the number of decimal places short to 3, and not round, then you can do the following:

$number = intval($number * ($p = pow(10, $precision))) / $p;

It may look intimidating at first, but the concept is really simple. You have a number, you multiply it by 103 (it becomes 1518845756.789), cast it to an integer so everything after the 3 decimal places is removed (becomes 1518845756), and then divide the result by 103 (becomes 1518845.756).

Demo

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

PHP floating point precision: Is var_dump secretly rounding and how can I debug precisley then?

You can try to use number_format it won't be perfect since you have to provide number of decimals, but should help.

echo number_format(8-6.4, 50);

1.59999999999999964472863211994990706443786621093750

echo number_format(2.3*100, 50);

229.99999999999997157829056959599256515502929687500000

Edit: As the number of decimal places is varying (this also depends on the system used) the following might be useful - gets the full number for sure and removes trailing zeros:

echo rtrim(number_format(1.0/3.432, 100),0);

0.29137529137529138978379705804400146007537841796875

Set precision for floats in php for 15 numbers after comma

Ok I found answer for my question, which works as I wanted: ini_set('precision', 15);

PHP: Limits when Counting with float values

PHP uses double precision floating point, which has a 52-bit mantissa. This means that integers represented using floats start losing precision when they reach 253 (the extra bit of precision is because the leading bit of a normalized mantissa is always 1, so this doesn't need to be included explicitly in the representation). The following example demonstrates this:

echo number_format(pow(2.0, 53)-1) . "<br>" . 
number_format(pow(2.0, 53)) . "<br>" .
number_format(pow(2.0, 53)+1);

outputs:

9,007,199,254,740,991
9,007,199,254,740,992
9,007,199,254,740,992

To get equivalent floating point precision in MySQL you should use the DOUBLE datatype, which is 64-bit floating point. If you just use FLOAT you'll get 32-bit single precision, which only has 23 bits of mantissa, and loses integer precision at 16,777,216.

See FLOAT and DOUBLE Data Type Representation for more details about how MySQL stores floating point internally.

Floating point precision issue in PHP and MySQL for storing occasional decimal values

Understand that float is not a precise decimal number but a value that's close to it. The way it's stored is very efficient at the cost of not being exact. Without knowing your exact needs, one possible solution is to store the numbers as floats and round them to one decimal.

// the casts are there just for the demonstration, you probably don't need them
$number = "170";
echo round((float)$number, 1); // gives 170
$number = "98.6";
echo round((float)$number, 1); // gives 98.6

Floats represent integers the way you want them, and they lose precision only when you store certain decimals.

PHP 8 float decimal point different than PHP 7

PHP 8.0 UPGRADE NOTES:

var_dump() and debug_zval_dump() will now print floating-point numbers
using serialize_precision rather than precision. In a default configuration,
this means that floating-point numbers are now printed with full accuracy
by these debugging functions.

So you can change this

ini_set('serialize_precision', 16);

https://3v4l.org/uOAPD#v8.1rc3

However, I doubt this is your real issue! since this change affect only "these debugging functions" and also serlization functions like serialize, json_encode

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 :)



Related Topics



Leave a reply



Submit