2. Numbers

Checking Whether a String Is a Valid Number

// Two basic approaches to numeric validation:
// * Built-in functions like 'is_numeric', 'is_int', 'is_float' etc
// * Regexes, as shown below

$s = '12.345';

preg_match('/\D/', $s) && die("has nondigits\n");
preg_match('/^\d+$/', $s) || die("not a natural number\n");
preg_match('/^-?\d+$/', $s) || die("not an integer\n");
preg_match('/^[+-]?\d+$/', $s) || die("not an integer\n");
preg_match('/^-?\d+\.?\d*$/', $s) || die("not a decimal\n");
preg_match('/^-?(?:\d+(?:\.\d*)?|\.\d+)$/', $s) || die("not a decimal\n");
preg_match('/^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/', $s) || die("not a C float\n");

// ----------------------------

function getnum($s)
{
  sscanf($s, "%D", $number); return isset($number) ? $number : 0;
}

echo getnum(123) . "\n";   // ok
echo getnum(0xff) . "\n";  // ..
echo getnum(044) . "\n";   // ..

echo getnum('x') . "\n";   // fail

Comparing Floating-Point Numbers

// In PHP floating point comparisions are 'safe' [meaning the '==' comparison operator
// can be used] as long as the value consists of 14 digits or less [total digits, either
// side of the decimal point e.g. xxxxxxx.xxxxxxx, xxxxxxxxxxxxxx., .xxxxxxxxxxxxxx]. If
// values with more digits must be compared, then:
//
// * Represent as strings, and take care to avoid any implicit conversions e.g. don't pass
//   a float as a float to a function and expect all digits to be retained - they won't be -
//   then use 'strcmp' to compare the strings
//
// * Avoid float use; perhaps use arbitrary precision arithmetic. In this case, the
//   'bccomp' function is relevant

// Will work as long as each floating point value is 14 digits or less
if ($float_1 == $float_2)
{
  ; // ...
}

// Compare as strings
$cmp = strcmp('123456789.123456789123456789', '123456789.123456789123456788');

// Use 'bccomp'
$precision = 5; // Number of significant comparison digits after decimal point
if (bccomp('1.111117', '1.111116', $precision))
{
  ; // ...
}

$precision = 6;
if (bccomp('1.111117', '1.111116', $precision))
{
  ; // ...
}

// ----------------------------

$wage = 536;
$week = $wage * 40;
printf("One week's wage is: $%.2f\n", $week / 100);

Rounding Floating-Point Numbers

// Preferred approach
$rounded = round($unrounded, $precision);

// Possible alternate approach
$format = '%[width].[prec]f';
$rounded = sprintf($format, $unrounded);

// ------------

$a = 0.255; $b = round($a, 2);
echo "Unrounded: {$a}\nRounded: {$b}\n";

$a = 0.255; $b = sprintf('%.2f', $a);
echo "Unrounded: {$a}\nRounded: {$b}\n";

$a = 0.255;
printf("Unrounded: %.f\nRounded: %.2f\n", $a, $a);

// ----------------------------

echo "number\tint\tfloor\tceil\n";

foreach(array(3.3, 3.5, 3.7, -3.3) as $number)
{
  printf("%.1f\t%.1f\t%.1f\t%.1f\n", $number, (int) $number, floor($number), ceil($number));
}

Converting Between Binary and Decimal

// PHP offers the 'bindec' and 'decbin' functions to converting between binary and decimal

$num = bindec('0110110');

$binstr = decbin(54);

Operating on a Series of Integers

foreach (range($X, $Y) as $i)
{
  ; // ...
}

foreach (range($X, $Y, 7) as $i)
{
  ; // ...
}

for ($i = $X; $i <= $Y; $i++)
{
  ; // ...
}

for ($i = $X; $i <= $Y; $i += 7)
{
  ; // ...
}

// ----------------------------

echo 'Infancy is:'; foreach(range(0, 2) as $i) echo " {$i}\n";
echo 'Toddling is:'; foreach(range(3, 4) as $i) echo " {$i}\n";
echo 'Childhood is:'; foreach(range(5, 12) as $i) echo " {$i}\n";

Working with Roman Numerals

// PHP offers no native support for Roman Numerals. However, a 'Numbers_Roman' class
// is available for download from PEAR: [http://pear.php.net/package/Numbers_Roman].
// Note the following 'include' directives are required:
//
//   include_once('Numbers/Roman.php');

