2015-11-02 59 views
6

我正在做一個使用bcmath的PHP計算,需要用分數指數提升e。不幸的是,bcpow()只接受整數指數。指數的精度通常比浮點數要高,因此普通的算術函數不會削減它。PHP:如何將數字提高到(微小)分數指數?

例如:

$e = exp(1); 
$pow = "0.000000000000000000108420217248550443400745280086994171142578125"; 
$result = bcpow($e, $pow); 

結果是"1"與錯誤, 「BC數學警告:非零規模指數」。

是否有另一個功能可以用來代替bcpow()

+0

請注意,$ pow = 1/9223372036854775808 –

回答

6

你最好的選擇是使用泰勒級數展開。正如你所說,PHP的bcpow僅限於提高到整數指數。

所以你可以做的是滾動你自己的BC因子函數,並使用wiki頁面實現指數函數的泰勒級數展開。

function bcfac($num) { 
    if ($num==0) return 1; 
    $result = '1'; 
    for (; $num > 0; $num--) 
     $result = bcmul($result,$num); 
    return $result; 
} 
$mysum = '0'; 
for ($i=0; $i<300; $i++) { 
    $mysum = bcadd($mysum, bcdiv(bcpow($pow,$i), bcfac($i))); 
} 
print $mysum; 

顯然,$i<300是無窮大......你可以改變它,以滿足您的性能需求的近似值。

隨着$i=20,我

1.00000000000000000010842021724855044340662275184110560868263421994092888869270293594926619547803962155136242752708629105688492780863293090291376157887898519458498571566021915144483905034693109606778068801680332504212458366799913406541920812216634834265692913062346724688397654924947370526356787052264726969653983148004800229537555582281617497990286595977830803702329470381960270717424849203303593850108090101578510305396615293917807977774686848422213799049363135722460179809890014584148659937665374616

這是令人欣慰的,因爲小的指數應該產生非常接近的東西1.0。

+0

另外,我嘗試使用'bcscale(500)'並得到相同的結果。 – Kevin

+0

完美!感謝您指點我泰勒系列。好東西。 – jchamberlain

+0

wolfram alpha同意第一~395位數http://www.wolframalpha.com/input/?i=e^%280.000000000000000000108420217248550443400745280086994171142578125%29 – chiliNUT

1

老問題,但人們可能仍然感興趣。

所以凱文用泰勒多項式得到了正確的想法,但是當你從它直接推導出你的算法時,你可能會陷入困境,主要是當你使用較大的截止值$我。

這是爲什麼: 在每一步,我的意思是每一個新的$ i,代碼調用bcfac($ i)。每次bcfac被稱爲執行$ i-1計算。而且,我一路高達299 ...這幾乎是45000操作!不是你的quick'n'easy浮點操作,但慢BC字符串操作 - 如果你設置bcscale(100)你的bcmul必須處理多達10000對字符!

此外,bcpow也隨着$ i的增加而減速。沒有bcfac那麼多,因爲它可以有效地使用類似於平方和乘法的方法,但它仍然增加了一些東西。

總體而言,所需時間隨着計算的多項式項的數量二次增長。

那麼......該怎麼辦?

這裏有一個技巧:

每當你處理多項式,尤其是泰勒多項式,使用霍納方法。

它轉換爲:exp(x)= x^0/0! + x^1/1! + x^2/2! + x^3/3! + ...

...成:EXP(X)=(((...)* X/3 + 1)* X/2 + 1)* X/1 + 1

突然之間,你根本不需要任何權力或因子。

function bc_exp($number) { 
    $result = 1; 
    for ($i=299; $i>0; $i--) 
     $result = bcadd(bcmul(bcdiv($result, $i), $number), 1); 
    return $result; 
} 

對於每一步,無論$ i是多少,這隻需要3個bc操作。 以$ i = 299的起始值(以與kevin代碼相同的精度計算exp),我們現在只需要897個bc操作,而不是45000. 即使使用30作爲截止值而不是300,我們現在只需要87個bc操作,而其他代碼仍然需要822個單獨的因子。

