使用 Math.sqrt() 方法的 ME Math.pow()
開發我們的 Math.pow() 方法的第二種方法是通過使用 Math.sqrt() 方法(參見示例 7)。使用Math.sqrt() 方法要求我們對具有偶分母的冪進行估計。例如,n = 82/3 => n3 = 82 是一個棘手問題,因為我們需要立方根,而非平方根。為了調整示例中的此問題,我們我們就對兩端再求一次平方:n3 = 82 => n6 = 84。然後,我們就可以繼續進行,恰好在兩次迭代之內求出解。
當然,我們的 ME Math.pow() 方法的指數不會以分子和分母的形式開始,而是向我們傳遞一個實數。我們將此實數轉換為具有偶分母的小數,然後利用相應的平方根數來對
n 求解。在我們的 n = 82/3 示例中,我們進行如下轉換:
n = 82/3 => n = 8683/1024 => n1024 = 8683
我們選擇 1024 作為分母,因為對平方根函數迭代 10 次將得到 n 的值。特別指出,(n1024)(1/(2^10)) = n。當然,我們可能需要根據方程右側的大小來減少迭代次數。示例 7 演示了這種方法。
示例 7
- ouble pow(double x, double y)
- {
- //Convert the real power to a fractional form
- int den = 1024; //declare the denominator to be 1024
- /*ConvenIEntly 2^10=1024, so taking the square root 10
- times will yIEld our estimate for n. In our example
- n^3=8^2n^1024 = 8^683.*/
- int num = (int)(y*den); // declare numerator
- int iterations = 10; /*declare the number of square root
- iterations associated with our denominator, 1024.*/
- double n = Double.MAX_VALUE; /* we initialize our
- estimate, setting it to max*/
- while( n >= Double.MAX_VALUE && iterations > 1)
- {
- /* We try to set our estimate equal to the right
- hand side of the equation (e.g., 8^2048). If this
- number is too large, we will have to rescale. */
- n = x;
- for( int i=1; i < num; i++ )n*=x;
- /*here, we handle the condition where our starting
- point is too large*/
- if( n >= Double.MAX_VALUE )
- {
- iterations--; /*reduce the iterations by one*/
- den = (int)(den / 2); /*redefine the denominator*/
- num = (int)(y*den); //redefine the numerator
- }
- }
- /*************************************************
- ** We now have an appropriately sized right-hand-side.
- ** Starting with this estimate for n, we proceed.
- **************************************************/
- for( int i = 0; i < iterations; i++ )
- {
- n = Math.sqrt(n);
- }
- // Return our estimate
- return n;
- }
自然對數和歐拉 e 的泰勒級數逼近
對於正實數產生 Math.pow() 方法最簡便的方式之一是鏈接幾個方法,包括使用 泰勒級數。假定我們需要冪 y = xb。該式與 ln(y) = b*ln(x) 等價。進而,我們可以使用泰勒級數擴展估算 x 的自然對數,如下所示。
- ln(x) = (x-1) –(x-1)2/2 + (x-1)3/3 - (x-1)4/4….if |x-1|<=1 OR
- ln(x) = 1/(x/(x-1)) + 1/(x/(x-1))2 + 1/(x/(x-1))3… if |x|>1
由於 x 為正,因而 x 的整個域為這些方程所覆蓋。此交錯級數可以提供對底數對數的非常接近的逼近。用指數乘以此對數將得到 ln(y),方程的右側 ln(y)=b*ln(x)。現在,我們僅需求出 eln(y) 即可完成運算。我們使用另一個泰勒級數擴展來完成此過程:ex = 1 + x + x2 / 2! + x3 / 3! + … 使用這兩個公式,即可求得問題的解,如示例 8 所示。
示例 8
- double pow(double a, double b)
- {
- // true if base is greater than 1
- boolean gt1 = (Math.sqrt((a-1)*(a-1)) <= 1)? false:true;
- int oc = -1; // used to alternate math symbol (+,-)
- int iter = 20; // number of iterations
- double p, x, x2, sumX, sumY;
- // is exponent a whole number?
- if( (b-Math.floor(b)) == 0 )
- {
- // return base^exponent
- p = a;
- for( int i = 1; i < b; i++ )p *= a;
- return p;
- }
- x = (gt1)?
- (a /(a-1)): // base is greater than 1
- (a-1); // base is 1 or less
- sumX = (gt1)?
- (1/x): // base is greater than 1
- x; // base is 1 or less
- for( int i = 2; i < iter; i++ )
- {
- // find x^iteration
- p = x;
- for( int j = 1; j < i; j++)p *= x;
- double xTemp = (gt1)?
- (1/(i*p)): // base is greater than 1
- (p/i); // base is 1 or less
- sumX = (gt1)?
- (sumX+xTemp): // base is greater than 1
- (sumX+(xTemp*oc)); // base is 1 or less
- oc *= -1; // change math symbol (+,-)
- }
- x2 = b * sumX;
- sumY = 1+x2; // our estimate
- for( int i = 2; i <= iter; i++ )
- {
- // find x2^iteration
- p = x2;
- for( int j = 1; j < i; j++)p *= x2;
- // multiply iterations (ex: 3 iterations = 3*2*1)
- int yTemp = 2;
- for( int j = i; j > 2; j-- )yTemp *= j;
- // add to estimate (ex: 3rd iteration => (x2^3)/(3*2*1) )
- sumY += p/yTemp;
- }
- return sumY; // return our estimate
- }
幾乎在所有情況下,由泰勒級數逼近返回的估計比衰變算法方法更為精確,而 Math.sqrt() 也可以產生更好的結果。泰勒級數方法所使用的計算周期較少, 但在值趨於 0 時會不穩定。Math.sqrt() 結果可以提供良好的逼近,通常到第三位數字。有關使用多個任意分配的正實型變量的方法的比較,請參見表 1。我們可以看到,對於實際應用, Math.sqrt() 或泰勒級數方法對於是大多數值都比較優良。
表 1:衰變算法和平方根方法的比較
底數,指數 實際結果 泰勒級數逼近 Math.sqrt() 估計值 衰變算法估計值
8.0, 0.75 4.75682846 4.7423353221144557 4.75682846 4.751286816
8.0, 0.667 4.002774 3.9919355054959973 3.994588452 3.994453662
16.0, 8.0 4294967296 4294967296 4294967296 4294752931
32.0, 5.0 33554432 33554432 33554432 33553177.47
11.0, 3.0 1331 1331 1331 1330.967224
10.0, 10.0 10000000000 10000000000 10000000000 9999699608
77.0, 3.0 456533 456533 456533 456527.6254
5.0, 15.0 30517578125 30517578125 30517578125 30516279235
15.0, 9.0 38443359375 38443359375 38443359375 38440083836
3.0, 21.0 10460353203 10460353203 10460353203 10459907131
5.0, 0.05 1.083798387 1.0837883791740017 1.083457755 1.08205432
7.0, 0.37 2.054406 2.0529191207908064 2.050973357 2.051043668
1.5, 0.789 1.377006542 1.377006541546755 1.376496289 1.376798426
1.5, 3.789 4.647397078 4.647381683179335 4.64015972 4.644836289
0.06282, 0.325784 0.405919146 0.41327102396968585 0 0.06282
0.7261, 0.20574 0.936270645 0.9362706445348806 0.93646901 0.7261
0.903272, 0.48593 0.951767579 0.951767579257642 0.951823588 0.903272
0.821111, 0.767392 0.85963221 0.8596322100794738 0.859766145 0.821111
0.24352, .004322 0.99391353 0.9939136545397182 0.994497397 0.24352
0.000125, .99556 0.000130089 627097113.1963351 0 0.000125
編程注意事項和結論
本文已經解決了在 Java ME 中開發 Math.pow() 方法的三種途徑。雖然樸素的“強力”幾何衰變試探性方法比較不錯,而且對於小問題可能很有用處,但是 Math.sqrt() 改寫對於大多數范圍的應用可能要好一些。最佳方法可能是泰勒級數逼近。顯然,這三個示例均未包括完成該任務的特有方法(如二分法及參考資料中介紹的其他方法),並且我們希望其他方法可以提供占用較少資源的更為高效的技巧。最後需要注意的一點是:如果要求您開發此類方法,務必考慮其應用,所需的參數以及預期的結果和精度。