$roman = Numbers_Roman::toNumeral($arabic);
$arabic = Numbers_Roman::toNumber($roman);

// ----------------------------

$roman_fifteen = Numbers_Roman::toNumeral(15);

$arabic_fifteen = Numbers_Roman::toNumber($roman_fifteen);

printf("Roman for fifteen is: %s\n", $roman_fifteen);
printf("Arabic for fifteen is: %d\n", $arabic_fifteen);

Generating Random Numbers

// Techniques used here simply mirror Perl examples, and are not an endorsement
// of any particular RNG technique

// In PHP do this ...
$random = rand($lowerbound, $upperbound);
$random = rand($x, $y);

// ----------------------------

function make_password($chars, $reqlen)
{
  $len = strlen($chars);
  for ($i = 0; $i < $reqlen; $i++) $password .= substr($chars, rand(0, $len), 1);
  return $password;
}

$chars = 'ABCDEfghijKLMNOpqrstUVWXYz'; $reqlen = 8;

$password = make_password($chars, $reqlen);

Generating Different Random Numbers

// PHP sports a large number of C Standard Library routines including the 'srand'
// function, used to re-seed the RNG used with calls to the 'rand' function. Thus,
// as per Perl example:

while (TRUE)
{
  $seed = (int) fgets(STDIN);
  if (!empty($seed)) break;
}

srand($seed);

Making Numbers Even More Random

// The above is considered - for many reasons - a poor way of seeding the RNG. PHP
// also offers alternate versions of the functions, 'mt_srand' and 'mt_rand',
// which are described as faster, and more 'random', though key to obtaining a
// more 'random' distribution of generated numbers seems to be through using
// a combination of a previously saved random value in combination with an
// unrepeatable value [like the current time in microseconds] that is multiplied
// by a large prime number, or perhaps as part of a hash [examples available in
// PHP documentation for 'srand' and 'mt_srand']

mt_srand($saved_random_value + microtime() * 1000003);

// or

mt_srand(($saved_random_value + hexdec(substr(md5(microtime()), -8))) & 0x7fffffff);

// Use of 'mt_rand' together with an appropriate seeding approach should help better
// approximate the generation of a 'truly random value'
$truly_random_value = mt_rand();

Generating Biased Random Numbers

function random() { return (float) rand() / (float) getrandmax(); }

function gaussian_rand()
{
  $u1 = 0.0; $u2 = 0.0; $g1 = 0.0; $g2 = 0.0; $w = 0.0;
  
  do
  {
    $u1 = 2.0 * random() - 1.0; $u2 = 2.0 * random() - 1.0;
    $w = $u1 * $u1 + $u2 * $u2;
  } while ($w > 1.0);
  
  $w = sqrt((-2.0 * log($w)) / $w); $g2 = $u1 * $w; $g1 = $u2 * $w;

  return $g1;
}

// ------------

$mean = 25.0; $sdev = 2.0;
$salary = gaussian_rand() * $mean + $sdev;

printf("You have been hired at: %.2f\n", $salary);

Doing Trigonometry in Degrees, not Radians

// 'deg2rad' and 'rad2deg' are actually PHP built-ins, but here is how you might implement
/  them if needed
function deg2rad_($deg) { return ($deg / 180.0) * M_PI; }
function rad2deg_($rad) { return ($rad / M_PI) * 180.0; }

// ------------

printf("%f\n", deg2rad_(180.0));
printf("%f\n", deg2rad(180.0));

// ----------------------------

function degree_sin($deg) { return sin(deg2rad($deg)); }

// ------------

$rad = deg2rad(380.0);

printf("%f\n", sin($rad));
printf("%f\n", degree_sin(380.0));

Calculating More Trigonometric Functions

function my_tan($theta) { return sin($theta) / cos($theta); }

// ------------

$theta = 3.7;

printf("%f\n", my_tan($theta));
printf("%f\n", tan($theta));

Taking Logarithms

$value = 100.0;

$log_e = log($value);
$log_10 = log10($value);

// ----------------------------

function log_base($base, $value) { return log($value) / log($base); }

// ------------

$answer = log_base(10.0, 10000.0);

printf("log(10, 10,000) = %f\n", $answer);

Multiplying Matrices

