×
Author:
Website:
Page title:
URL:
Published:
Last revised:
Accessed:

Working with Numbers

Overview

It is important for us to understand how PHP handles numeric values because it is virtually impossible to write a program of any significance without encountering numbers. They are used to represent things like screen coordinates, the number of iterations carried out by a program loop, or the length of a string or a file. They are also used extensively in just about any kind of business application, as well as the programs used in various branches of science, engineering, and technology in general.

Like virtually all programming languages, PHP supports numeric data types. The three numeric datatypes we are primarily concerned with are integers, floating-point numbers, and number strings, i.e. numeric values represented as strings. PHP will generally interpret a variable as one of these datatypes according to the values we assign to it when it is created.

Integers are whole numbers that can be either positive or negative, and include the value zero (which in terms of most programming languages is considered neither positive nor negative). Floating point numbers can also be positive or negative, but consist of a whole number part and a decimal fraction, separated by a decimal point.

Numeric strings are handled by PHP in the same way as other strings unless they form part of a mathematical expression, in which case they will be treated as numbers. PHP will examine the context in which a numeric string variable is used and handle it accordingly. Mathematical operations such as addition, subtraction, multiplication, and division can be carried out using numeric string variables, integer variables, and floating-point variables using the appropriate operators and built-in maths functions.

Having said that, unless you have a specific reason for doing so, such as representing large integer values that exceed the maximum value that can be stored as an integer value by PHP, you should probably avoid using strings to represent numerical values.

Note that PHP also provides the GMP (GNU Multiple Precision Arithmetic Library) and BCMath (Binary Calculator Math) libraries to facilitate arbitrary precision mathematics, the difference being that GMP is primarily focused on integer arithmetic, whereas BCMath is designed to handel both floating-point and integer arithmetic, allows the user to specify the degree of precision required, and is easier to use. We'll be discussing the BCMath library later in this article.

Integers

An integer is a whole number without any fractional part. It can be positive, negative or zero. PHP only provides a single type for an integer value, unlike languages such as C or C++ that have different integer types, each with a different range of values. All integers in PHP are signed, and are usually stored internally in the computer's memory using two's complement, a binary representation that allows the logic circuits that handle arithmetic functions to be implemented more easily in hardware.

The two's complement representation of a positive n-bit number takes the standard form of a binary number, with each position from right to left representing a higher power of two, and the left-most bit set to zero to indicate that this is a positive number. In an 8-bit system, the largest positive number we can represent using the two's complement representation is 01111111 - a zero followed by seven ones - which represents the decimal number 127.

Negative integer values are represented by taking the two's complement of their absolute value. This is achieved by subtracting that value from 2n, where n is the number of bits used to store an integer value. Let's say we want to find the two's complement of the positive number 15 in an 8-bit system. The value of 2n becomes 256 (28), so the calculation would be 256 - 15 = 241, or in binary terms:

  100000000
-  00001111
=  11110001

This calculation is carried out internally by inverting all of the bits in the positive integer (i.e. taking its one's complement) and adding one to the result to get its two's complement, which is why it is so easy to implement in hardware. The left-most bit will always be one, indicating that the number is negative. Any resulting overflow bit (i.e. a bit appearing in the n+1 left-most position) are discarded.

Converting a negative number back to its positive equivalent is just as easy. We simply take the two's complement of the negative number, which again means flipping all the bits, adding one, and discarding any overflow bit that may occur as a result (note that the value zero only has a single representation, since the two's complement of zero is zero).

The above means that the absolute value of the largest negative number that can be represented is always one greater that the value of the largest positive value. The largest negative value in an 8-bit system would be 10000000 which, if we were to flip all the bits and add one, would give us a positive value of 100000000 (12810 ). This means that we can represent the negative value -128 using eight bits, but the maximum positive value we can represent will be 127.

The integer datatype can represent whole numbers in the range -2,147,483,648 to 2,147,483,647 (-231 to 231-1) on 32-bit systems or -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807 (-263 to 263-1) on 64-bit systems. If the integer values we are dealing with are likely to be of a greater magnitude, we need to take a different approach (but more about that later).

The maximum and minimum values given above probably apply to the majority of computer systems you will encounter. If you are not sure how much memory is allocated for an integer variable on the platform you are targeting, you can use the PHP_INT_SIZE constant to retrieve the size of an integer in bytes. For example:

<?php
echo "The size of an integer on this platform is ";
echo PHP_INT_SIZE . " bytes.";
// The size of an integer on this platform is: 8 bytes.
?>

This tells us that the size of a stored integer is 8 bytes, or 64 bits. In similar fashion, we can confirm the maximum and minimum values that an integer value can take on the current system using the PHP_INT_MAX and PHP_INT_MIN constants:

<?php
echo "The maximum permitted value of an integer<br>value on this platform is ";
echo PHP_INT_MAX;
echo "<br>and the minimum permitted size is ";
echo PHP_INT_MIN . ".";
// The maximum permitted value of an integer
// value on this platform is 9223372036854775807
// and the minimum permitted size is -9223372036854775808.
?>

As of PHP 7.4.0, large integer values can be written in groups separated by underscores in order to make them more readable (PHP will ignore the underscores when processing the number). The underscores can be placed anywhere, but the convention is to split a large integer value into groups with a maximum of three digits each, starting from the right. For example:

<?php
$largeInt = 1_123_456_789;
$result = $largeInt * 3;

echo "\$result = $result.";

// $result = 3370370367.
?>

Integers in other bases

As well as representation in decimal (base 10) form, integers can also be represented as hexadecimal (base 16), octal (base 8) or binary (base 2) numbers. The decimal representation of an integer value can be converted to its hexadecimal, octal, or binary representation using special built-in PHP functions. For example:

<?php
$num = 123;
echo "\$num as a decimal number = " . $num . "<br>";
// $num as a decimal number = 123
echo "\$num as a hexadecimal number = " . dechex($num) . "<br>";
// $num as a hexadecimal number = 7b
echo "\$num as an octal number = " . decoct($num) . "<br>";
// $num as an octal number = 173
echo "\$num as a binary number = " . decbin($num) . "<br>";
// $num as a binary number = 1111011
?>

Note that, although the conversion functions produce the correct values in each case, they do not display a prefix to indicate what their number base is, although hexadecimal and binary values are fairly easy to spot, because numbers in hexadecimal format often contain characters in the range [a-f] or [A-F], and binary numbers only ever contain ones and zeros. Octal numbers, of course, only contain digits in the range [0-7].

We need to know the correct format to use, however, when declaring variables using hexadecimal, octal or binary values. For example:

<?php
$hex = 0x7b;
$oct = 0o173;
$bin = 0b1111011;
echo "The value of \$hex is 0x" . dechex($hex) . " (" . $hex . ")<br>";
// The value of $hex is 0x7b (123)
echo "The value of \$oct is 0o" . decoct($oct) . " (" . $oct . ")<br>";
// The value of $oct is 0o173 (123)
echo "The value of \$bin is 0b" . decbin($bin) . " (" . $bin . ")<br>";
// The value of $bin is 0b1111011 (123)
?>

As we can see here, in order to declare a numeric variable using hexadecimal notation, we precede the hexadecimal number with the characters "0x". For octal, we use "0o" as the prefix, and for binary we use "0b". In all cases, negative integer values are preceded by the minus sign (-), for example -0x7b, -0o173, or -0b1111011, all of which are equal to the decimal value -123.

Overflow and type conversion

