Search This Blog

4/23/2011

Float Type

The float type is the data type used to represent floating point numbers. In PHP, as in other programming languages, the float type has limits, so precision might be lost. In any case, I will explain that in this article.


Float Representation

The float type, because its practice use, often is represented in decimal base, as we see in integer representation. The diference is that float numbers has a simbol "." to separete the integer value of its decimal value. Some examples:

$f = 0.0;
$f = 1.0;
$f = 0.1;
$f = 0.0455;
$f = 123.0838;
$f = -239.134;

Note: it is possible to omit one side of the decimal point. If you omit one side, it is considered as zero. But you can omit only one side, never both. See the examples:

$f = .2;  // $f = 0.2;
$f = 2.;  // $f = 2.0;
$f = -.3; // $f = -0.3;
$f = -3.; // $f = -3.0;

In PHP, when a numeric value exceeds the higher integer allowed (it could be discovered by checking the value of the constant PHP_INT_MAX), it is automatically casted to float. So, it is possible to create a float number by the octal or hexadecimal base, but the value should be higher than PHP_INT_MAX. Or you can use casting.

$f = 0xFFFFFFFF; // $f = 4294967295.0;
$f = (float)0xF; // $f = 15.0;

PHP offers a way to represent float values with an "expoent of 10 notation". To do that, a suffix is included after a regular float notation (with decimal base). This suffix is composed by the letter "e" (without quotes and case-insensitive) followed by a positive or negative integer number, that indicates the value of the expoent of 10. This notation is useful to represent very high values, very low values, or values very close of zero.

// 5 * 103 = 5 * 1000 = 5000
$f = 5e3;

// -5 * 103 = -5 * 1000 = -5000
$f = -5E3;

// 5 * 10-3 = 5 * (1 / 103) = 5 * (1 / 1000) = 5 * 0,001 = 0,005
$f = 5e-3;

// -5 * 10-3 = -5 * (1 / 103) = -5 * (1 / 1000) = -5 * 0,001 = -0,005
$f = -5e-3;

Cast to Float

Int

A integer casted to float continue with the same numeric value, but is internally kept by a float type.

Bool

The "true" is converted to 1.0, while "false" is converted to 0.0.

Array

Empty array is converted to 0.0, while non-empty array is converted to 1.0.

String

The conversion from string to float is similar to a conversion from string to integer. The PHP interpreter try to extract the bigger portion of the string that can be converted to float (includding the letter "e" to represent the exponent of 10). If any portion from left to right can be converted, then it considers 0.0. See examples to understand (the red portion is ignored):

$f = floatval("1.1a");      // $f = 1.1;
$f = floatval("1.a");       // $f = 1.0;
$f = floatval(".1a");       // $f = 0.1;
$f = floatval("5e2");       // $f = 500.0;
$f = floatval("1.1e1");     // $f = 11;
$f = floatval("1.1e01");    // $f = 11;
$f = floatval("-23.4e-1x"); // $f = -0.23;
$f = floatval("3 4 5");     // $f = 3.0;
$f = floatval("x1");        // $f = 0;

Object

Objects can not be converted to float directly.


Formatting Float Numbers

There are not all countries that uses the same notation to express a real number (the symbol used to represent the thousand separator or the decimal point can be diferent). There are two ways to represent a number with a location convention. The first is to use the function number_format, where the programmer can tell wich symbol will be used as decimal point and thousand separator:

$f = 2500.7;
echo number_format($f, 1, ',', '.'); // Show: 2.500,7
echo number_format($f, 2, ',', '.'); // Show: 2.500,70
echo number_format($f, 0, ',', '.'); // Show: 2.501

The second way is specifying the locale by the function setlocale. This function modify the way some values are expressed. In case of float values, it would be used the follow code:

$f = 2500.7;

setlocale(LC_NUMERIC, 'pt_BR'); // Portuguese/Brazil
echo $f; // Show: 2500,7

setlocale(LC_NUMERIC, 'en'); // English
echo $f; // Show: 2500.7

// Change the locale to the default value (PHP notation)
setlocale(LC_NUMERIC, 'C');

The locale convention are defined by the plataform.


Lost Precision

While working with float values, you should understand that it keeps the value internally with certain limitations. For example, it is not posible to represent a decimates periodic. So, when you divide 1 by 3 (0.333...), you do not have the exact value.

See an example where happens lost precision:

$f = (0.1 + 0.7) * 10;
echo (int)$f;

This code shows 7, instead of 8. This happens because 0.7 is internally kept as 0.69999999999999995559. When this value is added by 0.1, its value is updated to 0.799999999999999, and multiplying by 10, is updated to 7.99999999999999. When this value is casted to integer, its decimal value is lost, so, the result is 7.

There is a solution: if you know the size of decimals in an operation, you can multiply by 10^N (where N is the size of decimals), then round the value to an integer. When the operation is over, you divide by the same value:


// Consider the size of decimals as "1"
$op1 = 0.7;
$op2 = 0.1;

// Multiply by 10^1
$op1 *= 10;
$op2 *= 10;

// Convert to Rounded Integer
$op1 = intval(round($op1));
$op2 = intval(round($op2));

// Operation
$result = ($op1 + $op2) * 10;

// Divide by 10^1
$result = $result / 10;

echo $result; // show: 8

This solution has some limitations. A better way to prevent lost precision is to make calculations with strings. The BC extension can do this:

$precision = 1;
$result = bcadd('0.7', '0.1', $precision);
$result = bcmul($result, '10', $precision);
echo (int)$result; // show: 8

No comments:

Post a Comment