// PHP offers no native support for matrices. However, a 'Math_Matrix' class
// is available for download from PEAR: [http://pear.php.net/package/Math_Matrix].
// Note the following 'include' directives are required:
//
//  include_once('Math/Matrix.php');

$a = new Math_Matrix(array(array(3, 2, 3), array(5, 9, 8)));
$b = new Math_Matrix(array(array(4, 7), array(9, 3), array(8, 1)));

echo $a->toString() . "\n";
echo $b->toString() . "\n";

// NOTE: When I installed this package I had to rename the 'clone' method else
// it would not load, so I chose to rename it to 'clone_', and this usage is
// shown below. This bug may well be fixed by the time you obtain this package

$c = $a->clone_();
$c->multiply($b);

echo $c->toString() . "\n";

Using Complex Numbers

// PHP offers no native support for complex numbers. However, a 'Math_Complex' class
// is available for download from PEAR: [http://pear.php.net/package/Math_Complex].
// Note the following 'include' directives are required:
//
//   include_once('Math/Complex.php');
//   include_once('Math/TrigOp.php');
//   include_once('Math/ComplexOp.php');

$a = new Math_Complex(3, 5);
$b = new Math_Complex(2, -2);

$c = Math_ComplexOp::mult($a, $b);

echo $c->toString() . "\n";

// ----------------------------

$d = new Math_Complex(3, 4);
$r = Math_ComplexOp::sqrt($d);

echo $r->toString() . "\n";

Converting Between Octal and Hexadecimal

// Like C, PHP supports decimal-alternate notations. Thus, for example, the integer
// value, 867, is expressable in literal form as:
//
//   Hexadecimal -> 0x363
//   Octal       -> 01543
//
// For effecting such conversions using strings there is 'sprintf' and 'sscanf'.

$dec = 867;
$hex = sprintf('%x', $dec);
$oct = sprintf('%o', $dec);

// ------------

$dec = 0;
$hex = '363';

sscanf($hex, '%x', $dec);

// ------------

$dec = 0;
$oct = '1543';

sscanf($oct, '%o', $dec);

// ----------------------------

$number = 0;

printf('Gimme a number in decimal, octal, or hex: ');
sscanf(fgets(STDIN), '%D', $number);

printf("%d %x %o\n", $number, $number, $number);

Putting Commas in Numbers

// PHP offers the 'number_format' built-in function to, among many other format tasks, 
// commify numbers. Perl-compatible [as well as extended] regexes are also available

function commify_series($s) { return number_format($s, 0, '', ','); }

// ------------

$hits = 3456789;

printf("Your website received %s accesses last month\n", commify_series($hits));

// ----------------------------

function commify($s)
{
  return strrev(preg_replace('/(\d\d\d)(?=\d)(?!\d*\.)/', '${1},', strrev($s)));
}

// ------------

$hits = 3456789;

echo commify(sprintf("Your website received %d accesses last month\n", $hits));

Printing Correct Plurals

function pluralise($value, $root, $singular='' , $plural='s')
{
  return $root . (($value > 1) ? $plural : $singular);
}

// ------------

$duration = 1;
printf("It took %d %s\n", $duration, pluralise($duration, 'hour'));
printf("%d %s %s enough.\n", $duration, pluralise($duration, 'hour'),
      pluralise($duration, '', 'is', 'are'));

$duration = 5;
printf("It took %d %s\n", $duration, pluralise($duration, 'hour'));
printf("%d %s %s enough.\n", $duration, pluralise($duration, 'hour'),
      pluralise($duration, '', 'is', 'are'));

// ----------------------------

function plural($singular)
{
  $s2p = array('/ss$/' => 'sses', '/([psc]h)$/' => '${1}es', '/z$/' => 'zes',
               '/ff$/' => 'ffs', '/f$/' => 'ves', '/ey$/' => 'eys',
               '/y$/' => 'ies', '/ix$/' => 'ices', '/([sx])$/' => '$1es',
               '$' => 's');

  foreach($s2p as $s => $p)
  {
    if (preg_match($s, $singular)) return preg_replace($s, $p, $singular);
  }
}

// ------------

foreach(array('mess', 'index', 'leaf', 'puppy') as $word)
{
  printf("%6s -> %s\n", $word, plural($word));
}

Program: Calculating Prime Factors

// @@INCOMPLETE@@
// @@INCOMPLETE@@