Introduction

In everyday life, numbers are easy to identify. They're 3:00 P.M., as in the current time, or $1.29, as in the cost of a pint of milk. Maybe they're like π, the ratio of the circumference to the diameter of a circle. They can be pretty large, like Avogadro's number, which is about 6 x 1023. In PHP, numbers can be all these things.

However, PHP doesn't treat all these numbers as "numbers." Instead, it breaks them down into two groups: integers and floating-point numbers. Integers are whole numbers, such as -4, 0, 5, and 1,975. Floating-point numbers are decimal numbers, such as -1.23, 0.0, 3.14159, and 9.9999999999.

Conveniently, most of the time PHP doesn't make you worry about the differences between the two because it automatically converts integers to floating-point numbers and floating-point numbers to integers. This conveniently allows you to ignore the underlying details. It also means 3/2 is 1.5, not 1, as it would be in some programming languages. PHP also automatically converts from strings to numbers and back. For instance, 1+"1" is 2.

However, sometimes this blissful ignorance can cause trouble. First, numbers can't be infinitely large or small; there's a minimum size of 2.2e-308 and a maximum size of about 1.8e308.[1] If you need larger (or smaller) numbers, you must use the BCMath or GMP libraries, which are discussed in Recipe 2.14.

Next, floating-point numbers aren't guaranteed to be exactly correct but only correct plus or a minus a small amount. Now, this amount is small enough for most occasions, but you can end up with problems in certain instances. For instance, humans automatically convert 6 followed by an endless string of 9s after the decimal point to 7, but PHP thinks it's 6 with a bunch of 9s. Therefore, if you ask PHP for the integer value of that number, it returns 6, not 7. For similar reasons, if the digit located in the 200th decimal place is significant, floating-point numbers aren't useful. Again, the BCMath and GMP libraries ride to the rescue. But, for most occasions, PHP behaves very nicely when playing with numbers and lets you treat them just as you do in real life.

Checking Whether a String Contains a Valid Number

Problem

You want to ensure that a string contains a number. For example, you want to validate an age that the user has typed into a form input field.

To strip the thousands separators from your number before calling is_numeric( ) use str_replace( ):

is_numeric(str_replace($number, ',', ''));

