使用 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 次迭代都求該解,這樣幾乎達不到最優。謹記此事實並且不提供改善的試探性搜索算法,我們轉到二次逼近,這會提供更多合理的迭代要求。