PHP - Floating Number Precision

PhpFloating PointPrecisionFloating Accuracy

Php Problem Overview


$a = '35';
$b = '-34.99';
echo ($a + $b);

Results in 0.009999999999998

What is up with that? I wondered why my program kept reporting odd results.

Why doesn't PHP return the expected 0.01?

Php Solutions


Solution 1 - Php

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.

Solution 2 - Php

Floating point numbers, like all numbers, must be stored in memory as a string of 0's and 1's. It's all bits to the computer. How floating point differs from integer is in how we interpret the 0's and 1's when we want to look at them.

One bit is the "sign" (0 = positive, 1 = negative), 8 bits are the exponent (ranging from -128 to +127), 23 bits are the number known as the "mantissa" (fraction). So the binary representation of (S1)(P8)(M23) has the value (-1^S)M*2^P

The "mantissa" takes on a special form. In normal scientific notation we display the "one's place" along with the fraction. For instance:

4.39 x 10^2 = 439

In binary the "one's place" is a single bit. Since we ignore all the left-most 0's in scientific notation (we ignore any insignificant figures) the first bit is guaranteed to be a 1

1.101 x 2^3 = 1101 = 13

Since we are guaranteed that the first bit will be a 1, we remove this bit when storing the number to save space. So the above number is stored as just 101 (for the mantissa). The leading 1 is assumed

As an example, let's take the binary string

00000010010110000000000000000000

Breaking it into it's components:

Sign    Power           Mantissa
 0     00000100   10110000000000000000000
 +        +4             1.1011
 +        +4       1 + .5 + .125 + .0625
 +        +4             1.6875

Applying our simple formula:

(-1^S)M*2^P
(-1^0)(1.6875)*2^(+4)
(1)(1.6875)*(16)
27

In other words, 00000010010110000000000000000000 is 27 in floating point (according to IEEE-754 standards).

For many numbers there is no exact binary representation, however. Much like how 1/3 = 0.333.... repeating forever, 1/100 is 0.00000010100011110101110000..... with a repeating "10100011110101110000". A 32-bit computer can't store the entire number in floating point, however. So it makes its best guess.

0.0000001010001111010111000010100011110101110000

Sign    Power           Mantissa
 +        -7     1.01000111101011100001010
 0    -00000111   01000111101011100001010
 0     11111001   01000111101011100001010
01111100101000111101011100001010

(note that negative 7 is produced using 2's complement)

It should be immediately clear that 01111100101000111101011100001010 looks nothing like 0.01

More importantly, however, this contains a truncated version of a repeating decimal. The original decimal contained a repeating "10100011110101110000". We've simplified this to 01000111101011100001010

Translating this floating point number back into decimal via our formula we get 0.0099999979 (note that this is for a 32-bit computer. A 64-bit computer would have much more accuracy)

A Decimal Equivalent

If it helps to understand the problem better, let's look decimal scientific notation when dealing with repeating decimals.

Let's assume that we have 10 "boxes" to store digits. Therefore if we wanted to store a number like 1/16 we would write:

+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 2 | 5 | 0 | 0 | e | - | 2 |
+---+---+---+---+---+---+---+---+---+---+

Which is clearly just 6.25 e -2, where e is shorthand for *10^(. We've allocated 4 boxes for the decimal even though we only needed 2 (padding with zeroes), and we've allocated 2 boxes for signs (one for the sign of the number, one of the sign of the exponent)

Using 10 boxes like this we can display numbers ranging from -9.9999 e -9 to +9.9999 e +9

This works fine for anything with 4 or fewer decimal places, but what happens when we try to store a number like 2/3?

+---+---+---+---+---+---+---+---+---+---+
| + | 6 | . | 6 | 6 | 6 | 7 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+

This new number 0.66667 does not exactly equal 2/3. In fact, it's off by 0.000003333.... If we were to try and write 0.66667 in base 3, we would get 0.2000000000012... instead of 0.2

This problem may become more apparent if we take something with a larger repeating decimal, like 1/7. This has 6 repeating digits: 0.142857142857...

Storing this into our decimal computer we can only show 5 of these digits:

+---+---+---+---+---+---+---+---+---+---+
| + | 1 | . | 4 | 2 | 8 | 6 | e | - | 1 |
+---+---+---+---+---+---+---+---+---+---+

This number, 0.14286, is off by .000002857...

It's "close to correct", but it's not exactly correct, and so if we tried to write this number in base 7 we would get some hideous number instead of 0.1. In fact, plugging this into Wolfram Alpha we get: .10000022320335...

These minor fractional differences should look familiar to your 0.0099999979 (as opposed to 0.01)

Solution 3 - Php

There's plenty of answers here about why floating point numbers work the way they do...

But there's little talk of arbitrary precision (Pickle mentioned it). If you want (or need) exact precision, the only way to do it (for rational numbers at least) is to use the BC Math extension (which is really just a BigNum, Arbitrary Precision implementation...

To add two numbers:

$number = '12345678901234.1234567890';
$number2 = '1';
echo bcadd($number, $number2);

will result in 12345678901235.1234567890...

This is called arbitrary precision math. Basically all numbers are strings which are parsed for every operation and operations are performed on a digit by digit basis (think long division, but done by the library). So that means it's quite slow (in comparison to regular math constructs). But it's very powerful. You can multiply, add, subtract, divide, find modulo and exponentiate any number that has an exact string representation.

So you can't do 1/3 with 100% accuracy, since it has a repeating decimal (and hence isn't rational).

But, if you want to know what 1500.0015 squared is:

Using 32 bit floats (double precision) gives the estimated result of:

2250004.5000023

But bcmath gives the exact answer of:

2250004.50000225

It all depends on the precision you need.

Also, something else to note here. PHP can only represent either 32 bit or 64 bit integers (depending on your install). So if an integer exceeds the size of the native int type (2.1 billion for 32bit, 9.2 x10^18, or 9.2 billion billion for signed ints), PHP will convert the int into a float. While that's not immediately a problem (Since all ints smaller than the precision of the system's float are by definition directly representable as floats), if you try multiplying two together, it'll lose significant precision.