To check if your number is a specific type, there are a variety of self-explanatorily named related functions: is_bool( ) , is_float( ) (or is_double( ) or is_real( ); they're all the same), and is_int( ) (or is_integer( ) or is_long( ) ).

Solution

Discussion

Floating-point numbers are represented in binary form with only a finite number of bits for the mantissa and the exponent. You get overflows when you exceed those bits. As a result, sometimes PHP (and other languages, too) don't believe two equal numbers are actually equal because they may differ toward the very end.

To avoid this problem, instead of checking if $a == $b, make sure the first number is within a very small amount ($delta) of the second one. The size of your delta should be the smallest amount of difference you care about between two numbers. Then use abs( ) to get the absolute value of the difference.

See Also

Rounding Floating-Point Numbers

Problem

You want to round a floating-point number, either to an integer value or to a set number of decimal places.

Solution

To round a number to the closest integer, use round( ) :

$number = round(2.4); // $number = 2

To round up, use ceil( ):

$number = ceil(2.4); // $number = 3

To round down, use floor( ):

$number = floor(2.4); // $number = 2

Discussion

If a number falls exactly between two integers, its behavior is undefined:

$number = round(2.5); // $number is 2 or 3!

Be careful! As we mention in Recipe 2.3, floating-point numbers don't always work out to exact values because of how they're stored internally by the computer. This can create situations in which the obvious answer isn't. A value you expect to have a decimal part of "0.5" might instead be ".499999...9" (with a whole bunch of 9s) or ".500000...1" (with many 0s and a trailing 1). If you want to ensure that a number is rounded up as you might expect, add a small delta value to it before rounding:

$delta = 0.0000001;
$number = round(2.5 + $delta); // $number = 3

To keep a set number of digits after the decimal point, round( ) accepts an optional precision argument. For example, if you are calculating the total price for the items in a user's shopping cart:

Operating on a Series of Integers

Problem

Solution

Use the range( ) function, which returns an array populated with integers:

foreach(range($start,$end) as $i) {
plot_point($i);
}

Instead of using range( ), it can be more efficient to use a for loop. Also, you can increment using values other than 1. For example:

for ($i = $start; $i <= $end; $i += $increment) {
plot_point($i);
}

Discussion

Loops like this are common. For instance, you could be plotting a function and need to calculate the results for multiple points on the graph. Or, you could be NASA counting down until the launch of the Space Shuttle Columbia.

In the first example, range( ) returns an array with values from $start to $end. Then foreach pulls out each element and assigns it to $i inside of the loop. The advantage of using range( ) is its brevity, but this technique has a few disadvantages. For one, a large array can take up unnecessary memory. Also, you're forced to increment the series one number at a time, so you can't loop through a series of even integers, for example.

As of PHP 4.1, it is valid for $start to be larger than $end. In this case, the numbers returned by range( ) are in descending order. Also, you can use iterate over character sequences:

The for loop method just uses a single integer and avoids the array entirely. While it's longer, you have greater control over the loop, because you can increment and decrement $i more freely. Also, you can modify $i from inside the loop, something you can't do with range( ), because PHP reads in the entire array when it enters the loop, and changes to the array don't effect the sequence of elements.

Generating Random Numbers Within a Range

Problem

Solution

Discussion

Generating random numbers is useful when you want to display a random image on a page, randomize the starting position of a game, select a random record from a database, or generate a unique session identifier.

To generate a random number between two end points, pass mt_rand( ) two arguments:

$random_number = mt_rand(1, 100);

Calling mt_rand( ) without any arguments returns a number between 0 and the maximum random number, which is returned by mt_getrandmax( ) .

Generating truly random numbers is hard for computers to do. Computers excel at following instructions methodically; they're not so good at spontaneity. If you want to instruct a computer to return random numbers, you need to give it a specific set of repeatable commands; the very fact that they're repeatable undermines the desired randomness.

PHP has two different random number generators, a classic function called rand( ) and a better function called mt_rand( ). MT stands for Mersenne Twister, which is named for the French monk and mathematician Marin Mersenne and the type of prime numbers he's associated with. The algorithm is based on these prime numbers. Since mt_rand( ) is more random and faster than rand( ), we prefer it to rand( ).

If you're running a version of PHP earlier than 4.2, before using mt_rand( ) (or rand( )) for the first time in a script, you need to seed the generator, by calling mt_srand( ) (or srand( )). The seed is a number the random function uses as the basis for generating the random numbers it returns; it's how to solve the repeatable versus random dilemma mentioned earlier. Use the value returned by microtime( ) , a high-precision time function, to get a seed that changes very quickly and is unlikely to repeat — qualities desirable in a good seed. After the initial seed, you don't need to reseed the randomizer. PHP 4.2 and later automatically handles seeding for you, but if you manually provide a seed before calling mt_rand( ) for the first time, PHP doesn't alter it by substituting a new seed of its own.

If you want to select a random record from a database — an easy way is to find the total number of fields inside the table — select a random number in that range, and then request that row from the database:

Generating Biased Random Numbers

Problem

You want to generate random numbers, but you want these numbers to be somewhat biased, so that numbers in certain ranges appear more frequently than others. For example, you want to spread out a series of banner ad impressions in proportion to the number of impressions remaining for each ad campaign.

Discussion

Imagine if instead of an array in which the values are the number of remaining impressions, you have an array of ads in which each ad occurs exactly as many times as its remaining number of impressions. You can simply pick an unweighted random place within the array, and that'd be the ad that shows.

This technique can consume a lot of memory if you have millions of impressions remaining. Instead, you can calculate how large that array would be (by totalling the remaining impressions), pick a random number within the size of the make-believe array, and then go through the array figuring out which ad corresponds to the number you picked. For instance:

Discussion

Both log( ) and log10( ) are defined only for numbers that are greater than zero. The pc_logn( ) function uses the change of base formula, which says that the log of a number in base n is equal to the log of that number, divided by the log of n.

Discussion

The built-in constant M_E is an approximation of the value of e. It equals 2.7182818284590452354. So exp($n) and pow(M_E,$n) are identical.

It's easy to create very large numbers using exp( ) and pow( ); if you outgrow PHP's maximum size (almost 1.8e308), see Recipe 2.14 for how to use the arbitrary precision functions. With these functions, PHP returns INF, infinity, if the result is too large and NAN, not-a-number, on an error.

See Also

Formatting Numbers

Problem

You have a number and you want to print it with thousands and decimals separators. For instance, you want to display prices for items in a shopping cart.

Solution

Use the number_format( ) function to format as an integer:

$number = 1234.56;
print number_format($number); // 1,235 because number is rounded up

Specify a number of decimal places to format as a decimal:

print number_format($number, 2); // 1,234.56

Discussion

The number_format( ) function formats a number by inserting the correct decimal and thousands separators for your locale. If you want to manually specify these values, pass them as the third and fourth parameters:

The third argument is used as the decimal point and the last separates thousands. If you use these options, you must specify both arguments.

By default, number_format( ) rounds the number to the nearest integer. If you want to preserve the entire number, but you don't know ahead of time how many digits follow the decimal point in your number, use this:

If you plan to have multiple plurals inside your code, using a function such as pc_may_pluralize( ) increases readability. To use the function, pass pc_may_pluralize( ) the singular form of the word as the first argument and the amount as the second. Inside the function, there's a large array, $plurals, that holds all the special cases. If the $amount is 1, you return the original word. If it's greater, you return the special pluralized word, if it exists. As a default, just add an "s" to the end of the word.

Calculating Trigonometric Functions

Problem

You want to use trigonometric functions, such as sine, cosine, and tangent.

Doing Trigonometry in Degrees, not Radians

Problem

You have numbers in degrees but want to use the trigonometric functions.

Solution

Use deg2rad( ) and rad2deg( ) on your input and output:

$cosine = rad2deg(cos(deg2rad($degree)));

Discussion

By definition, 360 degrees is equal to 2π radians, so it's easy to manually convert between the two formats. However, these functions use PHP's internal value of π, so you're assured a high-precision answer. To access this number for other calculations, use the constant M_PI, which is 3.14159265358979323846.

There is no built-in support for gradians. This is considered a feature, not a bug.

Solution

$sum = gmp_add('1234567812345678', '8765432187654321');
// $sum is now a GMP resource, not a string; use gmp_strval( ) to convert
print gmp_strval($sum);

Discussion

The BCMath library is easy to use. You pass in your numbers as strings, and the function return the sum (or difference, product, etc.) as a string. However, the range of actions you can apply to numbers using BCMath is limited to basic arithmetic.

The GMP library is available as of PHP 4.0.4. While most members of the GMP family of functions accept integers and strings as arguments, they prefer to pass numbers around as resources, which are essentially pointers to the numbers. So, unlike BCMath functions, which return strings, GMP functions return only resources. You then pass the resource to any GMP function, and it acts as your number.

The only downside is when you want to view or use the resource with a non-GMP function, you need to explicitly convert it using gmp_strval( ) or gmp_intval( ).

However, you can do many more things with GMP numbers than addition, such as raising a number to a power, computing large factorials very quickly, finding a greatest common divisor (GCD), and other fancy mathematical stuff:

The BCMath and GMP libraries aren't necessarily enabled with all PHP configurations. As of PHP 4.0.4, BCMath is bundled with PHP, so it's likely to be available. However, GMP isn't bundled with PHP, so you'll need to download, install it, and instruct PHP to use it during the configuration process. Check the values of function_defined('bcadd') and function_defined('gmp_init') to see if you can use BCMath and GMP.

Solution

Discussion

The base_convert( ) function changes a string in one base to the correct string in another. It works for all bases from 2 to 36 inclusive, using the letters a through z as additional symbols for bases above 10. The first argument is the number to be converted, followed by the base it is in and the base you want it to become.

There are also a few specialized functions for conversions to and from base 10 and the most commonly used other bases of 2, 8, and 16. They're bindec( ) and decbin( ), octdec( ) and decoct( ), and hexdec( ) and dechex( ):

Another alternative is to use sprintf( ) , which allows you to convert decimal numbers to binary, octal, and hexadecimal numbers with a wide range of formatting, such as leading 0s and a choice between upper- and lowercase letters for hexadecimal numbers.

See Also

Calculating Using Numbers in Bases Other Than Decimal

Problem

You want to perform mathematical operations with numbers formatted not in decimal, but in octal or hexadecimal. For example, you want to calculate web-safe colors in hexadecimal.

Solution

Prefix the number with a leading symbol, so PHP knows it isn't in base 10. The following values are all equal:

0144 // base 8
100 // base 10
0x64 // base 16

Here's how to count from decimal 1 to 15 using hexadecimal notation:

for ($i = 0x1; $i < 0x10; $i++) { print "$i\n"; }

Discussion

Even if you use hexadecimally formatted numbers in a for loop, by default, all numbers are printed in decimal. In other words, the code in the Solution doesn't print out "..., 8, 9, a, b, ...". To print in hexadecimal, use one of the methods listed in Recipe 2.15. Here's an example:

for ($i = 0x1; $i < 0x10; $i++) { print dechex($i) . "\n"; }

For most calculations, it's easier to use decimal. Sometimes, however, it's more logical to switch to another base, for example, when using the 216 web-safe colors. Every web color code is of the form RRGGBB, where RR is the red color, GG is the green color, and BB is the blue color. Each color is actually a two-digit hexadecimal number between 0 and FF.

What makes web-safe colors special is that RR, GG, and BB each must be one of the following six numbers: 00, 33, 66, 99, CC, and FF (in decimal: 0, 51, 102, 153, 204, 255). So, 003366 is web-safe, but 112233 is not. Web-safe colors render without dithering on a 256-color display.

When creating a list of these numbers, use hexadecimal notation in this triple-loop to reinforce the list's hexadecimal basis:

Here the loops compute all possible web-safe colors. However, instead of stepping through them in decimal, you use hexadecimal notation, because it reinforces the hexadecimal link between the numbers. Print them out using printf( ) to format them as uppercase hexadecimal numbers at least two digits long. One-digit numbers are passed with a leading zero.

See Also

Recipe 2.15 for details on converting between bases; Chapter 3, "Web Design Principles for Print Designers," in Web Design in a Nutshell (O'Reilly).

Notes

↑ These numbers are actually platform-specific, but the values are common because they are from the 64-bit IEEE standard 754.

↑ The most glaring example of this difference came during the transition from PHP 3 to PHP 4. In PHP 3, empty('0') returned false, but as of PHP 4, it returns true. On the other hand, empty(0) has always returned true and still does. (Actually, you need to call empty( ) on variables containing '0' and 0.) See the Introduction to Chapter 5 for details.