Horner's Method再次拯救了這一天!

一些其他的想法:

1)凱文的代碼將propably與輸入崩潰= 「0」,這取決於bcmath時如何處理錯誤,因爲代碼在第一步改掉bcpow(0,0)($ I = 0)。 2)較大的指數需要較長的多項式,因此需要更多的迭代,例如, bc_exp(300)會給出錯誤的答案,即使在$ i = 299的情況下,像bc_exp(3)這樣的東西也可以正常工作。 每學期增加x^n/n!到結果,所以這個項必須在多項式開始收斂之前變小。現在比較兩個連續的術語:

(x^(n+1)/(n+1)!)/(x^n/n!) = x/n 

每個被加數比一個較大的前由x的因子/ N(我們通過霍納方法中使用),因此,爲了對於x ^(N + 1)/ (N + 1)!得到小的x/n也必須變小,這只是當n> x時的情況。

Inconclusio:只要迭代次數小於輸入值,結果就會發散。只有當您添加步驟直到迭代次數大於輸入次數時,算法纔會開始慢慢收斂。

爲了達到可以滿足願意使用bcmath的人的結果,您的$ i需要顯着大於您的$數量。當你試圖計算像e^346674567801

這樣的解決方案是將輸入分成整數部分和小數部分。 比整數部分使用bcpow和小數部分使用bc_exp,現在從開始收斂,因爲小數部分小於1.最後,乘以結果。

e^x = e^(intpart+fracpart) = e^intpart * e^fracpart = bcpow(e,intpart) * bc_exp(fracpart) 

你甚至可以直接落實到上面的代碼:

function bc_exp2($number) { 
    $parts = explode (".", $number); 
    $fracpart = "0.".$parts[1]; 
    $result = 1; 
    for ($i=299; $i>0; $i--) 
     $result = bcadd(bcmul(bcdiv($result, $i), $fracpart), 1); 
    $result = bcmul(bcpow(exp(1), $parts[0]), $result); 
    return $result; 
} 

注意,EXP(1)賦予您propably不會滿足您的需求爲bcmath時用戶一個浮點數。根據您的bcscale設置,您可能希望使用更準確的e值。3)談論迭代次數:在大多數情況下,300將會過度殺傷,而在其他一些情況下,它可能還不夠。一個算法,需要你的bcscale和$數字,並計算所需的迭代次數會很好。 Alraedy得到了一些涉及log(n!)的想法,但沒有具體的。

4)要使用此方法的任意基地,您可以使用^ x = e ^(x * ln(a))。 您可能希望在使用bc_exp(而不是在bc_exp2中執行此操作)之前將x分成它的intpart和fracpart,以避免不必要的函數調用。

function bc_pow2($base,$exponent) { 
    $parts = explode (".", $exponent); 
    if ($parts[1] == 0){ 
     $result = bcpow($base,$parts[0]); 
    else $result = bcmul(bc_exp(bcmul(bc_ln($base), "0.".$parts[1]), bcpow($base,$parts[0]); 
    return result; 
} 

現在我們只需要編程bc_ln。我們可以使用與上面相同的策略:

取自然對數函數的泰勒多項式。 (因爲ln(0)沒有定義,所以取1作爲開發點) 使用Horner的方法來大幅度提高性能。 將結果轉換爲bc操作的循環。 當處理x> 1時,還要使用ln(x)= -ln(1/x)來保證收斂。

+0

如何估計'bcmul','bcln'和' (bc_exp(bcmul(bc_ln($ base),「0。」。$ parts [1]),bcpow($ base,$ parts [0]);''這樣我可以節省一些時間計算這些操作?現在,使用bcscale作爲100是不理想的。 –

+0

您是否意識到如何完成3)步驟?我真的對此感興趣:) –

相關問題