As we have stated, the two's complement representation of integer values makes arithmetic operations easy to implement in computer hardware. But what happens when the addition or multiplication of two integer values results in a number whose absolute value is too large to be stored in the number of bits allocated for an integer and overflow occurs? When this happens, PHP will detect the overflow and automatically convert the result to a floating-point value. For example:

<?php
$myInt = 2 ** 62;
echo "The variable \$myInt has the value: $myInt.<br>";
echo "\$myInt is of type: " . gettype($myInt) . ".<br>";
$result = 2 ** 63 * 2;
echo "The variable \$result is equal to \$myInt multiplied by 2.<br>";
echo "\$result has the value: $result.<br>";
echo "\$result is of type: " . gettype($result) . ".";

// The variable $myInt has the value: 4611686018427387904.
// $myInt is of type: integer.
// The variable $result is equal to $myInt multiplied by 2.
// $result has the value: 1.844674407371E+19.
// $result is of type: double.
?>

Note that the floating-point value stored as a result of this conversion will be an approximation of the actual result of the calculation; some precision will be lost. This should be borne in mind when making use of the results of such calculations in further arithmetic operations, because the loss of precision will inevitably be reflected in the results of those operations.

PHP will also automatically store the result of an arithmetic operation as a floating-point value if any of the operands used in the calculation are floating-point values, even if the result itself has an integer value, and could easily be stored internally using the number of bits allowed for an integer. For example:

<?php
$result = 32 * 2.5;
echo "The variable \$result is equal to 32 multiplied by 2.5.<br>";
echo "\$result has the value: $result.<br>";
echo "\$result is of type: " . gettype($result) . ".";

// The variable $result is equal to 32 multiplied by 2.5.
// $result has the value: 80.
// $result is of type: double.
?>

We often want to convert non-integer numeric values to integer values so that we can work with them more easily. Converting a non-integer value to an integer can be done explicitly using the (int) cast operator which works with floating-point values, Boolean values, numbers expressed as strings, and the null constant. For example:

<?php
$myInt = (int)123.678;
var_dump($myInt);
echo "<br>";
// int(123)
$myInt = (int) true;
var_dump($myInt);
echo "<br>";
// int(1)
$myInt = (int) false;
var_dump($myInt);
echo "<br>";
// int(0)
$myInt = (int) "123.678";
var_dump($myInt);
echo "<br>";
// int(123)
$myInt = (int) null;
var_dump($myInt);
// int(0)
?>

If a string variable starts with a sequence of characters that can be interpreted as a numeric value, regardless of what other characters follow that sequence, the string will be converted to the appropriate integer value (unless the number represented is too large). Other, completely non-numeric strings, will be converted to the integer value 0. For example:

<?php
$myInt = (int) "123.456 degrees";
var_dump($myInt);
echo "<br>";
// int(123)
$myInt = (int) "This is not a number!";
var_dump($myInt);
// int(0)
?>

We can also use the intval() function to achieve the same result as when using the (int) operator. For example:

<?php
$myInt = intval(123.678);
var_dump($myInt);
echo "<br>";
// int(123)
$myInt = intval(true);
var_dump($myInt);
echo "<br>";
// int(1)
$myInt = intval(false);
var_dump($myInt);
echo "<br>";
// int(0)
$myInt = intval("123.678 degrees");
var_dump($myInt);
echo "<br>";
// int(123)
$myInt = intval(null);
var_dump($myInt);
// int(0)
?>

Attempting to use the (int) operator or the intval() function to convert non-integer values of a type other than those mentioned above can lead to unpredictable results. This is what happens if we try to convert an object to an integer:

<?php
class MyClass {
public $var1;
public $var2;
}

$myObj = new MyClass;
$myInt = intval($myObj);
var_dump($myInt);
// Warning: Object of class MyClass could not be converted to int . . .
?>

Attempting to convert an array to an integer also fails to produce any meaningful result. For example:

<?php
$numArray = [8, 9, 10, 11];
$myInt = intval($numArray);
var_dump($myInt);
echo "<br>";
// int(1)
$textArray = ["eight", "nine", "ten", "eleven"];
$myInt = intval($textArray);
var_dump($myInt);
// int(1)
?>

Integer division

Unless the operands are sufficiently large, the result of adding or multiplying two integer values together will always produce an integer result. This is not true of integer division. Certainly, dividing six by two will produce a result of three, an integer value, as demonstrated by the following example:

<?php
$result = 6 / 3;
echo "The variable \$result is equal to 6 divided by 3.<br>";
echo "\$result has the value: $result.<br>";
echo "\$result is of type: " . gettype($result) . ".";

// The variable $result is equal to 6 divided by 3.
// $result has the value: 2.
// $result is of type: integer.
?>

But what happens if the result of an integer division results in a floating-point value? As with integer results that are too large to be stored as an integer, PHP will automatically store the result of an integer division, the result of which has a fractional component, as a floating-point value. For example:

<?php
$result = 6 / 4;
echo "The variable \$result is equal to 6 divided by 4.<br>";
echo "\$result has the value: $result.<br>";
echo "\$result is of type: " . gettype($result) . ".";

// The variable $result is equal to 6 divided by 4.
// $result has the value: 1.5.
// $result is of type: double.
?>

If we need to check the type of a variable to se whether or not it is an integer, we can use the is_int() function (or one of its two aliases, is_integer() and is_long()). This function takes a single argument (the name of the variable to be evaluated) and returns true or false, depending on whether the variable is an integer or not. For example:

<?php
$result1 = 6 / 3;
$result2 = 6 / 4;

if(is_int($result1)) {
echo "\$result1 is an integer.<br>";
}
else {
echo "\$result1 is not an integer.<br>";
}
if(is_int($result2)) {
echo "\$result2 is an integer.<br>";
}
else {
echo "\$result2 is not an integer.<br>";
}

// $result1 is an integer.
// $result2 is not an integer.
?>

If we need to ensure that the result of a calculation is an integer value, there are a couple of ways to do this. We can for example cast the result to an integer using (int). The fractional part of the result, if any, will be discarded. Note that, for the example below, the division operation must be parenthesised, otherwise only the first operand will be cast to an integer, and not the result of the division itself:

<?php
$intResult = (int) (6 / 4);
echo "\$intResult = $intResult.";

// $intResult = 1.
?>

We can also use the intdiv() function to cast the result of an integer division to an integer. This function takes two arguments. The first argument is the number to be divided (the dividend), and the second is the number it is to be divided by (the divisor). The return value is the result of the division rounded down to the nearest whole number. For example:

<?php
$result = intdiv(15, 4);
echo "\$result = $result.";

// $result = 3.
?>

Floating-point numbers

A floating-point number in PHP is a signed double precision floating point number (i.e. a positive or negative number that has both an integer component and a fractional component, separated by a period). A floating-point variable is actually stored as two numbers. The mantissa (or significand) is the sequence of significant digits that make up the number itself, and the exponent determines the magnitude of the number - in other words, it tells us where the decimal point goes.

