2024 年 PHP 日本研討會

BC 數學函式

目錄

  • bcadd — 兩個任意精度數字相加
  • bcceil — 無條件進位任意精度數字
  • bccomp — 比較兩個任意精度數字
  • bcdiv — 兩個任意精度數字相除
  • bcdivmod — 取得任意精度數字的商數和餘數
  • bcfloor — 無條件捨去任意精度數字的小數部分
  • bcmod — 取得任意精度數字的模數
  • bcmul — 乘以兩個任意精度數字
  • bcpow — 將任意精度數字乘方至另一個數字
  • bcpowmod — 將任意精度數字乘方至另一個數字,並以指定的模數取餘數
  • bcround — 四捨五入任意精度數字
  • bcscale — 設定或取得所有 bc 數學函式的預設小數位數參數
  • bcsqrt — 取得任意精度數字的平方根
  • bcsub — 從另一個任意精度數字減去一個任意精度數字
新增註解

使用者貢獻的註解 18 則註解

Bouke Haarsma
16 年前
請注意不要在字串中使用/包含空格。 我花了一段時間才找到一些進階計算中的錯誤!

<?php
echo bcadd("1", "2"); // 3
echo bcadd("1", "2 "); // 1
echo bcadd("1", " 2"); // 1
?>
dawidgarus at gmail dot com
12 年前
如果您也對像這樣的難以理解的程式碼感到困惑
<?php
$a
= "3";
$b = "5";
bcadd(bcmod(bcadd(bcdiv(bcsqrt(bcadd(7, bcpow($a, 2))), 4), $b), "4"), "0.5"); // 我可能在哪裡犯了個錯誤
?>

您可以考慮使用我的函式,它使上面的範例看起來像
<?php
bc
("(sqrt(7 + $1^2) / 4 + $2) % 4 + 0.5", "3", "5");
?>

程式碼
<?php

function bc() {
$functions = 'sqrt';
// list of | separated functions
// sqrt refer to bcsqrt etc.
// function must take exactly 1 argument

$argv = func_get_args();
$string = str_replace(' ', '', '('.$argv[0].')');
$string = preg_replace('/\$([0-9\.]+)/e', '$argv[$1]', $string);
while (
preg_match('/(('.$functions.')?)\(([^\)\(]*)\)/', $string, $match)) {
while (
preg_match('/([0-9\.]+)(\^)([0-9\.]+)/', $match[3], $m) ||
preg_match('/([0-9\.]+)([\*\/\%])([0-9\.]+)/', $match[3], $m) ||
preg_match('/([0-9\.]+)([\+\-])([0-9\.]+)/', $match[3], $m)
) {
switch(
$m[2]) {
case
'+': $result = bcadd($m[1], $m[3]); break;
case
'-': $result = bcsub($m[1], $m[3]); break;
case
'*': $result = bcmul($m[1], $m[3]); break;
case
'/': $result = bcdiv($m[1], $m[3]); break;
case
'%': $result = bcmod($m[1], $m[3]); break;
case
'^': $result = bcpow($m[1], $m[3]); break;
}
$match[3] = str_replace($m[0], $result, $match[3]);
}
if (!empty(
$match[1]) && function_exists($func = 'bc'.$match[1])) {
$match[3] = $func($match[3]);
}
$string = str_replace($match[0], $match[3], $string);
}
return
$string;
}

?>

請注意,您必須使用 bcscale() 定義小數位數。
artefact2 at gmail dot com
14 年前
以下是一些用於將大型十六進位數字與大型十進位數字互相轉換的實用函式

<?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);
}
}
pulstar at mail dot com
22 年前
我在我的 base2dec() 函式中發現了一個小錯誤需要修正
如果您想要為您的進位轉換指定其他數字,則應移除「if($base<37) $value=strtolower($value);」這一行。將其更改為以下方式

if(!$digits) {
$digits=digits($base);
if($base<37) {
$value=strtolower($value);
}
}

使用這些函式的另一個例子是產生 session 的 key,用於命名臨時檔案或其他用途

srand((double) microtime()*1000000);
$id=uniqid(rand(10,999));
$mykey=dec2base(base2dec($id,16),64);

$mykey 是一個 64 進位的數值,它很適合透過 URL 傳遞,而且比 MD5 字串短(它永遠是 11 個字元長)。如果您需要更安全的東西,只需在 digits() 函式中打亂 64 個數字即可。

我希望你會喜歡它。

此致,
Edemilson Lima
stonehew et g m a i l dut com
20 年前
我修改了這些泰勒展開式,以便為一些物理作業製作圖表。我不認為您會想要用 PHP 做任何真正的科學研究……但管他呢,為什麼不呢?我計劃在不久的將來實作 spigot 演算法或類似的方法來產生圓周率。

<?
// 任意精度的正弦和餘弦函數
// 作者 tom boothby
// 可自由使用

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;
}

