Hudzilla.org - the homepage of Paul Hudson
Contents > Miscellaneous topics Wish List | Report Bug | About Me ]

16.6     Arbitrary-precision mathematics

This is NOT the latest copy of this book; click here for the latest version.

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.





<< 16.5 Browser detection   16.7 Spellchecking and text matching >>
Table of Contents
Want to see this stuff in print? PHP in a Nutshell takes the core topics covered here, adds in thousands of edits from the editorial team and myself, and combines them to make an unbeatable reference for PHP programmers at all levels.



My latest book has hundreds more tips on how to use PHP, Apache, and MySQL, plus Perl, Python, shell scripts, performance tuning, and more!



Top-right shadow
 
Bottom-left shadow Bottom shadow

Comments from other readers
Wulf - 05 Dec 2008

Hi James,

Just like in old fashioned C, there's the modulo operator in php as well.

$x=20;
$remainder=$x % 7; //not precise but good enough for your purpose

Better use the discussed bcmath operators:

$remainder=bcmod($x, 7);

if($remainder==7) {
echo 'divisible';
} else {
echo 'not divisible';
}

Kind regards,
Wulf

msg4james@yahoo.co.uk - 05 Dec 2008

Hello there,

I was doing some basic PHP, and got to a point of wanting to know how to get the remainder of a division operation.

For instance, based on whether the number is divisible by 7 would simply check if the remainder is zero, otherwise its a NO!

But how do I achieve PHP checking X / 7 = remainder:-

if (remainder == '0')
{
echo "Is divisible";
}else
{
echo "Not divisible";
}

In old fashioned C, it used to be mod( X / 7); and that was done. Then you check for ZERO, otherwise it was not.

Hope to hear from some of you gurus of PHP.

Regards,
James.



Add comment
Please note that by posting a comment here you are committing it to the public domain. This is important so that others can make use of your code themselves, and also so that I can incorporate helpful notes directly into the main text. Comments are limited to 2000 characters in length.

If you are reporting an error in the content, please tell me directly.

Your name/email address:
Your comment:
 
Now, in order to verify that you're a real person, please answer this simple question: what is three plus six?
The answer is:
(please write in
numbers, eg 19)


Top-right shadow
 
Bottom-left shadow Bottom shadow