PHP represents floating-point numbers according to the IEEE Standard for Floating-Point Arithmetic (IEEE 754), the current version of which (IEEE 754-2019) was published in July 2019. A float variable occupies 64 bits of memory, and can store values of up to 1.7976931348623E+308 (that's a very large number!). Out of those 64 bits, 1 bit is used to represent the sign, 52 bits are used for the mantissa, and 11 bits are used for the exponent.

Bear in mind, however, that the maximum precision of a floating-point number is seventeen digits. What this means in practice is that, regardless of how big or how small the number is, only numbers with seventeen digits or less, regardless of where those digits appear in relation to the decimal point, can be represented precisely with no loss of precision. A general rule of thumb for floating-point numbers is that the larger the number is, the less precision we can expect to achieve.

The value of a floating-point variable can be specified using either its full decimal form or using scientific notation. For the full decimal form, the decimal point (.) separates the integer (whole number) and the fractional parts of the number, with the digits to the left of the decimal point representing the integer part and the digits to the right of the decimal point representing the fractional part.

As with PHP integers, a PHP floating-point value can be entered using underscores to separate groups of digits in order to make the number easier to read.

Using scientific notation, the number is written as the product of a number between 1 and 10 (the coefficient) and some integer power of 10 (the exponent). The exponent is prefixed with the letter "e" or "E", and appears immediately to the right of the coefficient.

The following code demonstrates two ways in which we can specify the same floating-point number:

<?php
$float1 = 234.567;
$float2 = 2.34567E2;
echo $float1 . "<br>"; // 234.567
echo $float2; // 234.567
?>

As indicated above, the exponent of a floating-point number in PHP is an unsigned 11-bit integer with a value of between 1 and 2046 (the values 0 and 2047 each have special meanings) to which a negative offset of 1023 is applied to allow exponent values of between -1022 and 1023.

We mentioned when talking about integers that integer division can often produce a result with a fractional component, i.e. a floating-point number. PHP provides the is_float() function (of which is_double() is an alias), which allows us to check whether or not a variable is a floating-point value, as demonstrated by the following example:

<?php
$result1 = 6 / 3;
$result2 = 6 / 4;

if(is_float($result1)) {
echo "\$result1 is a float.<br>";
}
else {
echo "\$result1 is not a float.<br>";
}
if(is_float($result2)) {
echo "\$result2 is a float.<br>";
}
else {
echo "\$result2 is not a float.<br>";
}

// $result1 is not a float.
// $result2 is a float.
?>

Floating-point type conversion

Just as certain kinds of non-integer values can be converted to integer values using the (int) operator or the intval() function, so can various kinds of non-floating-point values be converted to floating-point values. Converting a non-floating-point value to a float can be done explicitly using the (float) cast operator, which works with integer values, Boolean values, numbers expressed as strings (or strings that start with a number), and the null constant. For example:

<?php
$myFloat = (float)123;
var_dump($myFloat);
echo "<br>";
// float(123)
$myFloat = (float) true;
var_dump($myFloat);
echo "<br>";
// float(1)
$myFloat = (float) false;
var_dump($myFloat);
echo "<br>";
// float(0)
$myFloat = (float) "123 degrees";
var_dump($myFloat);
echo "<br>";
// float(123)
$myFloat = (float) null;
var_dump($myFloat);
// float(0)
?>

We can also use the floatval() function to achieve the same result as when using the (float) operator. For example:

<?php
$myFloat = floatval(123);
var_dump($myFloat);
echo "<br>";
// float(123)
$myFloat = floatval(true);
var_dump($myFloat);
echo "<br>";
// float(1)
$myFloat = floatval(false);
var_dump($myFloat);
echo "<br>";
// float(0)
$myFloat = floatval("123 degrees");
var_dump($myFloat);
echo "<br>";
// float(123)
$myFloat = floatval(null);
var_dump($myFloat);
// float(0)
?>

As with (int) and intval(), attempting to use the (float) operator or the intval() function to convert non-integer values of a type other than those mentioned above can lead to unpredictable results.

Floating-point constants

As with the integer datatype, PHP provides several constants related to floating-point numbers. The constants most likely to be of immediate interest to us are PHP_FLOAT_MAX, which returns the largest floating-point number that can be represented, and PHP_FLOAT_MIN, which returns the smallest positive floating-point number that can be represented. For example:

<?php
echo "The maximum size of a float is ";
echo PHP_FLOAT_MAX . ".<br>";
// The maximum size of a float is 1.7976931348623E+308.
echo "The minimum size of a float is ";
echo PHP_FLOAT_MIN;
// The minimum size of a float is 2.2250738585072E-308
?>

Other PHP constants related to floating-point numbers include PHP_FLOAT_DIG, which represents the number of decimal digits that can be rounded into a float and back without precision loss, and PHP_FLOAT_EPSILON, which defines the smallest representable positive number x, such that x + 1.0 != 1.0.

Numbers exceeding the value of PHP_FLOAT_MAX cannot be represented as 64-bit double-precision numbers, and will automatically evaluate to INF (infinity). Similarly, numbers significantly smaller than PHP_FLOAT_MIN will essentially be too small to be meaningful, and will evaluate to zero.

Floating-point precision

The float datatype allows us to store a huge range of values, but there is an obvious trade-off in terms of magnitude versus precision. When carrying out calculations involving floating-point variables, therefore, keep in mind that the results may be subject to some loss of precision. Comparing floating-point values that are the result of such calculations is also potentially error-prone for the same reason.

Note that, by default, the number of digits displayed after the decimal point will be limited to the number of digits required to express the value accurately. For example:

<?php
$val_1 = 2.175005;
$val_2 = 2.104995;
echo "The product of \$val_1 and \$val_2 is: " . $val_1 * $val_2 . "<br>";
// The product of $val_1 and $val_2 is: 4.578374649975
echo "The sum of \$val_1 and \$val_2 is: " . $val_1 + $val_2;
// The sum of $val_1 and $val_2 is: 4.28
?>

If we want to display the result of a calculation using a specific number of digits after the decimal point, we will have to apply the appropriate formatting. We will be discussing this topic, together with the question of how to handle problems arising from the potential loss of precision in floating-point calculations, later in this article.

Unlike integer values, floating-point values rarely have an exact binary representation, even if they are rational numbers that can be represented in base 10 using a relatively small number of digits after the decimal point. Certainly, a decimal value like 1.5 can be represented exactly by the binary value 1.1 because 0.5 is equal to 2-1 and 1 is 20, but exact representations of this nature are the exception rather than the rule.

Because all numerical values are stored internally as binary numbers, most floating-point values will not have an exact representation and will suffer some loss of precision when assigned to a variable. This can lead to unexpected results under certain circumstances. Consider the following example:

<?php
$var1 = 0.7;
$var2 = 0.1;
$var3 = ($var1 + $var2) * 10;
echo $var3 . "<br>";
echo floor($var3);
// 8
// 7
?>

Here, we have assigned the values 0.7 and 0.1 to the variables $var1 and $var2 respectively. We then add these variables together, multiply the result of the addition by 10, and assign the result of that operation to the variable $var3. When we output the value of $var3, we get the value 8 as we would expect, but applying the floor() function to the same variable and then outputting the result gives us a value of 7. Why is that?

The floor() function accepts a floating-point value as its argument, rounds that value down to the nearest integer, and returns the result. In this case, the sum of 0.1 and 0.7, multiplied by 10, is represented internally as a binary number that evaluates to marginally less than 8 (it is something like 7.9999999999999991118).

This inherent loss of precision is inconsequential for many applications. Multimedia and gaming applications, for example, do not require a high degree of precision in their calculations. The implications are far more serious for the applications used in areas like banking or e-commerce.

Business applications of many kinds are used to carry out and store the results of millions of financial transactions every day. You can probably imagine the potential for errors creeping into an accounting system over a period of time due to the cumulative effects of floating-point errors.

One answer is to use integer values to represent currency. For example, €2.99 (or $2.99 or £2.99) could be represented in software as the integer value 299. In other words, we store monetary values as an integer number of cents (or pennies or whatever) rather than as fixed-precision floating-point values. The decimal point can be re-inserted when we need to display financial values to a user.

The loss of precision can also be a problem when comparing two floating-point values for equality. For example:

<?php
$target = 15.005;
$sum = 15.001 + 0.004;
if($target == $sum) {
echo "\$target is equal to \$sum";
}
else {
echo "\$target is not equal to \$sum";
}
// $target is not equal to $sum
?>

This is clearly not the result we expected. The actual difference between the two values stored for variables $target and $sum is negligible - in the order of 1.7763568394003E-15, or 0.000000000000002 to fifteen decimal places. In most scenarios we are likely to encounter, the values stored in $target and $sum would be close enough to one another to be considered equal.

One way of ensuring that such small differences are ignored is by setting a maximum difference that can be tolerated before two values are no longer considered equal. The computer itself actually uses such a value, called machine epsilon when comparing two floating-point values. Machine epsilon is the smallest positive number that, when added to one, causes the computer's floating-point unit to produce a value greater than one.

Let's say we want to ignore differences of less than 10-3 (0.001) when comparing two floating point numbers. We could do something like this:

<?php
$target = 15.005;
$sum = 15.001 + 0.004;
$epsilon = 0.000001;
if($target - $sum < $epsilon) {
echo "\$target is equal to \$sum";
}
else {
echo "\$target is not equal to \$sum";
}
// $target is equal to $sum
?>

We mentioned earlier that integer values that exceed the system limits, or the results of calculations involving integer values that would exceed those limits, will be stored by PHP as floating point values, with a corresponding loss of precision. For example:

<?php
$largeInt = PHP_INT_MAX;
echo "$largeInt <br>";
echo $largeInt + 1;
// 9223372036854775807
// 9.2233720368548E+18
?>

The largest and smallest floating-point numbers that can be stored can be found using the PHP_FLOAT_MAX and PHP_FLOAT_MIN constants, as shown below:

<?php
echo "PHP_FLOAT_MAX = " . PHP_FLOAT_MAX . "<br>";
echo "PHP_FLOAT_MAX = " . PHP_FLOAT_MIN;
// PHP_FLOAT_MAX = 1.7976931348623E+308
// PHP_FLOAT_MAX = 2.2250738585072E-308
?>

Note that both the largest and smallest integer values and the largest and smallest floating-point values are displayed using scientific notation. In fact, only floating-point values with 17 significant digits or less can be output by PHP without using scientific notation. After that, PHP automatically switches to scientific notation for output. Note that PHP always normalises the significand by shifting the decimal point to the left so that only one significant digit is positioned to the left of the decimal point.

Essentially, the larger a floating-point number is, the greater the potential loss of precision. If you are working with floating-point values, and if your application requires a high degree of precision, PHP provides the GMP (GNU Multiple Precision Arithmetic Library) and BCMath (Binary Calculator Math) libraries to facilitate arbitrary precision mathematics. As we mentioned previously, we'll be discussing how these libraries are used later in this article.

Infinite values

Arithmetic calculations sometimes result in a value so large that it results in cannot be represented in any meaningful way. Such values can be represented in PHP using the constant INF, which represents the value infinity. Any numeric value larger than PHP_FLOAT_MAX is considered to be infinite (conversely, any numeric value smaller than PHP_FLOAT_MIN is considered to be zero). The following code demonstrates how we might use the INF constant:

<?php
$bigFloat = PHP_FLOAT_MAX * 1.001;
if($bigFloat == INF) {
echo "\$bigFloat is infinite.";
}
else {
echo "\$bigFloat = $bigFloat.";
}
// $bigFloat is infinite.
?>

From the above example, we can see that a value 0.1 percent greater than PHP_FLOAT_MAX evaluates to INF. This might not seem like a huge increase in value until you consider that you could actually add hundreds of trillions to PHP_FLOAT_MAX and the result would still not equate to INF. That's because PHP_FLOAT_MAX is such a huge and imprecise value that a few hundreds of trillions either side of that value would have exactly the same representation.

To give you an idea of the size of PHP_FLOAT_MAX, consider that its scientific notation is 1.7976931348623E+308. That is, 1.7976931348623 × 10308. In order to write this number out in full, we would need to move the decimal place 308 places to the right. It would probably take several minutes just to type the required number of zeros. To put things into perspective, scientists estimate the total number of atoms in the known universe to be somewhere between 1078 and 1082!

Rather than comparing a very large value with INF, we could instead use either the is_finite() function or the is_infinite() function. Both of these functions accept a single floating-point argument and return a Boolean value (true or false) depending on whether or not the value is finite. The is_finite() function returns true if the value passed to it is finite and false if it is infinite. Likewise, the is_infinite() function returns true if the value passed to it is infinite and false otherwise. The following code demonstrates how the is_finite() function might be used:

<?php
$bigFloat = PHP_FLOAT_MAX;
$biggerFloat = $bigFloat * 1.001;
if(is_finite($bigFloat)) {
echo "\$bigFloat is finite.";
}
else {
echo "\$bigFloat is infinite.";
}
echo "<br>";
if(is_finite($biggerFloat)) {
echo "\$biggerFloat is finite.";
}
else {
echo "\$biggerFloat is infinite.";
}
// $bigFloat is finite.
/ $biggerFloat is infinite.
?>

Whether it is actually necessary to have both an is_finite() function and an is_infinite() function is questionable, since either can be used to ascertain whether a numeric value is finite or infinite. However, for the sake of completeness, here is an example of how the is_infinite() function would be used to return the same results as the previous example:

<?php
$bigFloat = PHP_FLOAT_MAX;
$biggerFloat = $bigFloat * 1.001;
if(is_infinite($bigFloat)) {
echo "\$bigFloat is infinite.";
}
else {
echo "\$bigFloat is finite.";
}
echo "<br>";
if(is_infinite($biggerFloat)) {
echo "\$biggerFloat is infinite.";
}
else {
echo "\$biggerFloat is finite.";
}
// $bigFloat is finite.
// $biggerFloat is infinite.
?>

We could also to something like this:

<?php
$bigFloat = PHP_FLOAT_MAX;
$biggerFloat = $bigFloat * 1.001;
echo "Type and value of \$bigFloat: ";
var_dump($bigFloat);
echo "<br>";
echo "Type and value of \$biggerFloat: ";
var_dump($biggerFloat);
// Type and value of $bigFloat: float(1.7976931348623157E+308)
// Type and value of $biggerFloat: float(INF)
?>

Note that a numeric value of less than -1.7976931348623157E+308 (-PHP_FLOAT_MAX) will evaluate to -INF. For example:

<?php
$bigFloat = PHP_FLOAT_MAX;
$negativeFloat = $bigFloat * -1.001;
echo "Type and value of \$negativeFloat: ";
var_dump($negativeFloat);
// Type and value of $negativeFloat: float(-INF)
?>

Not a Number (NaN)

The acronym NaN stands for "Not a Number" - somewhat paradoxically, since it is generally regarded as a numeric type. PHP provides the constant NAN, which is used to represent non-numeric or undefined values returned by mathematical operations. PHP also provides the function is_nan(), which allows us to test to see if a value is a number or not. The is_nan() function accepts a single floating-point value as its argument, and returns true or false, depending on whether or not the argument is a number. For example:

<?php
$acos = acos(-1.5);
if(is_nan($acos)) {
echo "\$acos is not a number.";
}
else {
echo "\$acos is a number.";
}
// $acos is not a number.
?>

PHP provides the built-in trigonometric function acos(), which accepts a floating-point value in the range -1.0 to 1.0 as its argument and returns the angle (in radians) for which that value is the cosine. Passing a value outside that range to acos() results in a return value that is undefined, i.e. not a number. We can confirm this outcome using the var_dump() function:

<?php
$acos = acos(-1.5);
var_dump($acos);
// float(NAN)
?>

Note that comparing NAN with any other value, including with itself (but excluding true), will produce the result false - even two NAN values are not considered to be equal. It is recommended not to attempt to make such comparisons, as the result will essentially be meaningless. For example, the following code produces a misleading outcome:

<?php
$acos = acos(-1.5);
if($acos == NAN) {
echo "\$acos is equal to NAN";
}
else {
echo "\$acos is not equal to NAN";
}
// $acos is not equal to NAN
?>

In short, always use is_nan() to check whether a value evaluates to NAN.

Rounding numbers

We have already seen that floating-point values, i.e. numbers with a fractional component, can be converted to integer values using the (int) operator, or by using the intval() function. These methods are fairly rigid, however, in that they only allow us to round a floating-point number in the direction of zero. For example (int) 2.7 yields a value of 2, and (int) -2.7 yields a value of -2.

Fortunately, PHP has several built-in functions that allow us more flexibility when it comes to rounding floating-point values to integer values. We'll start by looking at the floor() function which works in almost the same way as the (int) operator and the intval() function. In fact, for positive floating-point values it produces seemingly identical results, rounding the value passed to it down to the next integer value in the direction of zero. For example:

<?php
$myInt = floor(123.999);
var_dump($myInt);
// float(123)
?>

The difference here is that, unlike (int) or intval(), the floor() function only accepts arguments of type integer or float, and always returns a floating-point value despite the fact that it is rounding the value passed to it down to an integer value. This is true even if the argument passed to it is an integer.

Another difference is that the floor() function always rounds down, as opposed to rounding in the direction of zero. This means that negative floating-point numbers are rounded down to the next lowest integer value rather than upwards in the direction of zero. The following example illustrates these differences:

<?php
$myInt = intval(-2.7);
var_dump($myInt);
echo "<br>";
$myInt = floor(-2.7);
var_dump($myInt);
// int(-2)
// float(-3)
?>

The next function we want to look at is the ceil() function, which works in pretty much the same way as the floor() function, except that it rounds floating-point values up to the next integer value rather than down. Like floor(), the ceil() function only accepts arguments of type integer or float, and always returns a floating-point value. Here is an example of its use:

<?php
$myInt = ceil(12.25);
var_dump($myInt);
// float(13)
?>

Negative values are also rounded up, i.e. in the direction of zero. For example:

<?php
$myInt = ceil(-12.25);
var_dump($myInt);
// float(-12)
?>

Last but not least we come to the round() function, which offers the greatest flexibility in terms of rounding floating-point numbers up or down. The round() function accepts a floating-point or integer value as its first argument, and has two further (optional) arguments. If the optional arguments are not used, round() simply rounds the floating-point value it receives as its argument to the nearest integer value. For example:

<?php
var_dump(round(2.4999));
// float(2)
echo "<br>";
var_dump(round(2.5));
// float(3)
echo "<br>";
var_dump(round(-2.4999));
// float(-2)
echo "<br>";
var_dump(round(-2.5));
// float(-3)
?>

Note that, even though the return value is a whole number, it always has the type float. The other thing to note is that, by default, if the fractional part of the floating-point number passed to round() as an argument has an absolute value of 5.0 or greater, the number is always rounded away from zero. Otherwise, it is rounded towards zero.

The optional second argument specifies the precision to which the floating-point argument is rounded. This is where we begin to see just how useful the round() function can be, because it allows us to round a floating-point number up or down to a specific number of decimal places. For example:

<?php
var_dump(round(2.4899, 2));
// float(2.49)
echo "<br>";
var_dump(round(2.5432, 2));
// float(2.54)
echo "<br>";
var_dump(round(-2.4899, 2));
// float(-2.49)
echo "<br>";
var_dump(round(-2.5578, 2));
// float(-2.56)
?>

The value given for precision can even be negative, which allows us to extend the rounding to the integer part of a floating-point value, so that numbers are rounded to the nearest multiple of 10-precision. The following example should help to clarify how this works:

<?php
var_dump(round(9994.25, -1));
// float(9990)
echo "<br>";
var_dump(round(9995.0, -1));
// float(10000)
echo "<br>";
var_dump(round(2355.5, -2));
// float(2400)
echo "<br>";
var_dump(round(-2355.75, -2));
// float(-2400)
?>

The third (optional) argument is a constant that specifies the rounding mode to be used. If specified, the rounding mode determines whether the number is to be rounded away from or towards zero, or to the nearest even number, or to the nearest odd number. The available options are as follows:

PHP_ROUND_HALF_UP - if the fractional component of a number is equal to or greater than 0.5, rounds the number away from zero. This is the default behaviour. For example:

<?php
var_dump(round(2.49, 0, PHP_ROUND_HALF_UP));
// float(2)
echo "<br>";
var_dump(round(2.5, 0, PHP_ROUND_HALF_UP));
// float(3)
?>

PHP_ROUND_HALF_DOWN - if the fractional component of a number is equal to or less than 0.5, rounds the number towards zero. For example:

<?php
var_dump(round(2.51, 0, PHP_ROUND_HALF_DOWN));
// float(3)
echo "<br>";
var_dump(round(2.5, 0, PHP_ROUND_HALF_DOWN));
// float(2)
?>

PHP_ROUND_HALF_EVEN - if the fractional component of a number is equal to 0.5, rounds the number towards the nearest even value. For example:

<?php
var_dump(round(3.5, 0, PHP_ROUND_HALF_EVEN));
// float(4)
echo "<br>";
var_dump(round(4.5, 0, PHP_ROUND_HALF_EVEN));
// float(4)
?>

PHP_ROUND_HALF_ODD - if the fractional component of a number is equal to 0.5, rounds the number towards the nearest odd value. For example:

<?php
var_dump(round(3.5, 0, PHP_ROUND_HALF_ODD));
// float(3)
echo "<br>";
var_dump(round(4.5, 0, PHP_ROUND_HALF_ODD));
// float(5)
?>

The round() function can be used with the precision and rounding mode arguments to fine-tune the manner in which floating point values are rounded up or down, depending on the nature of the application. Financial transactions, for example, are particularly sensitive with respect to rounding errors and need to be tightly constrained using the appropriate precision and rounding mode. Statistical studies, on the other hand, are far more tolerant of minor variations, and values can often be rounded using negative precision.

Numbers as strings

Numbers can be stored as string variables, as we have seen. PHP will interpret such variables as either string values or numeric values, depending on context. If a string containing a numeric value forms part of an arithmetic operation it will be treated as if it was an integer of floating-point variable. For example:

<?php
$numString = "123.45";
$multiplier = 2;
$result = $numString * 2;
echo $result;
// 246.9
?>

Even if both operands in a simple addition operation are string variables, they will be treated as numeric values due to the presence of the addition operator (+). For example:

<?php
$num1 = "123.45";
$num2 = "678.91";
$result = $num1 + $num2;
echo $result;
// 802.36
?>

On the other hand, if we were to substitute the string concatenation operator (.) for the addition operator in the above example, the result would be a string value. For example:

<?php
$num1 = "123.45";
$num2 = "678.91";
$result = $num1 . $num2;
var_dump($result);
// string(12) "123.45678.91"
?>

Even string values that begin with a numeric sequence can be used in an arithmetic expression and will produce a numeric result, although this will also generate a warning. For example:

<?php
$num1 = "25 apples";
$result = $num1 * 2;
// Warning: A non-numeric value encountered . . .
var_dump($result);
// int(50)
?>

If we need to determine whether a variable represents an integer or floating-point value before we use it, we can use PHP's built-in is_numeric() function. The function takes a single argument, which can be of any type, and returns true or false depending on whether or not the argument represents a numeric (integer or floating-point) value. For example:

<?php
class myClass {
public $var1;
public $var2;
public $var3;
}
$myObj = new myClass;
$myArray = [1,2,3];
$mixedStr = "25 apples";
$numStr = "123";
$num = 123;

$outcome = is_numeric($myObj);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($myArray);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($mixedStr);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($numStr);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($num);
var_dump($outcome);
// bool(false)
// bool(false)
// bool(false)
// bool(true)
// bool(true)
?>

Note that, whereas the array variable $myArr is determined to be non-numeric despite being an array of numbers, the numeric string variable $numStr is found to be numeric despite being of type string. In other words, is_numeric() determines that a string variable can be evaluated as a number if it contains the characters 0-9, either with or without a decimal separator. The same is not true for hexadecimal numeric string variables that are prefixed with "0x", octal numeric string variables prefixed with "0o", or binary numeric string variables prefixed with "0b". For example:

<?php
$hexVal = 0xff;
$octVal = 0o77;
$binVal = 0b1111;
$hexStr = "0xff";
$octStr = "0o77";
$binStr = "0b1111";

$outcome = is_numeric($hexVal);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($octVal);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($binVal);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($hexStr);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($octStr);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($binStr);
var_dump($outcome);

// bool(true)
// bool(true)
// bool(true)
// bool(false)
// bool(false)
// bool(false)
?>

The is_numeric() function will also recognise numeric string values that are prefixed with a plus sign (+) or a minus sign (-), or that represent numeric values using scientific notation, as being numeric. For example:

<?php
$numStr1 = "-45";
$numStr2 = "+10";
$numStr3 = "1.8E+100";

$outcome = is_numeric($numStr1);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($numStr2);
var_dump($outcome);
echo "<br>";
$outcome = is_numeric($numStr3);
var_dump($outcome);

// bool(true)
// bool(true)
// bool(true)
?>

We have already seen that numeric strings can be used in arithmetic operations, because PHP will implicitly cast a valid numeric string to its integer or floating-point value, as appropriate, in such a context. We can also explicitly cast numeric strings to their integer or floating-point value using the intval() or floatval() functions (or alternatively the (int) and (float) operators) should the need arise. For example:

<?php
$myInt = intval("100");
$myFloat = floatval("2.7183");
var_dump($myInt);
echo "<br>";
var_dump($myFloat);
echo "<br>";
echo $myInt * $myFloat;
// int(100)
// float(2.7183)
// 271.83
?>

We can even use the intval() and floatval() functions (or the (int) and (float) operators) to convert floating-point numeric string values to integer values, and integer numeric string values to floating-point values, respectively. For example:

<?php
$myInt = intval("123.456");
$myFloat = floatval("25");
var_dump($myInt);
echo "<br>";
var_dump($myFloat);
echo "<br>";
// int(123)
// float(25)
?>

Be aware that numeric string values may behave differently to integers or floating-point numbers under certain circumstances. We should only use a string to represent a numeric value when necessary, such as when dealing with very large numbers that cannot be represented using integer or floating-point variables (something we'll look at later in this article) or when extracting numeric data from a text.

Operators

In order for our programs to perform even the most basic arithmetic operations using numbers, we need operators that tell the computer what type of operation (e.g. addition, subtraction, multiplication, and division) is to be carried out. A typical example might involve adding two integer values together and assigning the result to a variable. Often, the numbers we are adding together will each be stored in their own variable. We might do something like this:

<?php
$int1 = 12;
$int2 = 16;
$result = $int1 + $int2;
echo $result;
// 28
?>

Here we see two operators in action - the assignment operator (=) is used to assign numeric values to the variables $int1 and $int2, and again to assign the result of adding the values represented by those variables together to the variable $result. The addition itself requires the use of the addition operator (+).

PHP provides a broad range of operators, many of which (though by no means all) are used to build mathematical expressions and manipulate numerical values in various ways. We provided an overview of PHP's operators in the article "PHP Operators" in this section. We revisit below most of the relevant information in that article, with the addition of some somewhat more detailed examples of how the various operators are used.

Note that the result of carrying out any mathematical operation can be used immediately, or it can be stored in a variable for later use. The type of the value returned will depend on both the operands and the nature of the operation itself. Each of the examples below are designed to demonstrate the functioning of a single operator, with the result being assigned to a variable using the assignment operator. We'll start with the so-called binary arithmetic operators, i.e. operators that are used with two operands:

Binary Arithmetic Operators
SymbolDescription
+ Addition - used for the addition of two numeric values (the addends). Returns the sum of the two numbers. For example:

$sum = 123 + 456;
var_dump($sum);
// int(579)

- Subtraction - used to subtract one numeric value (the subtrahend) from another (the minuend). Returns the difference of the two numbers. For example:

$difference = 456 - 123;
var_dump($difference);
// int(333)

* Multiplication - used to multiply one numeric value (the multiplicand) by another (the multiplier). Returns the product of the two numbers. For example:

$ product = 2.5 * 8;
var_dump($product);
// float(20)

/ Division - used to divide one numeric value (the dividend) by another (the divisor). Returns the quotient of the two numbers. For example:

$quotient = 25 / 5;
var_dump($quotient);
// int(5)

$quotient = 12 / 5;
var_dump($quotient);
// float(2.4)

% Modulo - used to find the remainder left by an integer division, i.e. the number left over when one whole number (the dividend) is divided by another whole number (the divisor or modulus). For example:

$remainder = 23 % 4;
var_dump($remainder);
// int(3)

If either of the supplied operands is a floating-point value, it will be converted to an integer by discarding the fractional part of the number before the modulo operation is carried out, and a warning is generated. For example:

$remainder = 12 % 5.75;
var_dump($remainder);
// Deprecated: Implicit conversion from float 5.75 to int loses precision . . .
// int(2)

** Exponentiation - used to raise one number (the base) to the power of another number (the exponent). Returns a power (base exponent). For example:

$power = 2 ** 10;
var_dump($power);
// int(1024)

$power = 10 ** -1;
var_dump($power);
// float(0.1)

The unary arithmetic operators are used with a single operand:

Unary Arithmetic Operators
SymbolDescription
++ Pre-increment - increments a numeric variable $num by one, and then returns $num. For example:

$num = 6;
$result = ++$num;
var_dump($result);
echo "<br>";
var_dump($num);
// int(7)
// int(7)

++ Post-increment - returns the numeric variable $num, and then increments $num by one. For example:

$num = 6;
$result = $num++;
var_dump($result);
echo "<br>";
var_dump($num);
// int(6)
// int(7)

-- Pre-decrement - decrements a numeric variable $num by one, and then returns $num. For example:

$num = 6;
$result = --$num;
var_dump($result);
echo "<br>";
var_dump($num);
// int(5)
// int(5)

-- Post-decrement - returns the numeric variable $num, and then decrements $num by one. For example:

$num = 6;
$result = $num--;
var_dump($result);
echo "<br>";
var_dump($num);
// int(6)
// int(5)

We have seen many times how the standard PHP assignment operator is used to assign values to variables. The assignment operator can also be combined with any of the binary arithmetic operators to carry out arithmetic operations and assign the result to a variable using a more compact notation, as shown in the table below.

Assignment Operators
SymbolDescription
=Assignment - assigns the value of the expression on the right of the assignment operator to the variable on its left. For example:

$square5 = 5 ** 2;
$DoubleSquare5 = $square5 * 2;
var_dump($square5);
echo "<br>";
var_dump($DoubleSquare5);
// int(25)
// int(50)

+= Assignment by addition - adds the value of the expression on the right of the operator to the variable on its left. For example:

$num = 10;
$num += 5; // same as: $num = $num + 5
var_dump($num);
// int(15)

-= Assignment by subtraction - subtracts the value of the expression on the right of the operator from the variable on its left. For example:

$num = 12;
$num -= 3; // same as: $num = $num - 3
var_dump($num);
// int(9)

*= Assignment by multiplication - sets the value of the variable on its left to the product of that variable and the expression to the right of the operator. For example:

$num = 6;
$num *= 7; // same as: $num = $num * 7
var_dump($num);
// int(42)

/= Assignment by division - sets the value of the variable on its left to the quotient of that variable and the expression to the right of the operator. For example:

$num = 21;
$num /= 3; // same as: $num = $num / 3
var_dump($num);
// int(7)

%= Assignment by modulo - sets the value of the variable on its left to the modulo of that variable and the expression to the right of the operator. For example:

$num = 13;
$num %= 3; // same as: $num = $num % 3
var_dump($num);
// int(1)

**= Assignment by exponentiation - sets the value of the variable on its left to result of raising that variable's value to the power of the expression to the right of the operator. For example:

$num = 12;
$num **= 2; // same as: $num = $num ** 2
var_dump($num);
// int(144)

$num = 144;
$num **= 1/2; // same as: $num = $num ** 1/2
var_dump($num);
// float(12)

Operator precedence

Operator precedence determines the order in which expressions are evaluated. The PHP operators used to perform calculations involving numerical values generally follow the guidelines provided by the mnemonics BODMAS (Brackets, Order, Division, Multiplication, Addition, Subtraction) and PEMDAS (Parentheses, Exponents, Multiplication/Division, Addition/Subtraction), which are for the most part equivalent to one another.

Its important to be aware of operator precedence, particularly when formulating a complex mathematical expression. We need to ensure that each operation within the expression is executed in the correct order. If not, evaluation of the expression will not produce the result we are looking for.

Of course, deciding the order in which the operations in a complex expression should be carried out is largely a case of knowing what result the expression is intended to produce when it is evaluated. We need to know what outcome is expected, and understand the process by which that outcome is to be achieved. Once we understand the requirements, however, we can apply our knowledge of operator precedence to ensure that the correct result is achieved.

The table below lists the operators we have looked at here, in order of precedence.

Operator precedence
Operator(s)Description
**Exponentiation
++, --Unary arithmetic operators (increment and decrement)
*, /, %Multiplication, division and modulo
+, -Addition and subtraction
=, +=, -=, *=, /=, %=, **=Assignment operators ((plain assignment, assignment by addition, assignment by subtraction, assignment by multiplication, assignment by division, assignment by modulo, assignment by exponentiation)

From the above, you can see that the exponentiation operator, which allows us to raise one number to the power of another, has the highest priority, followed by the unary increment and decrement operators. Keep in mind when using the increment and decrement operators that the results will differ, depending on whether the operator precedes or follows the variable with which it is associated. If the operator precedes the variable, the variable is updated before its value is used. Otherwise, it is updated after its value is used.

Next come the multiplication, division and modulo operators, which all have the same precedence. Operations involving two or more of these operators should be evaluated on a strictly left-to-right basis. Note that this is not critical for expressions containing only multiplication operators because multiplication is associative. This means that the order in which the individual addition operations are carried out does not matter. For example:

($a * $b) * ($c * $d) = $a * ($b * $c) * $d = $a * $b * $c * $d

Once we start looking at operations involving division, however, the order in which the operations are carried out becomes critical. For example, consider the following expression:

12 / 3 / 2 = 4 / 2 = 2

Here, we carry out each of the two division operations in strict order, so 12 divided by 3 gives us 4, and 4 divided by 2 gives us 2. If we force the second division operation to be carried out first using brackets, we get a different result:

12 / (3 / 2) = 12 / 1.5 = 8

The addition and subtraction operators are next in terms of precedence, and have equal status. Operations involving them are performed only after any operations with a higher priority have been processed. Operations involving multiplication and division, for example, are always undertaken before operations involving addition and subtraction.

Expressions containing only addition operators do not necessarily have to be evaluated on a strictly left-to-right basis because addition, like multiplication, is associative. The order in which the individual addition operations are carried out does not matter. For example:

($a + $b) + ($c + $d) = $a + ($b + $c) + $d = $a + $b + $c + $d

As with division, however, once we start dealing with operations involving subtraction, the left-to-right rule must be strictly observed. Consider the following expression:

25 - 12 - 3 = 13 - 3 = 10

We carry out each of the two subtraction operations in strict order, so 25 minus 12 gives us 13, and 13 minus 3 gives us 10. If we force the second subtraction operation to be carried out first using brackets, we get a different result:

25 - (12 - 3) = 25 - 9 = 16

Brackets can be used to change the order in which individual arithmetic operations are carried out within an expression if necessary. It is also sometimes done to avoid ambiguity. Brackets are often used in situations where they do not override the default operator precedence, but serve to make the order of evaluation explicit and improve readability.

The first rule to remember with regard to operator precedence - and one that is not explicitly stated in BODMAS, PEMDAS or any of the other mnemonics for operator precedence - is that operations having the same precedence are always evaluated in strict order from left to right. Next, expressions within parentheses are evaluated (the same left-to-right rule applies if there are two or more sets of parentheses at the same level). If the expression within the parentheses is itself a complex expression, the precedence rules are applied recursively.

After parentheses comes exponentiation, so terms that are raised to a power are evaluated next. Then we have multiplication, division, and modulo operations, which have equal status and should be evaluated in the order in which they appear. In penultimate position, we have addition and subtraction. These operations also have equal status, but should be executed from left to right.

Note that, even though the ordering of associative operations such as multiplication and addition is not important, they will nevertheless be evaluated from left to right by default. The only way to change that is to use parentheses, which is not normally necessary if an expression has been correctly formulated.

The assignment operators have the lowest priority, since in all (conventional) programming languages, an assignment operator is invariably preceded by a variable, and the expression to the right of the assignment operator must have been fully evaluated before the result can be assigned to, or otherwise have any effect upon, that variable.

Formatting numbers

Computers are happy to process huge amounts of numerical data. They need to be able to determine whether a numeric value is an integer or a floating-point value for the purposes of storing that value as a variable, and for handling calculations involving the value. Apart from that, they don't much care what the number looks like. Human beings, on the other hand, can be quite picky about how numbers are presented to them.

Common requirements include constraining the output of a calculation to a fixed number of decimal places, aligning a column of numerical values so that the decimal point (formally known as the radix point and usually represented by a dot or a comma) are vertically aligned, and the use of commas or periods to delimit groups of numbers within numeric strings.

In most cases where numerical values are presented for human consumption, the underlying data type is preserved, while the value is converted to a string variable in whatever format is deemed appropriate for a particular application. To that end, PHP provides the number-format() function.

The number_format() function takes a floating-point value as its first argument, and returns a string that represents that value. The format in which the value is presented will depend on what additional arguments are supplied to number_format(). The following example shows what happens if we apply the function to a number without using any of the formatting arguments:

<?php
$myFloat = 123456.789012;
$strFloat = number_format($myFloat);
var_dump($strFloat);
// string(7) "123,457"
?>

To understand what number_format() has done here, we need to look at each of the (optional) formatting arguments. The second argument is an integer value that specifies how many digits appear after the decimal point. Because this argument defaults to 0, our number has been rounded to the nearest integer value before being converted to a string (numbers with a fractional component of 0.5 or greater will always be rounded up; all other numbers will be rounded down).

Let's see what happens when we use the second argument:

<?php
$myFloat = 123456.789012;
$strFloat = number_format($myFloat, 2);
var_dump($strFloat);
// string(10) "123,456.79"
?>

As you can see, the output is now a numeric string that incudes the decimal separator, which is followed by two digits (note that the number has been rounded up to the nearest hundredth, because the third decimal digit following the decimal separator in our floating-point value is >= 5).

You may also notice that a comma is supplied in front of the first group of three digits to the left of the decimal separator, and that the decimal separator itself is a period. This brings us to the third and fourth arguments taken by number_format(). These are both string values, and specify the characters to be used for the decimal separator and the thousands separator respectively.

If left unspecified, the decimal separator defaults to a period (".") and the thousands separator defaults to a comma (","). We can specify different characters (or even sequences of characters) if required, by using the third and fourth arguments. For example, some countries use a comma as the decimal separator and a period as the thousands separator, so we could do something like this:

<?php
$myFloat = 123456.789012;
$strFloat = number_format($myFloat, 2, ",", ".");
var_dump($strFloat);
// string(10) "123.456,79"
?>

Sometimes we need a little more control over the format in which a number is presented to the user. This is especially true when we need to produce human-readable documents such as sales and purchase ledgers, invoices, or financial statements, all of which usually involve columns of figures. PHP provides the sprintf() function to enable us to exercise a considerable amount of control over how strings are formatted. A detailed analysis of how the sprintf() function works is provided in the article "Working with strings" in this section, so we will not repeat that analysis here.

The BCMath extension

PHP provides the integer numeric type for whole numbers with a value of up to 9,223,372,036,854,775,807 (263 - 1) on a 64-bit system. It is rarely if ever the case that we need to deal with numbers of anything like this magnitude. The same goes for floating point values. The largest floating point number PHP can represent using 64 bits is (plus or minus) 1.7976931348623E+308 - an unimaginably large number, but one lacking in precision.

Applications requiring integers larger than 263-1 (PHP_INT_MAX on a 64-bit system) or floating-point values with a precision greater than that afforded by PHP's float type are far and few between but they do exist. Some of the numbers pertaining to the study of cosmology, for example, are far larger. The number of stars in the universe is estimated to be in the region of one septillion (1024), which translates to a binary value of circa 280, a number several orders of magnitude greater than PHP_INT_MAX on most computer systems.

We can of course store very large integers as floating-point values, but only with a significant and often unacceptable loss of precision. However, there are solutions available. Some of the problems posed by the need to store very large numbers can be mitigated, if not completely eliminated, by using extensions to the language that facilitate the manipulation of such large numbers.

The BCMath (Binary Calculator Math) library is one such extension. It provides arbitrary precision arithmetic functions that allow us to work both with very large numbers and with floating point values that require more than fourteen significant digits. The library essentially used string representations of numeric values, both integer and floating-point numbers, in order to achieve this, essentially by-passing the limitations of PHP's integer and float types.

Let's look at some examples. We know that. On a 64-bit system, the constant PHP_INT_MAX evaluates to 9,223,372,036,854,775,807. This is a very large number! We can express it in words as "nine quintillion two hundred twenty-three quadrillion three hundred seventy-two trillion thirty-six billion eight hundred fifty-four million seven hundred seventy-five thousand eight hundred seven".

We can also express the number accurately in scientific notation as 9.223372036854775807 × 1018, which is how PHP manages to express integer values larger than PHP_INT_MAX. The problem here is that PHP is limited to seventeen significant digits, so we can't even represent PHP_INT_MAX accurately as a floating-point value. Here's what happens if we try:

<?php
$num = 9.223372036854775807E18;
var_dump ($num);
echo "<br>";
var_dump(intval($num));
// float(9.223372036854776E+18)
// int(-9223372036854775808)
?>

As you can see, PHP does not have a problem with expressing such a large value in scientific notation, but we have lost three significant digits because PHP has rounded the last four digits up, replacing ". . . 5807" with ". . . 6".

When we try to use use intval() to retrieve the integer value of $num, we get the value -9223372036854775808, a negative value because the value of $num is actually slightly larger than PHP_INT_MAX, and in the attempt to convert it to an integer, the sign bit has been changed from 0 to 1.

Whenever we attempt to carry out the integer addition or multiplication of two very large numbers, any result in excess of PHP_INT_MAX will be expressed as a floating-point value using scientific notation, and the result will always be an approximation. For example:

<?php
var_dump(9223372036854775807 * 2);
// float(1.8446744073709552E+19)
?>

There is no way that PHP can represent the result as an integer. If we require an accurate representation of the result as an integer value, we can use the BCMath function bcmul(). For example:

<?php
$result = bcmul("9223372036854775807", "2");
var_dump($result);
// string(20) "18446744073709551614"
?>

We can also add two very large numbers together using the BCMath function bcadd(). For example:

<?php
$result = bcadd("9223372036854775807", "9223372036854775807");
var_dump($result);
// string(20) "18446744073709551614"
?>

So now we have a way of achieving accurate results for integer arithmetic involving very large numbers. In fact, you could almost say that the sky is the limit. The BCMath extension has no official maximum number of digits for integer or floating-point numbers. There is of course a practical constraint in terms of system memory, but in theory BCMath can support numbers with up to 2,147,483,647 decimal digits!

When working with numbers of such magnitude it is probably reasonable to assume that we are not interested in a fractional component. At the other end of the scale, however, we might want to work with small values that have greater precision than is allowed by PHP's floating-point type, which is restricted to a maximum of seventeen significant digits.

Let's suppose we want to express √2 (the square root of two) to twenty decimal places. This number is irrational, which means that there are an infinite number of digits after the decimal separator, but we'll settle for twenty. We get a reasonably good approximation using PHP's sqrt() function. For example:

<?php
$root2 = sqrt(2);
var_dump($root2);
// float(1.4142135623730951)
?>

The result is limited to seventeen significant digits, with sixteen digits after the decimal point. If we need twenty digits after the decimal point, we need to turn to BCMath's bcsqrt() function. For example:

<?php
$root2 = bcsqrt(2, 20);
var_dump($root2);
// string(22) "1.41421356237309504880"
?>

Like virtually all BCMath functions, the bcsqrt() function takes an optional scale argument that specifies the degree of precision to use when returning a result. In this case we specified a value of 20 for the scale argument, which gives us an accurate result, with 20 digits after the decimal point.

The BCMath library allows us to achieve greater accuracy when undertaking calculations involving very large integer values, or where a high degree of precision is required for floating -point calculations. The down side is that, because BCMath is essentially manipulating strings to achieve its results, it is far more processor intensive, and therefore slower, than using PHP's built-in operators and math functions.

For applications where accuracy is significantly more important than processing speed, you should seriously consider using the BCMath library. You can find information on the full range of available BCMath functions in PHP Group's online manual for PHP 8.5).



Description