?>
stonehew ut gm a il det com
20 年前
如同其他 bc 函式一樣,您不能相信最後幾位數字,但其他一切似乎都檢查無誤。如果您想將此用於任何重要的事情,您可能需要在使用前根據其他 pi 來源驗證這一點。此函式在 329 次迭代中計算 pi 的 100 位小數——並非完全快速(每次迭代都會呼叫兩次階乘函式,如下所示),所以我盡量避免多次呼叫它。

<?
//任意精度 pi 近似器
//作者 tom boothby
//可自由使用

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;
}

?>
pulstar at mail dot com
21 年前
關於上面簡化範例的一些註釋:您可以僅使用數學運算子而不使用 BCMath 函式來執行基數轉換,但您將無法管理非常大的值或使用字串來壓縮或加擾資料。如果您的系統中安裝了 BCMath,則值得使用它。
oliver at summertime dot net
21 年前
上面腳本的簡化版本

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);
kmeissen at gmx dot de
8 年前
/*
* 即使在高解析度下也能非常快速地計算 ln(x)
* 使用標準 log() 函式來優化
* ln(1+x) = x - x*x/2 + x*x*x/3 - ... 的收斂性
*
* 範例
* bcscale(1000);
* $x = bcln("1000000");
*
* 結果
* $x = 13.81551055796427410410794872810618524560660893...
* 在 0.9 秒內,即 80 次迭代
*
* @author Klaus Meissen, 德國
* @license 公共領域
*/
function bcln($value) // value > 0
{
$m = (string)log($value);
$x = bcsub(bcdiv($value,bcexp($m)),"1");
$res = "0";
$xpow = "1";
$i=0;
do
{
$i++;
$xpow = bcmul($xpow,$x);
$sum = bcdiv($xpow, $i);
if ($i%2==1)
{
$res = bcadd($res, $sum);
}else{
$res = bcsub($res, $sum);
}
}
while (bccomp($sum, '0'));

(譯)當 (bccomp($sum, '0')) 為真時持續執行迴圈;
return bcadd($res,$m);

(譯)回傳 bcadd($res,$m) 的結果;
}

francois dot barbier at gmail dot com

(譯) francois.barbier@gmail.com

15 years ago

(譯)15 年前

As "benjcarson at digitaljunkies dot ca" (https://php.dev.org.tw/ref.bc.php#23038) noted in the first two comments, bcmath doesn't accept exponential notation.

(譯)如同 "benjcarson@digitaljunkies.ca" (https://php.dev.org.tw/ref.bc.php#23038) 在前兩則留言中提到的,bcmath 不接受科學記號(指數表示法)。

Moreover, you might have other problems if you feed the bcmath functions directly with floating point numbers.

(譯)此外,如果您直接將浮點數傳入 bcmath 函式,可能會遇到其他問題。
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.

(譯)<?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" --> 注意這裡是用逗號
var_dump((string) $b); // string(3) "0,7" --> 注意這裡是用逗號
var_dump(bcadd($a, $b)); // string(3) "0.0"
?>
傳入 bcadd() 函式的浮點數會自動轉換為字串,並使用本地化的十進位分隔符號。然而,bcmath 函式一律使用句點作為分隔符號,導致最後的結果不正確。

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...)

(譯)以下是一個將浮點數正確轉換為字串的函式。它會處理十進位分隔符號和科學記號,同時也能保留精度,避免數值漂移(例如,1.0 不會變成 0.99999...)。
<?php
/**
* 將數字轉換為與地區設定無關的字串,不使用科學記號表示法且不損失精度
*
* @param int/float/double $fNumber 要轉換的數字。
* @return string 與地區設定無關的已轉換數字。
*/
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;
}
?>

範例
<?php
setLocale
(LC_ALL, 'fr_BE.UTF-8'); // 小數點分隔符號現在是逗號
$precision = ini_get('precision') + 2; // 應為 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"
?>
第一個結果的精度損失是由於 PHP 內部浮點數的表示方式所致。
第二個結果錯誤是由於地區設定的小數點分隔符號。
最後,最後一個結果是正確的。

robert at scabserver dot com
20 年前
我花了一些時間尋找如何產生一個大的隨機數,最後我決定直接從 /dev/urandom 讀取。

我知道這是一個僅限 *nix 的解決方案,但我認為它可能對其他人有用。

值 $size 是以位元為單位的長度,如果您想要以位元組為單位,可以大幅簡化,但位元對我的需求更有幫助。

<?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;
}
?>
mail at djordjeungar dot com
11 年前
受到 dawidgarus 實作的啟發,這是我的簡易 bc math 輔助函式,它不支援函式呼叫,但支援布林值比較,速度大約快了 40%。

