# PHP Cookbook/Numbers

## 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.

### Solution

Use is_numeric( ) :

```if (is_numeric('five')) { /* false */ }

if (is_numeric(5))      { /* true  */ }
if (is_numeric('5'))    { /* true  */ }

if (is_numeric(-5))     { /* true  */ }
if (is_numeric('-5'))   { /* true  */ }
```

### Discussion

Besides working on numbers, is_numeric( ) can also be applied to numeric strings. The distinction here is that the integer 5 and the string 5 technically aren't the same in PHP.[2]

Helpfully, is_numeric( ) properly parses decimal numbers, such as 5.1; however, numbers with thousands separators, such as 5,100, cause is_numeric( ) to return false.

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( ) ).

Documentation on is_numeric( ) at http://www.php.net/is-numeric and str_replace( ) at http://www.php.net/str-replace.

## Comparing Floating-Point Numbers

### Problem

You want to check whether two floating-point numbers are equal.

### Solution

Use a small delta value, and check if the numbers are equal within that delta:

```\$delta = 0.00001;

\$a = 1.00000001;
\$b = 1.00000000;

if (abs(\$a - \$b) < \$delta) { /* \$a and \$b are equal */ }
```

### 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.

Recipe 2.4 for information on rounding floating-point numbers; documentation on floating-point numbers in PHP at http://www.php.net/language.types.float.

## 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:

```\$cart = 54.23;
\$tax = \$cart * .05;
\$total = \$cart + \$tax;       // \$total = 56.9415

\$final = round(\$total, 2);   // \$final = 56.94
```

Recipe 2.3 for information on comparing floating-point numbers; documentation on round( ) at http://www.php.net/round.

## Operating on a Series of Integers

### Problem

You want to apply a piece of code over a range of integers.

### 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:

```print_r(range('l', 'p'));
Array
(
[0] => l
[1] => m
[2] => n
[3] => o
[4] => p
)

```

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.

Recipe 4.4 for details on initializing an array to a range of integers; documentation on range( ) at http://www.php.net/range.

## Generating Random Numbers Within a Range

### Problem

You want to generate a random number within a range of numbers.

### Solution

Use mt_rand( ):

```// random number between \$upper and \$lower, inclusive
\$random_number = mt_rand(\$lower, \$upper);
```

### 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:

```\$sth = \$dbh->query('SELECT COUNT(*) AS count FROM quotes');
if (\$row = \$sth->fetchRow()) {
\$count = \$row[0];
} else {
die (\$row->getMessage());
}

\$random = mt_rand(0, \$count - 1);

\$sth = \$dbh->query("SELECT quote FROM quotes LIMIT \$random,1");
while (\$row = \$sth->fetchRow()) {
print \$row[0] . "\n";
}
```

This snippet finds the total number of rows in the table, computes a random number inside that range, and then uses LIMIT \$random,1 to SELECT one line from the table starting at position \$random.

Alternatively, if you're using MySQL 3.23 or above, you can do this:

```\$sth = \$dbh->query('SELECT quote FROM quotes ORDER BY RAND() LIMIT 1');
while (\$row = \$sth->fetchRow()) {
print \$row[0] . "\n";
}
```

In this case, MySQL randomizes the lines, and then the first row is returned.

Recipe 2.7 for how to generate biased random numbers; documentation on mt_rand( ) at http://www.php.net/mt-rand and rand( ) at http://www.php.net/rand; the MySQL Manual on RAND( ) at http://www.mysql.com/doc/M/a/Mathematical_functions.html.

## 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.

### Solution

Use the pc_rand_weighted( ) function shown in Example 2-1.

Example 2-1. pc_rand_weighted( )

```// returns the weighted randomly selected key
function pc_rand_weighted(\$numbers) {
\$total = 0;
foreach (\$numbers as \$number => \$weight) {
\$total += \$weight;
\$distribution[\$number] = \$total;
}
\$rand = mt_rand(0, \$total - 1);
foreach (\$distribution as \$number => \$weights) {
if (\$rand < \$weights) { return \$number; }
}
}
```

### 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:

```\$ads = array('ford' => 12234, // advertiser, remaining impressions
'att'  => 33424,
'ibm'  => 16823);

```

Recipe 2.6 for how to generate random numbers within a range.

## Taking Logarithms

### Problem

You want to take the logarithm of a number.

### Solution

For logs using base e (natural log), use log( ):

```\$log = log(10);          // 2.30258092994
```

For logs using base 10, use log10( ):

```\$log10 = log10(10);      // 1
```

For logs using other bases, use pc_logn( ):

```function pc_logn(\$number, \$base) {
return log(\$number) / log(\$base);
}

\$log2  = pc_logn(10, 2); // 3.3219280948874
```

### 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.

Documentation on log( ) at http://www.php.net/log and log10( ) at http://www.php.net/log10.

## Calculating Exponents

### Problem

You want to raise a number to a power.

### Solution

To raise e to a power, use exp( ):

```\$exp = exp(2);        // 7.3890560989307
```

To raise it to any power, use pow( ) :

```\$exp = pow( 2, M_E);  // 6.5808859910179

\$pow = pow( 2, 10);   // 1024
\$pow = pow( 2, -2);   // 0.25
\$pow = pow( 2, 2.5);  // 5.6568542494924

\$pow = pow(-2, 10);   // 1024
\$pow = pow( 2, -2);   // 0.25
\$pow = pow(-2, -2.5); // NAN (Error: Not a Number)
```

### 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.

Documentation on pow( ) at http://www.php.net/pow, exp( ) at http://www.php.net/exp, and information on predefined mathematical constants at http://www.php.net/math.

## 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:

