具有bcmath的快速任意精度对数

时间:2014-07-24 22:40:56

标签: php bcmath

这就是我所拥有的

function bcln($n, $scale=10) {
    $iscale = $scale+3;
    $result = '0.0';
    $i = 0;

    do {
        $pow = (1 + (2 * $i++));
        $mul = bcdiv('1', $pow, $iscale);
        $fraction = bcmul($mul, bcpow(bcsub($n, '1', $iscale) / bcadd($n, '1.0', $iscale), $pow, $iscale), $iscale);
        $lastResult = $result;
        $result = bcadd($fraction, $result, $iscale);
    } while($result !== $lastResult);

    return bcmul('2', $result, $scale);
}

但这需要5.7秒才能运行bcln(100)(自然对数为100,小数点后10位)。此外,对于更多的小数位,它并不总是准确的。有更好的算法吗?

对于该特定运行,需要573次迭代来确定结果。

1 个答案:

答案 0 :(得分:1)

你需要一个任意长度的字符串作为答案吗?或者您需要任意精度,还是任意指数大小? 或者......双精度浮点答案(返回值)就足够了;鉴于我们只是"只有"使用许多" 任意"的对数大小

双精度浮点数具有11位有符号指数:因此,如果您的大数字字符串长度≤1022位≈307位十进制数字(所以字符串长度为306个字符,包括小数点),您就安全了!更准确地说,如果得到的十进制指数的绝对值≤307,则应该是安全的。你需要更大的指数吗? (换句话说,我想:你是在处理现实世界的数字还是理论/纯数学?)

为什么不使用一些字符串处理,以及一些简单的浮点日志算法?对于任何真实世界的数字,这应该非常快 ......

function bclog10($n){
    //←Might need to implement some validation logic here!
    $pos=strpos($n,'.');
    if($pos===false){
        $dec_frac='.'.substr($n,0,15);$pos=strlen($n);
    }else{  $dec_frac='.'.substr(substr($n,0,$pos).substr($n,$pos+1),0,15);
    }
    return log10((float)$dec_frac)+(float)$pos;
}

您可以使用一些众所周知的对数算法转换基数:

function bclogn($n,$base=M_E){//$base should be float: default is e
    return bclog10($n)*log(10)/log($base);
}

我已经测试了这些功能,它们适合我,我提供的例子;提供与Windows 10计算器完全相同的答案,达到PHP使用的双精度算术的极限。

如果您实际需要超过15位数的精度而小于307的十进制指数,您可能能够实现自己的" BigFloat"类对象,并以某种方式使用分而治之的方法从标准的内置浮点函数构建其方法!那么也许,我们可以将其作为任意精度浮点对数算法的基础,通过将其与上述函数/技术相结合。您可能需要考虑咨询math.stackexchange.com的人员,以了解更多关于这是否可行的方法。

MAJOR EDIT:第二次尝试......

function bclog10($n){//By Matthew Slyman @aaabit.com
    $m=array();// ↓ Validation, matching/processing regex…
    preg_match('/^(-)?0*([1-9][0-9]*)?(\.(0*))?([1-9][0-9]*)?([Ee](-)?0*([1-9][0-9]*))?$/',$n,$m);
    if(!isset($m[1])){throw new \Exception('Argument: not decimal number string!');}
    $sgn=$m[1];if($sgn==='-'){throw new \Exception('Cannot compute: log(<⁺0)!');}
    $abs=$m[2];$pos=strlen($abs);
    if(isset($m[4])){$fre=$m[4];}else{$fre='';}$neg=strlen($fre);
    if(isset($m[5])){$frc=$m[5];}else{$frc='';}
    if(isset($m[7])){$esgn=$m[7]==='-'?-1:1;}else{$esgn=1;}
    if(isset($m[8])){$eexp=$m[8];}else{$eexp=0;}
    if($pos===0){
        $dec_frac='.'.substr($frc,0,15);$pos=-1*$neg;
    }else{  $dec_frac='.'.substr($abs.$fre.$frc,0,15);
    }
    return log10((float)$dec_frac)+(float)$pos+($esgn*$eexp);
}