BC Math 函数

advertisement
预定义常量
bcadd
BC Math
在线手册:中文 英文
PHP 手册
BC Math 函数
Table of Contents










bcadd - Add two arbitrary precision numbers
bccomp - Compare two arbitrary precision numbers
bcdiv - Divide two arbitrary precision numbers
bcmod - Get modulus of an arbitrary precision number
bcmul - Multiply two arbitrary precision number
bcpow - Raise an arbitrary precision number to another
bcpowmod - Raise an arbitrary precision number to another, reduced by a
specified modulus
bcscale - Set default scale parameter for all bc math functions
bcsqrt - Get the square root of an arbitrary precision number
bcsub - Subtract one arbitrary precision number from another
预定义常量
bcadd
BC Math
在线手册:中文 英文
PHP 手册
用户评论:
artefact2 at gmail dot com (29-Jul-2010 09:18)
Here are some useful functions to convert large hex numbers from and
to large decimal ones :
<?php
public static function bchexdec($hex) {
if(strlen($hex) == 1) {
return hexdec($hex);
} else {
$remain = substr($hex, 0, -1);
$last = substr($hex, -1);
return bcadd(bcmul(16, bchexdec($remain)), hexdec($last));
}
}
public static function bcdechex($dec) {
$last = bcmod($dec, 16);
$remain = bcdiv(bcsub($dec, $last), 16);
if($remain == 0) {
return dechex($last);
} else {
return bcdechex($remain).dechex($last);
}
}
thomas at tgohome dot com (14-Nov-2009 07:17)
Some useful bcmath functions:
<?php
/*
* Computes the factoral (x!).
* @author Thomas Oldbury.
* @license Public domain.
*/
function bcfact($fact, $scale = 100)
{
if($fact == 1) return 1;
return bcmul($fact, bcfact(bcsub($fact, '1'), $scale), $scale);
}
/*
* Computes e^x, where e is Euler's constant, or approximately
2.71828.
* @author Thomas Oldbury.
* @license Public domain.
*/
function bcexp($x, $iters = 7, $scale = 100)
{
/* Compute e^x. */
$res = bcadd('1.0', $x, $scale);
for($i = 0; $i < $iters; $i++)
{
$res += bcdiv(bcpow($x, bcadd($i, '2'), $scale), bcfact(bcadd($i,
'2'), $scale), $scale);
}
return $res;
}
/*
* Computes ln(x).
* @author Thomas Oldbury.
* @license Public domain.
*/
function bcln($a, $iters = 10, $scale = 100)
{
$result = "0.0";
for($i = 0; $i < $iters; $i++)
{
$pow = bcadd("1.0", bcmul($i, "2.0", $scale), $scale);
//$pow = 1 + ($i * 2);
$mul = bcdiv("1.0", $pow, $scale);
$fraction = bcmul($mul, bcpow(bcdiv(bcsub($a, "1.0", $scale),
bcadd($a, "1.0", $scale), $scale), $pow, $scale), $scale);
$result = bcadd($fraction, $result, $scale);
}
$res = bcmul("2.0", $result, $scale);
return $res;
}
/*
* Computes a^b, where a and b can have decimal digits, be negative
and/or very large.
* Also works for 0^0. Only able to calculate up to 10 digits. Quite
slow.
* @author Thomas Oldbury.
* @license Public domain.
*/
function bcpowx($a, $b, $iters = 25, $scale = 100)
{
$ln = bcln($a, $iters, $scale);
return bcexp(bcmul($ln, $b, $scale), $iters, $scale);
}
$precision = 35;
echo "3^4.5, precision 15, iters 25 = " . bcpowx('3', '4.5', 25,
$precision) . "\n";
echo "4.5^3, precision 15, iters 25 = " . bcpowx('4.5', '3', 25,
$precision) . "\n";
echo "8^1/2, precision 15, iters 25 = " . bcpowx('8', '0.5', 25,
$precision) . "\n";
echo "28^-1, precision 15, iters 25 = " . bcpowx('28', '-1', 25,
$precision) . "\n";
echo "432^0, precision 15, iters 25 = " . bcpowx('432', '0', 25,
$precision) . "\n";
echo "0^0, precision 15, iters 25 = " . bcpowx('0.0', '0', 25,
$precision) . "\n";
echo "9^999, precision 15, iters 25 = " . bcpowx('9', '999', 25,
$precision) . "\n";
echo "9^9999, precision 15, iters 25 = " . bcpowx('9', '9999', 25,
$precision) . "\n";
echo "9^99999, precision 15, iters 25 = " . bcpowx('9', '99999', 25,
$precision) . "\n";
echo "9^999999, precision 15, iters 25 = " . bcpowx('9', '999999',
25, $precision) . "\n";
echo "9^9999999, precision 15, iters 25 = " . bcpowx('9', '9999999',
25, $precision) . "\n";
echo "9^99999999, precision 15, iters 25 = " . bcpowx('9',
'99999999', 25, $precision) . "\n";
echo "9^999999999, precision 15, iters 25 = " . bcpowx('9',
'999999999', 25, $precision) . "\n";
echo "9^9999999999, precision 15, iters 25 = " . bcpowx('9',
'9999999999', 25, $precision) . "\n";
echo "9^99999999999, precision 15, iters 25 = " . bcpowx('9',
'99999999999', 25, $precision) . "\n";
echo "9^999999999999, precision 15, iters 25 = " . bcpowx('9',
'999999999999', 25, $precision) . "\n";
?>
francois dot barbier at gmail dot com (13-Aug-2009 01:19)
As "benjcarson at digitaljunkies dot ca"
(http://www.php.net/ref.bc.php#23038) noted in the first two
comments, bcmath doesn't accept exponential notation.
Moreover, you might have other problems if you feed the bcmath
functions directly with floating point numbers.
Consider the following example:
<?php
bcscale(1);
$a = 0.8;
$b = 0.7;
var_dump((string) $a); // string(3) "0.8"
var_dump((string) $b); // string(3) "0.a"
var_dump(bcadd($a, $b)); // string(3) "1.5"
setLocale(LC_ALL, 'fr_BE.UTF-8');
var_dump((string) $a); // string(3) "0,8" --> note the comma
var_dump((string) $b); // string(3) "0,7" --> note the comma
var_dump(bcadd($a, $b)); // string(3) "0.0"
?>
The floating point numbers passed to the bcadd() function are
automatically converted to string using the localized decimal
separator. However, the bcmath functions always use a full stop,
which results in the last result being incorrect.
Below is a function to convert floating point numbers to strings
correctly. It takes care of the decimal separator and the exponential
notation. It also preserve the precision without drifting away (e.g.
1.0 doesn't become 0.99999...)
<?php
/**
* Convert a number to locale independent string without E notation
and without
* loosing precision
*
* @param int/float/double $fNumber The number to convert.
* @return string The locale independent converted number.
*/
function bcconv($fNumber)
{
$sAppend = '';
$iDecimals = ini_get('precision') - floor(log10(abs($fNumber)));
if (0 > $iDecimals)
{
$fNumber *= pow(10, $iDecimals);
$sAppend = str_repeat('0', -$iDecimals);
$iDecimals = 0;
}
return number_format($fNumber, $iDecimals, '.', '').$sAppend;
}
?>
Example:
<?php
setLocale(LC_ALL, 'fr_BE.UTF-8'); // decimal separator is now a comma
$precision = ini_get('precision') + 2; // should give 16
bcscale($precision);
$big = pow(10, $precision);
$small = 1 / $big;
var_dump(bcconv($big + $small)); // string(17) "10000000000000000"
var_dump(bcadd($big, $small)); // string(18) "0.0000000000000000"
var_dump(bcadd(bcconv($big), bcconv($small))); // string(34)
"10000000000000000.0000000000000001"
?>
The first result's precision loss is due to PHP's internal floating
point numbers' representation.
The second result is wrong because of the localized decimal
separator.
Finally, the last result is correct.
Charles (14-Aug-2008 01:04)
Function to round bc string:
<?php
function bcround($strval, $precision = 0) {
if (false !== ($pos = strpos($strval, '.')) && (strlen($strval) $pos - 1) > $precision) {
$zeros = str_repeat("0", $precision);
return bcadd($strval, "0.{$zeros}5", $precision);
} else {
return $strval;
}
}
?>
vinni10 at gmx dot net (28-Jul-2008 09:48)
Here is a better bcrand() version.
It uses the standart mt_rand() funktion and should work on other
systems then unix ;-)
<?php
/**
* BCrand - Generates very big random numbers.
* string bcrand([mixed $min], mixed $max);
*/
function bcrand($min, $max=false)
{
if(!$max)
{
$max = $min;
$min = 0;
}
return bcadd(
bcmul(
bcdiv(
mt_rand(0, mt_getrandmax()),
mt_getrandmax(),
strlen($max)
),
bcsub(
bcadd($max,1),
$min
)
),
$min
);
}
echo bcrand("0",
"9999999999999999999999999999999999999999999999999999999999");
?>
Hope i could help someone :-)
Bouke Haarsma (29-Mar-2008 07:40)
Please be aware not to use/have spaces in your strings. It took me a
while to find the error in some advanced calculations!
<?php
echo bcadd("1", "2"); // 3
echo bcadd("1", "2 "); // 1
echo bcadd("1", " 2"); // 1
?>
bebaWork at gmail dot com (05-Dec-2007 10:11)
For install on Feora Core:
> yum install php-bcmath
> /etc/init.d/httpd restart
udochen at gmail dot com (28-Feb-2007 03:48)
Code below implements standard rounding on 5 or higer round up, else
don't round. There wasn't a round function for the BC functions, so
here is a simple one that works. Same args as round, except takes
strings and returns a string for more BC operations.
---------------function roundbc($x, $p) {
$x = trim($x);
$data = explode(".",$x);
if(substr($data[1],$p,1) >= "5") {
//generate the add string.
$i=0;
$addString = "5";
while($i < $p) {
$addString = "0" . $addString;
$i++;
}//end while.
$addString = "." . $addString;
//now add the addString to the original fraction.
$sum = bcadd($data[0] . "." . $data [1],$addString,$p+1);
//explode the result.
$sumData = explode(".",$sum);
//now, return the correct precision on the rounded number.
return $sumData[0] . "." . substr($sumData[1],0,$p);
} else {
//don't round the value and return the orignal to the desired
//precision or less.
return $data[0] . "." . substr($data[1],0,$p);
}//end if/else.
}//end roundbc.
mgcclx at gmail dot com (30-Jan-2007 10:52)
I wrote this function with many BCMath functions. It should be the
fastest function in PHP to find the number pi into any precision, my
test is it generate 2000 digits after the dot in 8 seconds. I don't
think you need anything more than that.
<?php
//bcpi function with Gauss-Legendre algorithm
//by Chao Xu (Mgccl)
function bcpi($precision){
$limit = ceil(log($precision)/log(2))-1;
bcscale($precision+6);
$a = 1;
$b = bcdiv(1,bcsqrt(2));
$t = 1/4;
$p = 1;
while($n < $limit){
$x = bcdiv(bcadd($a,$b),2);
$y = bcsqrt(bcmul($a, $b));
$t = bcsub($t, bcmul($p,bcpow(bcsub($a,$x),2)));
$a = $x;
$b = $y;
$p = bcmul(2,$p);
++$n;
}
return bcdiv(bcpow(bcadd($a, $b),2),bcmul(4,$t),$precision);
}
?>
marcus at synchromedia dot co dot uk (10-Aug-2006 12:17)
Oops, first posting contained wrong code... sorry.
An amendment to the entry by pulstar at mail dot com - the digits()
function can be made much faster (remove the line breaks from the big
string, and make sure you don't miss any characters!):
function digits2($base) {
if($base < 64) {
return substr('0123456789abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ-_', 0, $base);
} else {
return substr("\x0\x1\x2\x3\x4\x5\x6\x7\x8\x9\xa\xb\xc\xd
\xe\xf\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d
\x1e\x1f\x20!\x22#\x24%&'()*+,-./0123456789:;<=>
\x3f@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]
^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85
\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95
\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5
\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5
\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5
\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5
\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xe0\xe1\xe2\xe3\xe4\xe5
\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6
\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff", 0, $base);
}
}
in my benchmarks, this is around 150x faster for 256 digits
stonehew ut gm a il det com (25-Nov-2004 01:31)
Like any other bc function, you can't trust the last couple of
digits, but everything else seems to check out. If you want to use
this for anything important, you may want to verify this against
other sources of pi before use. This function calculates 100 decimal
places of pi in 329 iterations -- not exactly fast (each iteration
calls the factorial function, from below, twice), so I try to avoid
calling it more than once.
<?
//arbitrary precision pi approximator
//author tom boothby
//free for any use
function bcpi() {
$r=2;
$i=0;
$or=0;
while(bccomp($or,$r)) {
$i++;
$or=$r;
$r = bcadd($r,bcdiv(bcmul(bcpow(bcfact($i),2),
bcpow(2,$i+1)),bcfact(2*$i+1)));
}
return $r;
}
?>
stonehew et g m a i l dut com (24-Nov-2004 03:20)
I hacked these taylor expansions up to make diagrams for some physics
homework. I don't think you'll be wanting to do any real science with
PHP... but what the hell, why not? I plan to implement either a
spigot algorithm or something similar to generate pi in the near
future.
<?
// arbitrary precision sin and cosine functions
// author tom boothby
// free for any use
function bcfact($n) {
$r = $n--;
while($n>1) $r=bcmul($r,$n--);
return $r;
}
function bcsin($a) {
$or= $a;
$r = bcsub($a,bcdiv(bcpow($a,3),6));
$i = 2;
while(bccomp($or,$r)) {
$or=$r;
switch($i%2) {
case 0: $r = bcadd($r,bcdiv(bcpow($a,$i*2+1),bcfact($i*2+1))); break;
default: $r = bcsub($r,bcdiv(bcpow($a,$i*2+1),bcfact($i*2+1)));
break;
}
$i++;
}
return $r;
}
function bccos($a) {
$or= $a;
$r = bcsub(1,bcdiv(bcpow($a,2),2));
$i = 2;
while(bccomp($or,$r)) {
$or=$r;
switch($i%2) {
case 0: $r = bcadd($r,bcdiv(bcpow($a,$i*2),bcfact($i*2))); break;
default: $r = bcsub($r,bcdiv(bcpow($a,$i*2),bcfact($i*2))); break;
}
$i++;
}
return $r;
}
?>
Diabolos at GMail dot com (29-Oct-2004 02:42)
Here's a function to compute the natural exponential function in
arbitrary precision using the basic bcMath arithmetic operations.
EXAMPLE:
To compute the exponential function of 1.7 to 36 decimals:
$y = bcExp("1.7", 36);
The result:
4.331733759839529271053448625299468628
would be returned in variable $y
NOTE:
In practice, the last couple of digits may be inaccurate due to small
rounding errors. If you require a specific degree of precision,
always compute 3-4 decimals beyond the required precision.
The program code for the natural exponential function is:
******************************************
Function bcExp($xArg, $NumDecimals)
{
$x = Trim($xArg);
$PrevSum = $x - 1;
$CurrTerm = 1;
$CurrSum = bcAdd("1", $x, $NumDecimals);
$n = 1;
While (bcComp($CurrSum, $PrevSum, $NumDecimals))
{
$PrevSum = $CurrSum;
$CurrTerm = bcDiv(bcMul($CurrTerm, $x, $NumDecimals), $n + 1,
$NumDecimals);
$CurrSum = bcAdd($CurrSum, $CurrTerm, $NumDecimals);
$n++;
}
Return $CurrSum;
}
robert at scabserver dot com (04-Jun-2004 12:58)
I spent some time looking for how to generate a large random number,
in the end I've settled for reading directly from /dev/urandom
I know this is a *nix only solution, but I figured that it might come
in handy to someone else.
The value $size is the size in bits, it could be simplified greatly
if you want the size in bytes, but bits was more helpful to what I
needed.
<?php
function bcrand($size)
{
$filename = "/dev/urandom";
$handle = fopen($filename, "r");
$bin_urand = fread($handle, ceil($size/8.0));
fclose($handle);
$mask = (($size % 8 < 5) ? '0' : '') . dechex(bindec(str_repeat('1',
$size % 8))) . str_repeat('FF', floor($size/8));
$binmask = pack("H*", $mask);
$binrand = $binmask & $bin_urand;
$hexnumber = unpack("H*", $binrand);
$hexnumber = $hexnumber[''];
$numlength = strlen($hexnumber);
$decnumber = 0;
for($x = 1; $x <= $numlength; $x++)
{
$place = $numlength - $x;
$operand = hexdec(substr($hexnumber,$place,1));
$exponent = bcpow(16,$x-1);
$decValue = bcmul($operand, $exponent);
$decnumber = bcadd($decValue, $decnumber);
}
return $decnumber;
}
?>
pulstar at mail dot com (16-Apr-2003 08:12)
A little comment for the simplified example above: you can do base
converting without BCMath functions using only math operators, but
you will not able to manage very large values or work with strings to
compress or scramble data. If you have BCMath installed in your
system it worth use it for this.
oliver at summertime dot net (02-Mar-2003 02:12)
A simplier Version of the Script above:
function dec2base($dec, $digits) {
$value = "";
$base = strlen($digits);
while($dec>$base-1) {
$rest = $dec % $base;
$dec = $dec / $base;
$value = $digits[$rest].$value;
}
$value = $digits[intval($dec)].$value;
return (string) $value;
}
function base2dec($value, $digits) {
$value = strtoupper($value);
$base = strlen($digits);
$size = strlen($value);
$dec = '0';
for ($loop = 0; $loop<$size; $loop++) {
$element = strpos($digits,$value[$loop]);
$power = pow($base,$size-$loop-1);
$dec += $element * $power;
}
return (string) $dec;
}
$digits = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
echo dec2base('1000', $digits);
pulstar at mail dot com (20-Sep-2002 10:23)
A found a little fix to do in my base2dec() function:
The line "if($base<37) $value=strtolower($value);" should be removed
if you want to specify another digits for your base conversions.
Change it this way:
if(!$digits) {
$digits=digits($base);
if($base<37) {
$value=strtolower($value);
}
}
Another example using these functions is to generate a key for a
session, to name temporary files or something else:
srand((double) microtime()*1000000);
$id=uniqid(rand(10,999));
$mykey=dec2base(base2dec($id,16),64);
$mykey is a base64 value, which is a good key for passing thru an URL
and also is shorter than a MD5 string (it will be allways 11 chars
long). If you need something more secure, just scramble the 64 digits
in the digits() function.
Well, I hope you enjoy it.
Regards,
Edemilson Lima
pulstar at mail dot com (20-Sep-2002 11:27)
A good use for BCMath functions:
The functions below can convert a number in any base (from 2 to 256)
to its decimal value and vice-versa.
// convert a decimal value to any other base value
function dec2base($dec,$base,$digits=FALSE) {
if($base<2 or $base>256) die("Invalid Base: ".$base);
bcscale(0);
$value="";
if(!$digits) $digits=digits($base);
while($dec>$base-1) {
$rest=bcmod($dec,$base);
$dec=bcdiv($dec,$base);
$value=$digits[$rest].$value;
}
$value=$digits[intval($dec)].$value;
return (string) $value;
}
// convert another base value to its decimal value
function base2dec($value,$base,$digits=FALSE) {
if($base<2 or $base>256) die("Invalid Base: ".$base);
bcscale(0);
if($base<37) $value=strtolower($value);
if(!$digits) $digits=digits($base);
$size=strlen($value);
$dec="0";
for($loop=0;$loop<$size;$loop++) {
$element=strpos($digits,$value[$loop]);
$power=bcpow($base,$size-$loop-1);
$dec=bcadd($dec,bcmul($element,$power));
}
return (string) $dec;
}
function digits($base) {
if($base>64) {
$digits="";
for($loop=0;$loop<256;$loop++) {
$digits.=chr($loop);
}
} else {
$digits ="0123456789abcdefghijklmnopqrstuvwxyz";
$digits.="ABCDEFGHIJKLMNOPQRSTUVWXYZ-_";
}
$digits=substr($digits,0,$base);
return (string) $digits;
}
The purpose of digits() function above is to supply the characters
that will be used as digits for the base you want. NOTE: You can use
any characters for that when you convert to another base, but when
you convert again to the decimal base, you need to use the same
characters or you will get another unexpected result.
benjcarson at digitaljunkies dot ca (08-Jul-2002 08:00)
In addition to my last note, here are a quick pair of functions to
convert exponential notation values into bcmath-style number strings:
// exp2int converts numbers in the
// form "1.5e4" into strings
function exp2int($exp) {
list($mantissa, $exponent) = spliti("e", $exp);
list($int, $dec) = split("\.", $mantissa);
bcscale ($dec);
return bcmul($mantissa, bcpow("10", $exponent));
}
// float2exp converts floats into exponential notation
function float2exp($num) {
if (0 == $num) { return "0E1";}
list($int, $dec) = split("\.", $num);
// Extract sign
if ($int[0] == "+" || $int[0] == "-") {
$sign = substr($int, 0,1);
$int = substr($int, 1);
}
if (strlen($int) <= 1) { // abs($num) is less than 1
$i=0;
for ($i=0; $dec[$i]=='0' && $i < strlen($dec); $i++);
$exp = -$i-1;
$mantissa = substr($dec,$i,1).".".substr($dec,$i+1);
} else { // abs($num) is greater than 1
$i=0;
for ($i=0; $int[$i]=='0' && $i < strlen($int); $i++);
$exp = strlen($int)-1 - $i;
$mantissa = substr($int,$i,1).".".substr($int,$i+1).$dec;
}
return ($sign . $mantissa . "E" . $exp);
}
benjcarson at digitaljunkies ca (08-Jul-2002 07:17)
Note that bcmath doesn't seem to handle numbers in exponential
notation (i.e. "1e4"), although PHP considers such a value a number.
example:
$exp1 = "1E5";
$exp2 = "2E4";
$ans1 = bcadd($exp1, $exp2, 3);
$ans2 = $exp1 + exp2;
echo("bcadd: $exp1 + $exp2 = $ans1");
echo("php: $exp1 + $exp2 = $ans2");
// Output:
bcadd: 1E5 + 2E4 = 0.000
php: 1E5 + 2E4 = 120000
Just a gotcha if you're using passing PHP numbers into bcmath
functions...
Download