```\$number = 1234.56;
print number_format(\$number, 2, '@', '#'); // 1#234@56
```

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:

```\$number = 1234.56; // your number
list(\$int, \$dec) = explode('.', \$number);
print number_format(\$number, strlen(\$dec));
```

Documentation on number_format( ) at http://www.php.net/number-format.

## Printing Correct Plurals

### Problem

You want to correctly pluralize words based on the value of a variable. For instance, you are returning text that depends on the number of matches found by a search.

### Solution

Use a conditional expression:

```\$number = 4;
print "Your search returned \$number " . (\$number == 1 ? 'hit' : 'hits') . '.';
```

### Discussion

It's slightly shorter to write the line as:

```print "Your search returned \$number hit" . (\$number == 1 ? '' : 's') . '.';
```

However, for odd pluralizations, such as "person" versus "people," we find it clearer to break out the entire word rather than just the letter.

Another option is to use one function for all pluralization, as shown in the pc_may_pluralize( ) function in Example 2-2.

Example 2-2. pc_may_pluralize( )

```function pc_may_pluralize(\$singular_word, \$amount_of) {

// array of special plurals
\$plurals = array(
'fish' => 'fish',
'person' => 'people',
);

// only one
if (1 == \$amount_of) {
return \$singular_word;
}

// more than one, special plural
if (isset(\$plurals[\$singular_word])) {
return \$plurals[\$singular_word];
}

// more than one, standard plural: add 's' to end of word
return \$singular_word . 's';
}
```

Here are some examples:

```\$number_of_fish = 1;
print "I ate \$number_of_fish " . pc_may_pluralize('fish', \$number_of_fish) . '.';

\$number_of_people = 4;
print 'Soylent Green is ' . pc_may_pluralize('person', \$number_of_people) . '!';
I ate 1 fish.
Soylent Green is people!

```

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.

### Solution

PHP supports many trigonometric functions natively: sin( ) , cos( ), and tan( ):

```\$cos = cos(2.1232);
```

You can also use their inverses: asin( ), acos( ), and atan( ):

```\$atan = atan(1.2);
```

### Discussion

These functions assume their arguments are in radians, not degrees. (See Recipe 2.13 if this is a problem.)

The function atan2( ) takes two variables \$x and \$y, and computes atan(\$x/\$y). However, it always returns the correct sign because it uses both parameters when finding the quadrant of the result.

For secant, cosecant, and cotangent, you should manually calculate the reciprocal values of sin( ), cos( ), and tan( ):

```\$n = .707;

\$secant    = 1 / sin(\$n);
\$cosecant  = 1 / cos(\$n);
\$cotangent = 1 / tan(\$n);
```

Starting in PHP 4.1, you can also use hyperbolic functions: sinh( ), cosh( ), and tanh( ), plus, of course, asin( ), cosh( ), and atanh( ). The inverse functions, however, aren't supported on Windows.

Recipe 2.13 for how to perform trig operations in degrees, not radians; documentation on sin( ) at http://www.php.net/sin, cos( ) at http://www.php.net/cos, tan( ) at http://www.php.net/tan, asin( ) at http://www.php.net/asin, acos( ) at http://www.php.net/acos, atan( ) at http://www.php.net/atan, and atan2( ) at http://www.php.net/atan2.

## Doing Trigonometry in Degrees, not Radians

### Problem

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

### Solution

```\$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.

## Handling Very Large or Very Small Numbers

### Problem

You need to use numbers that are too large (or small) for PHP's built-in floating-point numbers.

### Solution

Use either the BCMath or GMP libraries.

Using BCMath:

```\$sum = bcadd('1234567812345678', '8765432187654321');

// \$sum is now the string '9999999999999999'
print \$sum;
```

Using GMP:

```\$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( ).

GMP functions are liberal in what they accept. For instance:

```\$four = gmp_add(2, 2);            // You can pass integers
\$eight = gmp_add('4', '4');       // Or strings
\$twelve = gmp_add(\$four, \$eight); // Or GMP resources
print gmp_strval(\$twelve);        // Prints 12
```

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:

```// Raising a number to a power
\$pow = gmp_pow(2, 10);             // 1024

// Computing large factorials very quickly
\$factorial = gmp_fact(20);         // 2432902008176640000

// Finding a GCD
\$gcd = gmp_gcd (123, 456);         // 3

// Other fancy mathematical stuff
\$legdendre = gmp_legendre(1, 7);   // 1
```

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.

Documentation on BCMath at http://www.php.net/bc and GMP at http://www.php.net/gmp.

## Converting Between Bases

### Problem

You need to convert a number from one base to another.

### Solution

Use the base_convert( ) function:

```\$hex = 'a1';                           // hexadecimal number (base 16)

// convert from base 16 to base 10
\$decimal = base_convert(\$hex, 16, 10); // \$decimal is now 161
```

### 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( ):

```// convert to base 10
print bindec(11011); // 27
print octdec(33);    // 27
print hexdec('1b');  // 27

// convert from base 10
print decbin(27);    // 11011
print decoct(27);    // 33
print dechex(27);    // 1b
```

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.

For instance, say you want to print out HTML color values:

```printf('#%02X%02X%02X', 0, 102, 204); // #0066CC
```

Documentation on base_convert( ) at http://www.php.net/base-convert and sprintf( ) formatting options at http://www.php.net/sprintf.

## 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:

```for (\$rr = 0; \$rr <= 0xFF; \$rr += 0x33)
for (\$gg = 0; \$gg <= 0xFF; \$gg += 0x33)
for (\$bb = 0; \$bb <= 0xFF; \$bb += 0x33)
printf("%02X%02X%02X\n", \$rr, \$gg, \$bb);
```

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.