使用 Java 開發移動設備應用程序時,可能需要用到特定 Java VM 所沒有的數學方法。本文將專門解決 Java ME 沒有“冪”方法 Math.pow() 的問題。我們將演示使用三種不同的方法開發同一個 ME 應用程序,並從中選出最佳的編程解決方案。
要討論此問題,我們先考察整數和分數冪參數,將我們的分析限於正實數。我們將演示求整數問題和小數問題的解集相對而言比較容易(而 不考慮指數的符號)。在大多數情況下,我們將使用示例問題 n = 82/3,其中我們會求出 n 的良好估計或實際解。如果初始指數事先不可用 ,則此問題的其他解(包括牛頓法和割線法)不易編程。雖然二分法是可行的解決方案,但我們將關注傳統上不為人所探究的三個方法。第一 個是簡單的(不過有時效率低下)幾何衰變算法;而第二個方法將利用 Math.sqrt() 方法並保證在不超過 11 次迭代中收斂到一個近似解。第 三個方法將使用泰勒級數逼近法求對數並對泰勒級數進行歐拉轉換。
產生整數解的 ME Math.pow() 方法
傳統上,Java Math.pow() 方法包含兩個參數。這兩個參數包括底數和指數。我們假定(最初)這兩個參數均為整數,然後求出 ME 中與 Java 方法使用相同參數的 Math.pow() 方法的可編程解。此處,可編程解相當簡單,如示例 1 所示。在本例中,我們僅運行以指數值為指標 的倍乘循環。
示例 1
int pow( int x, int y) /*we define the power method with
base x and power y (i.e., x^y)*/
{
int z = x;
for( int i = 1; i < y; i++ )z *= x;
return
}
當然,有人可能會發現需要求出非整數冪的值。正實數的簡單解(無需訪問 Math.pow() 方法)可能涉及使用 Math.log()。例如,請考慮 82/3 的情況。利用 2/3*ln(8) = 1.386294361 中自然對數的結果。要得到最終解,需要利用指數 1.386294361(特別指出 e1.386294361 = 4 )。在這種情況下,可能不需要使用冪函數。遺憾的是,Java ME 也不支持 Math.log() 方法。沒有 Math.pow() 或 Math.log() 方法時,我 們會考慮使用樸素的“強力”試探性方法,應用 Math.sqrt() 方法以及自然對數(和歐拉 e)的泰勒級數逼近來求得 Java ME 問題的解。
使用幾何衰變算法作為強力解的 ME Math.pow()
Java ME 的早期實現包括浮點主數據類型 float 和 double。最近,已添加了這些類型。現在我們將 Math.pow() 聲明中的整型參數替換為 double 數據類型。
可能需要在 Java ME Math.pow() 冪方法中使用小數指數。我們提供的生成 Math.pow() 的第一種方法是使用幾何衰變算法的樸素的“強力 ”試探性方法。簡單而言,衰變算法以一個大於已知解的值開始,然後應用某個方法來衰變該值,直到該值非常逼近該解(有關簡單線性衰變 算法的演示,請參見示例 2)。在我們的例子中,將進一步演示向上述解收斂的幾何形式。
示例 2
/* This example illustrates a simplistic decay algorithm that we will assume
converges to our desired solution (a positive integer) */
int n; // assume that n is the solution to the number we are trying to find
int varX = 1000; //assume that we know the solution is less than or equal to 1000
while( varX > 0 )
{
varX -= 1; // decrement by 1
if( varX == n)return varX;
}
在示例 2 中,我們從 1000 開始遞減,直到找到預期的數字,假定預期數字是一個正整數。這種類型的算法構成了強力試探性方法的基礎 。
使用類似的方法,我們可在遇到小數時應用此算法。假定我們需要求出 n 的值,其中 n = 82/3。要使用衰變算法,我們必須首先找到一個 合適的起點,該點要等於或大於解本身。這對於帶有正指數的正實數很容易做到。對於我們的示例,要對此解進行編程,對方法兩邊求立方, 得到 n3=82 。當然,此方程與 n3=64 等效。之後,我們的起始值將變為 64,我們知道 n 必須小於 64(因為 n3 = 64)。注意,如果限於正 實數,則此推導方法同樣適用於任何正指數值。現在,我們可能需要設計一個循環來產生 n 的“充分接近”預期數字的解。我們再來看示例 3 ,它適合於所有正底數和正指數。
示例 3
double pow( double x, double y ) //we define our new power method for fractions
{
int den = 1000; // specify arbitrary denominator
int num = (int)(y*den); // find numerator
int s = (num/den)+1;
/***********************************************************************
** Variable 's' provides the power for which we multiply the base to find
** our starting search value. For example, if we seek a solution for
** n = 8^(2/3), then we will use 8^2 or 64 as our starting value (which is
** generated in our next section of code.) Why? The solution for our
** problem (given that the base is positive) will always be less than or
** equal to the base times the numerator power.
************************************************************************/
/***********************************************************************
** Because we set the denominator to an arbitrary high value,
** we must attempt to reduce the fraction. In the example below,
** we find the highest allowable fraction that we can use without
** exceeding the limitation of our primitive data types.
************************************************************************/
double z = Double.MAX_VALUE;
while( z >= Double.MAX_VALUE )
{
den -=1; // decrement denominator
num = (int)(y*den); // find numerator
s = (num/den)+1; // adjust starting value
// find value of our base number to the power of numerator
z = x;
for( int i = 1; i < num; i++ )z *= x;
}
/***********************************************************************
** Now we are going to implement the decay algorithm to find
** the value of 'n'.
************************************************************************/
/***********************************************************************
** We now find 'n' to the power of 's'. We will then decrement 'n',
** finding the value of 'n' to the power of the denominator. This
** value, variable 'a', will be compared to 'z'. If the 'a' is nearly
** equal to 'z', then we will return 'n', our desired result.
************************************************************************/
double n = x; // We define 'n' as our return value (estimate) for 'x'.
// find 'n' to the power of 's'.
for( int i = 1; i < s; i++)n *= x;
// Begin decay loop
while( n > 0 )
{
double a = n; //proxy for n
// find 'a', the value of 'n' to the power of denominator
for( int i = 1; i < den; i++ )a *= n;
// compare 'a' to 'z'. Is the value within the hundred-thousandth?
// if so, return 'n'.
double check1 = a-z;
double check2 = z-a;
if( check1 < .00001|| check2 > .00001 ) return n;
n *= .999;// We arbitrarily use a decay of .1% per iteration
}
// value could not be found, return -1.
return -1.0;
}
本示例演示了衰變算法的使用方法。您會注意到,n 的值(解的估計值)將按 1% 強制遞減。您可能需要根據編程精度要求來改變此值。也 可能考慮包括編程邏輯,該邏輯用於將前一迭代解與當前迭代進行比較,然後,如果有改善繼續進行迭代,但是,如果解已回歸,則返回前一 個值。
這裡講述的解只處理正指數。如果值為負會出現什麼情況呢?下面我們將解決這種意外情況。
處理負指數
要再增加一層復雜度,假定正在向 Math.pow() 方法傳遞負指數。在這種情況下,指數為負,一種簡單的解決方案是將底數轉換為小數,使 指數為正。例如,8-2 可轉換為 (1/8)2。我們以可編程的方式用底數 x 來除 1,用 -1 來乘 y(參見示例 6)。
示例 6
if( y < 0 )
{
x = (1/x); // convert base number to fraction
y *= -1; // make exponent positive
}
現在,我們已經討論了用於在 Java ME 中估計冪函數的“強力”幾何衰變算法。讀者會注意到,對於較大的底數和指數分子組合,樸素的 “強力”試探性方法有性能問題。請考慮示例 85/2。使用此算法的起始值將為 85 = 32,768。如果使用 1% 的衰變,則要求全部 5196 次迭代 都求該解,這樣幾乎達不到最優。謹記此事實並且不提供改善的試探性搜索算法,我們轉到二次逼近,這會提供更多合理的迭代要求。
使用 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
double 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^2 n^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() 改寫對於大多數范圍的應用可能要好一些。最佳方法可能是泰勒級數逼近。顯然,這三個示例均未包括完成該 任務的特有方法(如二分法及參考資料中介紹的其他方法),並且我們希望其他方法可以提供占用較少資源的更為高效的技巧。最後需要注意 的一點是:如果要求您開發此類方法,務必考慮其應用,所需的參數以及預期的結果和精度。