For example, given $n = '40000000002':

As a number, $n will be float(40000000002), which is fine since it's exactly represented. But if we square it, we get: float(1.60000000016E+21)

As a string (using BC math), $n will be exactly '40000000002'. And if we square it, we get: string(22) "1600000000160000000004"...

So if you need the precision with large numbers, or rational decimal points, you might want to look into bcmath...

Solution 4 - Php

bcadd() might be useful here.

<?PHP

$a = '35';
$b = '-34.99';

echo $a + $b;
echo '<br />';
echo bcadd($a,$b,2);

?>

(inefficient output for clarity)

First line gives me 0.009999999999998. Second gives me 0.01

Solution 5 - Php

Because 0.01 can't be represented exactly as sum of series of binary fractions. And that is how floats are stored in memory.

I guess it is not what you want to hear, but it is answer to question. For how to fix see other answers.

Solution 6 - Php

Every number will be save in computer by binary value such as 0, 1. In Single-precision numbers occupy 32 bits.

The floating point number can be presented by: 1 bit for sign, 8 bit for exponent and 23 bit called mantissa (fraction).

Look the example below:

0.15625 = 0.00101 = 1.01*2^(-3)

enter image description here

  • sign: 0 mean positive number, 1 mean negative number, in this case it is 0.

  • exponent: 01111100 = 127 - 3 = 124.

    Note: the bias = 127 so biased exponent = −3 + the "bias". In single precision, the bias is ,127, so in this example the biased exponent is 124;

  • At fraction part, we have: 1.01 mean: 02^-1 + 12^-2

    Number 1 (first position of 1.01) do not need to save because when present the floating number in this way the first number always be 1. For example convert: 0.11 => 1.12^(-1), 0.01 => 12^(-2).

Another example show always remove the first zero: 0.1 will be presented 12^(-1). So the first alwasy be 1. The present number of 12^(-1) will be:

  • 0: positive number
  • 127-1 = 126 = 01111110
  • fraction: 00000000000000000000000 (23 number)

Finally: The raw binary is: 0 01111110 00000000000000000000000

Check it here: http://www.binaryconvert.com/result_float.html?decimal=048046053

Now if you already understand how a floating point number are saved. What happen if the number cannot save in 32 bit (simple precision).

For example: in decimal. 1/3 = 0.3333333333333333333333 and because it is infinite I suppose we have 5 bit to save data. Repeat again this is not real. just suppose. So the data saved in computer will be:

0.33333.

Now when the number loaded the computer calculate again:

0.33333 = 3*10^-1 + 3*10^-2 + 3*10^-3 + 3*10^-4 +  3*10^-5.

About this:

$a = '35';
$b = '-34.99';
echo ($a + $b);

The result is 0.01 ( decimal). Now let show this number in binary.

0.01 (decimal) = 0 10001111 01011100001010001111 (01011100001010001111)*(binary)

Check here: http://www.binaryconvert.com/result_double.html?decimal=048046048049

Because (01011100001010001111) is repeat just like 1/3. So computer cannot save this number in their memory. It must sacrifice. This lead not accuracy in computer.

Advanced ( You must have knowledge about mathematics ) So why we can easily show 0.01 in decimal but not in binary.

Suppose the fraction in binary of 0.01 (decimal) is finite.

So 0.01 = 2^x + 2^y... 2^-z
0.01 * (2^(x+y+...z)) =  (2^x + 2^y... 2^z)*(2^(x+y+...z)). This expression is true when (2^(x+y+...z)) = 100*x1. There are not integer n = x+y+...+z exists. 

=> So 0.01 (decimal) must be infine in binary.

Solution 7 - Php

Use PHP's round() function: http://php.net/manual/en/function.round.php

This answer solves problem, but not explains why. I thought that it is obvious [I am also programming in C++, so it IS obvious for me ;]], but if not, let's say that PHP has it's own calculating precision and in that particular situation it returned most complying information regarding that calculation.

Solution 8 - Php

wouldn't it be easier to use number_format(0.009999999999998, 2) or $res = $a+$b; -> number_format($res, 2);?

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestiondcmoodyView Question on Stackoverflow
Solution 1 - PhpNullUserExceptionView Answer on Stackoverflow
Solution 2 - PhpstevendesuView Answer on Stackoverflow
Solution 3 - PhpircmaxellView Answer on Stackoverflow
Solution 4 - PhpQuasipickleView Answer on Stackoverflow
Solution 5 - PhpAndreyView Answer on Stackoverflow
Solution 6 - Phpchristian NguyenView Answer on Stackoverflow
Solution 7 - PhpTomasz KowalczykView Answer on Stackoverflow
Solution 8 - PhpJurijs NesterovsView Answer on Stackoverflow