Arbitrary-precision mathematics

Earlier we looked at how using the equality operator would produce incorrect results when working with very large numbers. While we looked at one potential workaround (using === and comparing the numbers as strings) a better option is to use arbitrary-precision mathematics. That is, mathematics that is able to work on numbers as large as you want.

You may ask, "why, then, doesn't PHP use arbitrary-precision mathematics for everything?" To which the answer is, "because it's exceptionally slow!" Arbitrary-precision mathematical calculations have to jump through many more hoops than just blindingly manipulating numbers. I ran a simple benchmark that adds numbers together millions of times: it took 8 seconds using normal adding and 39 using arbitrary-precision maths. As these basic operations are used in many, many scripts, it would cause a serious slowdown in PHP if arbitrary-precision maths were used everywhere.

To add arbitrary-precision mathematics support to your Unix box, add --enable-bcmath to your configure line and recompile; Windows users need do nothing. Once you have it working, there are a handful of functions available to you:

string bcadd ( string left_operand, string right_operand [, int scale]) int bccomp ( string left_operand, string right_operand [, int scale]) string bcdiv ( string left_operand, string right_operand [, int scale]) string bcmod ( string left_operand, string modulus) string bcmul ( string left_operand, string right_operand [, int scale]) string bcpow ( string x, string y [, int scale]) string bcpowmod ( string x, string y, string modulus [, int scale]) bool bcscale ( int scale) string bcsqrt ( string operand [, int scale]) string bcsub ( string left_operand, string right_operand [, int scale])

Of those, bcdiv(), bcmod(), bcmul(), bcpow(), bcsqrt(), and bcsub() all work as you would expect them to. The bcdiv() function, for example, divides parameter one by parameter two, and returns the value.

The bccomp() and bcscale() functions are the only two that return something other than a string. The others return as strings because otherwise very large numbers will go crazy. Here's an example of bcadd() in action:

<?php
    $foo
=  2147483647;
    
$foo = bcadd($foo, 1);
    
var_dump($foo);
?>

I've used var_dump() at the end there so you will see that $foo gets changed to a string by bcadd(). As you can see, it's pretty predictable stuff. Note that the calculation functions all have an extra parameter to handle scale - this is the number of decimal places to be used. If you don't specify this, the default value is 0, which essentially rounds the value up.

As it's not very good to simply trim off all the decimal places in your answers, you have three choices: specify a scale parameter with each calculate, use the bcscale() function, which sets the number of decimal places to use for all future calculations, or change the bcmath.scale option in your php.ini file to something higher than 0. Here's bcscale() in code:

<?php
    $foo
=  2.12345;
    
$trimmed = bcadd($foo, 1);
    
bcscale(8);
    
$untrimmed = bcadd($foo, 1);

    print
"Trimmed: $trimmed\n";
    print
"Untrimmed: $untrimmed\n";
?>

That will output the following:

Trimmed: 3
Untrimmed: 3.12345000

Note that the second answer has unnecessary 0s at the end because we set the scale higher than necessary. This isn't a problem - better to have too much accuracy than too little when working with these sorts of calculations.

The second of three functions that need explaining (the first being bcscale()) is bcpowmod(). We have bcmod() to calculate the modulus of two numbers (that is, divide parameter one by parameter two and return the remainder), and we have bcpow() to raise parameter one to the power of parameter 2. The bcpowmod() function is a combination of the two: it raises parameter one to the power of parameter three, then gets the modulus of that result with parameter three. This actually works out faster than using them both separately, hence the extra function.

Here's a code example that should illustrate the point:

<?php
    $foo
= 5;
    
$bar = 3;
    echo
"Using bcpowmod(): ", bcpowmod($foo, $bar, 6), "\n";

    
$powfoobar = bcpow($foo, $bar);
    echo
"Using individual functions: ", bcmod($foo, 6), "\n";
?>

Both of those two calculations will return the same result, as the second does the same thing as the first, just in more lines of code.

The final potentially confusing arbitrary-mathematics function is bccomp(), and it's confusing because it works like strcmp() function: if the number in parameter one is larger than that of parameter two it returns 1, if parameter two is larger it returns -1, and if the two numbers are the same it returns 0.

This is further confused by the extra scale parameter that's common to the arbitrary-mathematics functions. In bccomp() it decides how many decimal places should be used in the comparison, which means that comparing 1 against 1.01 when the scale is 0 will return 0, meaning "the numbers are identical".

Here's an example to try out:

<?php
    $foo
= "5.0001";
    
$bar = "5.0002";

    echo
"Foo = bar? ", bccomp($foo, $bar), "\n";
    
bcscale(4);
    echo
"Foo = bar? ", bccomp($foo, $bar), "\n";
?>

That will output 0 then -1, meaning that first the numbers are identical then, after changing the scale to 4, the second number is seen as being larger.

Arbitrary-precision mathematics have their place wherever very exacting standards of mathematics are required. Most of the time, for performance reasons if nothing else, you should be using the standard arithmetic operators like + and -. That said, my usual mantra of "it's better that you know it and not use it, than not know it and need it next week" applies.

 

Next chapter: Spellchecking and text matching >>

Previous chapter: Browser detection

Jump to:

 

Home: Table of Contents

Follow us on Identi.ca or Twitter

Username:   Password:
Create Account | About TuxRadar