## Thursday, November 19, 2009

### Float comparison

#### What's more normal than using '==' or '===' operator to check equality between two numbers ?

That works for most cases, but computers have technical limitations as nobody can represent the exact value of Pi. Binary representation is reliable for integers unless you need to mean an out-of-range value. For decimals and real numbers, you might encounter problem of precision. What's happening with these floating point numbers, also known as floats and doubles (double precision) ?

Example :

\$float1 = 1.333;
\$float2 = 1.1 + 0.233;

// Are they really equal ?
if (\$float1 === \$float2)
echo 'OK';
else
echo 'ERROR !';

// ERROR !

// Is this a problem of type ?
if (gettype(\$float1) == gettype(\$float2))
echo 'No, \$float1 and \$float2 are both ' . gettype(\$float1);
else
echo 'Yes, \$float1 is a ' . gettype(\$float1) . ' whereas \$float2 is a ' . gettype(\$float2);

// No, \$float1 and \$float2 are both double

// Where is the problem ?!!
\$gap = \$float1 - \$float2;
echo '\$gap = ' . \$gap;

Result : \$gap = -2.22044604925E-16 !!
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

2 reasons :
• Limited length : 32 bits for floats, 64 bits for doubles.
Real numbers do exist but we can't exactly represent them without an endless container... You can't write Pi value on a paper sheet, you can't load it in a set of bits...
• Base-2 system
We easily understand why Pi can't be represented but above example shows an error with a finite number : 1.333. Yes, it's a finite number ... in a decimal system, but it's endless in a base-2 system !
• Decimal :        1.333 = 1 x 10^0 + 3 x 10^-1 + 3 x 10^-3 + 3 x 10^-3
• Base-2  :        1.333 = 1 x 2^0 + 0 X 2^-1   + 1 x 2^-2  + 0 x 2^-3 + 1 x 2^-4 + ...

Solution :

PHP core includes two libraries to operate great numbers with arbitrary precision : BC Math (Binary Calculator Math) and GMP (GNU Multiple Precision).

Here is an example using BC Math substraction (6 digits precision) :

\$gap = bcsub(1.333, 1.1 + 0.233, 6);
echo '\$gap = ' . \$gap;

\$comp = bccomp(1.333, 1.1 + 0.233, 6);
echo '\$comp = ' . \$comp;

Result : \$gap = 0.000000 and \$comp = 0 (
bccomp() returns 0 if the 2 operands are equal) !!
Note that bcsub() accepts string as operand parameters and returns a string too.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Here is an example using GMP substraction :

\$gmp1 = gmp_init('1.333');
\$gmp2 = gmp_init('1.1 + 0.233');

\$gap = gmp_sub(\$gmp1, \$gmp2);
echo '\$gap = ' . gmp_strval(\$gap, 10);

\$comp = gmp_cmp(\$gmp1, \$gmp2);
echo '\$comp = ' . gmp_strval(\$comp, 10);

Result : \$gap = 0 and \$comp = 0 (gmp_cmp() returns 0 if the 2 operands are equal) !!
Note that GMP functions accepts GMP resources, numbers and strings as operand parameters and returns a GMP resource.
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

Conclusion :

Each time you need to operate float or double, use BC Math or GMP libraries to avoid errors and the comparison function bccomp() to compare them...