劍指Offer[16]:數值的整數次方

題目

   實現函數 double Power(double base, int exponent), 求base的exponent次方,不使用庫函數,同時不需要考慮大數問題。

分析

  書中作者已經分析的很清楚了,主要有兩個方面去考慮:

  1. 指數(exponent)爲0和負數的情況;
  2. 求解的效率問題。

  第一個方面是容易被忽略的,還有需要考慮底數爲0時指數小於等於0是沒有意義的;第二個方面就是通過數學分析來提高算法效率,具體來說就是,用以下思想來求 aann 次方:

an={an/2an/2,n is an even numbera(n1)/2a(n1)/2a,n is an odd numbera^{n}=\left\{\begin{matrix} a^{n/2}\cdot a^{n/2}, & n\text{ is an even number}\\ a^{(n-1)/2}\cdot a^{(n-1)/2} \cdot a, & n\text{ is an odd number} \end{matrix}\right.

例如,求 xx 的32次方,只需要進行五次乘法:先求 xx 的平方,再在此基礎上求 xx 的4次方,接下來在 xx 的4次方的基礎上求 xx 的8次方,在 xx 的8次方的基礎上求 xx 的16次方,最後由 xx 的16次方得到 xx 的32次方。

  除了這個問題之外,還需要考慮一種情況就是**指數爲int型最大值(23112^{31}-1)和最小值(231-2^{31})**的情況。但是書中爲什麼沒有分析這種情況呢?下面代碼實現部分具體來看。

代碼

c++實現

  《劍指Offer》一書是用C++語言實現的,這裏首先給出c++版本的完整實現:

#include <stdio.h>
#include <iostream>
using namespace std;
bool g_InvalidInput = false;  // 用來標記非法輸入,例如底數爲0,指數小於等於零的情況

/*
double 是雙精度類型的變量,不能直接用 == 判斷兩個double類型的數是否相等
因此設置精度爲1e-16,兩個double數差的絕對值小於1e-16就認爲他們相等
*/
bool equal(double x1, double x2){
    return ((x1 - x2) < 1e-16) && ((x1 - x2) > -1e-16);
}

double PowerWithUnsignedExponent(double base, unsigned int exponent){
    if(exponent == 0)   return 1;
    if(exponent == 1)   return base;

    double result = PowerWithUnsignedExponent(base, exponent >> 1); // 使用移位運算代替除以2
    result *= result;
    if(exponent & 0x1 == 1){  // 通過位運算判斷指數的奇偶性
        result *= base;
    }
    return result;
}

double Power(double base, int exponent){
    g_InvalidInput = false;
    if(equal(base, 0.0) && exponent <= 0){  // 非法輸入的情況
        g_InvalidInput  = true;
        return 0;
    }

    // 通過unsigned int 將指數轉化爲其絕對值,這樣正指數和負指數的情況就可以一起計算
    unsigned int absExponent = (unsigned int)(exponent);
    if(exponent < 0){
        absExponent = (unsigned int)(-exponent);
    }
    double result = PowerWithUnsignedExponent(base, absExponent);
    // 對於負指數,最終結果應當是按其絕對值計算得到結果的倒數
    if(exponent < 0){
        return 1.0 / result;
    }
    return result;
}


int main(){
    int exponent = -2;
    double base = 2.0;
    cout << Power(base, exponent) << endl;
    return 0;
}

這裏有以下幾個點需要注意:

  1. 首先,通過unsigned int將指數轉化爲其絕對值,正數的絕對值爲(unsigned int)(exponent),負數的絕對值爲(unsigned int)(-exponent),並且由於unsigned int類型的數據範圍爲04294967295($2^{32}-1$),`int`型數據範圍爲-21474836482147483647(231,2311-2^{31},2^{31}-1),數據轉換也很好的處理了邊界指數的問題;
  2. 不能直接用 == 判斷兩個double類型數據相等,因爲計算機在處理浮點數的時候會有精度誤差;
  3. 由於位運算的效率非常高,因此程序中通過右移運算符代替除以2的運算,通過和 0x1做與運算判斷指數的奇偶性。

java實現

  c++學的太爛了,所以平時java用的多一點,於是看完書中講解就想着用java實現一下,然後自然而然寫出了以下代碼:

public class FastPower {

    public static void main(String[] args) {
        double base = 2;
        int exponent = -2;
        double result = Power(base, exponent);
        System.out.print(result);
    }

    public static double Power(double base, int exponent){
        if((Math.abs(base - 0.0) < 1e-16) && (exponent <= 0)){
            return 0;
        }
        
        // 指數爲負數的時候,將指數變爲其相反數,底數變爲其倒數
        if(exponent < 0){
            exponent = -exponent;
            base = 1 / base;
        }
        double result = myPow(base, exponent);
        return result;
    }

    private static double myPow(double base, int exponent) {
        if(exponent == 0)  return 1;   // 遞歸的返回條件
        if(exponent == 1)  return base;
        double result = myPow(base, exponent >> 1);
        result *= result;
        if((exponent & 0x1) == 1){
            result *= base;
        }
        return result;
    }
}

乍一看好像沒問題,運行一般的值也正常,直到將指數設置爲231-2^{31},發現運行報錯,如下圖

棧溢出???在遞歸的時候,如果棧溢出首先要考慮的就是遞歸是不是沒有返回,這種情況下會一直遞歸下去造成棧溢出。但是程序裏爲什麼會出現棧溢出呢?最後調試發現。。。。。分明對exponent取了相反數,爲什麼相反數還是本身?

  到這裏就發現問題了,對於int型的值231-2^{31},在取相反數後會超出int型的範圍,因此又成了自己,然後在移位過程中有符號的int型負數,最高位一直是1,因此永遠無法滿足遞歸的返回條件。這樣就理解爲什麼c++程序中使用了unsigned int,但是Java沒有unsigned int類型,因此用移位代替除以2的操作在指數爲231-2^{31}是不可行的,需要特別注意。

因此,需要將程序中的exponent >> 1改爲exponent / 2,此外,將移位操作符換爲除以2之後,if(exponent < 0)中的執行語句也可以去掉exponent = -exponent;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章