A week ago, I have released a bcpow() no fraction exponent work around. It is good, like all my scripts, but it is not fast. This code could take 12.152509 second to run the following statement on my pc.
bcroot(2,3072);
The main reason of slowness is in the algorithm. There is a bcpow() function that will use the second number passed to bcroot() as the exponent. In that above script, it multiple 2 for over 3000 times!
PHP's bcsqrt() function is very fast, with a little math knowledge you would know that 3072 = 1024 * 3 = 2^10 * 3. The new version of the function takes advantage of the bcsqrt()'s speed. So now, it runs bcsqrt() 10 times and then multiple it with bcpow($whatever, 3). Result? check this out:
0.000928 second.
You might want to know how did I found a number's product that is a power of 2.First of my attempt was do many bcmod() to the number until it returns 1. But then I noticed in binary, a number that have a product that is a power of 2, will have some trailing zeros, the number of trailing zeros is the exponent of 2. The other product will be what remains in the front. It will be great for you to know the function decbin() and bindec(). For example:
decbin(10) = 1010. 1 trailing zeros, so 10 = 2^1 * bindec(101)
decbin(12) = 1100. 2 trailing zeros, so 12 = 2^2 * bindec(11)
decbin(16) = 10000. 4 trailing zeros, so 16 = 2^4 * bindec(1)
Neat...I uses like 10 minute of my free time trying this...
Here is the new version of the function, it still need the bcgetscale() function that exists in my last post about bcroot()
//Version 0.3 of BCRoot //Change Log: It uses a lot bcsqrt() to make the speed fast //fix a small decimal bug, where the last decimal could be wrong //fix important bug. fufunction bcroot($a, $n, $scale='default'){ $default = bcgetscale();//Get the scale if($scale == 'default'){ $scale = $default;//use default scale } $limit = ceil(log($scale+15)/log(2))+1; if($n & ($n-1)){//check if $n is the power of 2, return 0 if is //decbin is the reason this function can't have number //larger than 2^31 $bin = decbin($n); $i = strlen($bin)-1; $pow = 0; while($i){ if($bin[$i]==='0'){ ++$pow; --$i; }else{ break; } } $n = bcdiv($n, bcpow(2, $pow),0); //now use Newton?s method to find the number $x = 1; $k = 0; bcscale($scale*$limit); while($k<$limit*$scale){ $t1 = bcdiv(1,$n); $t21 = bcmul(bcsub($n,1),$x); $t22 = bcdiv($a,bcpow($x, $n-1)); $t2 = bcadd($t21,$t22); $x = bcmul($t1, $t2); ++$k; } $i = 0; while($i < $pow){ $x = bcsqrt($x,$scale+3); ++$i; } $x = bcadd($x,0,$scale+1); }else{ //here use many bcsqrt, because this is FAST $i = 0; $pow = log($n)/log(2); while($i < $pow){ $a = bcsqrt($a,$scale*$limit); ++$i; } $x = bcadd($a,0,$scale+1); } bcscale($default); return bcround($x,$scale); } function bcround($x,$scale){ if($x[strlen($x)-1]<5){ $x = bcadd($x,0,$scale); }else{ $y = str_replace(array('1','2','3','4','5','6','7','8','9'),0,$x); $y[strlen($y)-1] = 10 - $x[strlen($x)-1]; $x = bcadd(bcadd($x,$y,$scale+1),0,$scale); } return $x; }
Comments
[...] New Upgrade on BCroot,
[...] New Upgrade on BCroot, speed boost [...]
Imprecise Results
Thanks Chao Xu for this great function, I've tested it with 32 precision digits but I'm getting imprecise results.
For instance:
bcroot(pow(3, 3), 3); // 3.00000054106417650071606484427419
And it gets even worse:
bcroot(pow(9, 3), 3); // 21.72463737470665690182336614385622
There seems to be some kind of bug in your function but thanks to my limited math skills I'm not able to understand where, or how to fix it.
Yes, this is buggy. Thx for
Yes, this is buggy. Thx for point it out.
I did this code when I was in high school and I had the assumption the precision error can be fixed by just a few constant more digits. Of course I was wrong. I just fixed the code, now it is likely to have the right answer. It is a lot slower... and I can't prove it will always produce the right answer.
btw in the new version, instead of trimming the unused digits, I rounded the last digit.
Post new comment