<?php
function bc() {
$argv = func_get_args();
$string = str_replace(' ', '', "({$argv[0]})");

$operations = array();
if (
strpos($string, '^') !== false) $operations[] = '\^';
if (
strpbrk($string, '*/%') !== false) $operations[] = '[\*\/\%]';
if (
strpbrk($string, '+-') !== false) $operations[] = '[\+\-]';
if (
strpbrk($string, '<>!=') !== false) $operations[] = '<|>|=|<=|==|>=|!=|<>';

$string = preg_replace('/\$([0-9\.]+)/e', '$argv[$1]', $string);
while (
preg_match('/\(([^\)\(]*)\)/', $string, $match)) {
foreach (
$operations as $operation) {
if (
preg_match("/([+-]{0,1}[0-9\.]+)($operation)([+-]{0,1}[0-9\.]+)/", $match[1], $m)) {
switch(
$m[2]) {
case
'+': $result = bcadd($m[1], $m[3]); break;
case
'-': $result = bcsub($m[1], $m[3]); break;
case
'*': $result = bcmul($m[1], $m[3]); break;
case
'/': $result = bcdiv($m[1], $m[3]); break;
case
'%': $result = bcmod($m[1], $m[3]); break;
case
'^': $result = bcpow($m[1], $m[3]); break;
case
'==':
case
'=': $result = bccomp($m[1], $m[3]) == 0; break;
case
'>': $result = bccomp($m[1], $m[3]) == 1; break;
case
'<': $result = bccomp($m[1], $m[3]) ==-1; break;
case
'>=': $result = bccomp($m[1], $m[3]) >= 0; break;
case
'<=': $result = bccomp($m[1], $m[3]) <= 0; break;
case
'<>':
case
'!=': $result = bccomp($m[1], $m[3]) != 0; break;
}
$match[1] = str_replace($m[0], $result, $match[1]);
}
}
$string = str_replace($match[0], $match[1], $string);
}

return
$string;
}
?>
eyadbere at gmail dot com
4 年前
如果您的計算是基於使用者輸入,別忘了設定輸入範圍和/或長度的限制,因為非常大的數字需要大量的處理,您可能會遇到伺服器無法繼續處理的情況。
Charles
16 年前
用於對 bc 字串進行四捨五入的函式

<?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;
}
}
?>
pulstar at mail dot com
22 年前
BCMath 函式的良好應用
以下函式可以將任何進位(從 2 到 256)的數字轉換為其十進位值,反之亦然。

// 將十進位值轉換為任何其他進位值
function dec2base($dec,$base,$digits=FALSE) {
if($base<2 or $base>256) die("進位無效: ".$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;
}

// 將其他進位值轉換為其十進位值
function base2dec($value,$base,$digits=FALSE) {
if($base<2 or $base>256) die("進位無效: ".$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;
}

上述 digits() 函式的目的是提供將用作您想要進位的數字的字元。注意:當您轉換為其他進位時,您可以使用任何字元,但是當您再次轉換為十進位時,您需要使用相同的字元,否則您將得到另一個意想不到的結果。
mgcclx at gmail dot com
17 年前
我用許多 BCMath 函式編寫了這個函式。它應該是 PHP 中計算圓周率到任意精度的最快函式,我的測試是它在 8 秒內生成小數點後 2000 位數字。我不認為您需要更多了。
<?php
// 使用高斯-勒壤得算法計算圓周率的 bcpi 函數
// 作者: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);
}
?>
udochen at gmail dot com
17 年前
以下程式碼實現了標準的四捨五入,5 或更高則進位,否則不進位。BC 函數沒有內建的四捨五入函數,所以這裡有一個簡單的實現方法。參數與 round 相同,但接受字串並返回字串,以便進行更多 BC 運算。

----------------

function roundbc($x, $p) {

$x = trim($x);
$data = explode(".",$x);

if(substr($data[1],$p,1) >= "5") {

// 產生要加的字串
$i=0;
$addString = "5";
while($i < $p) {
$addString = "0" . $addString;
$i++;
}//end while.
$addString = "." . $addString;

// 將要加的字串加到原始小數上
$sum = bcadd($data[0] . "." . $data [1],$addString,$p+1);

// 將結果分割
$sumData = explode(".",$sum);

// 返回四捨五入後正確精度的數字
return $sumData[0] . "." . substr($sumData[1],0,$p);

} else {

// 不進行四捨五入,並返回原始值到所需的精度
//精確度或更低。
return $data[0] . "." . substr($data[1],0,$p);

}//結束 if/else。

}//結束 roundbc。
Diabolos at GMail dot com
20 年前
以下是一個使用基本 bcMath 算術運算計算任意精度自然指數函數的函數。

範例
計算 1.7 的指數函數到小數點後 36 位

$y = bcExp("1.7", 36);

結果
4.331733759839529271053448625299468628

將會回傳到變數 $y

注意事項
實際上,由於小的捨入誤差,最後幾位數可能不準確。如果您需要特定程度的精度,請務必計算比所需精度多 3-4 位小數。

自然指數函數的程式碼為
******************************************

函數 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;
}
To Top