題目
實現函數 double Power(double base, int exponent)
, 求base的exponent次方,不使用庫函數,同時不需要考慮大數問題。
分析
書中作者已經分析的很清楚了,主要有兩個方面去考慮:
- 指數(exponent)爲0和負數的情況;
- 求解的效率問題。
第一個方面是容易被忽略的,還有需要考慮底數爲0時指數小於等於0是沒有意義的;第二個方面就是通過數學分析來提高算法效率,具體來說就是,用以下思想來求 的 次方:
例如,求 的32次方,只需要進行五次乘法:先求 的平方,再在此基礎上求 的4次方,接下來在 的4次方的基礎上求 的8次方,在 的8次方的基礎上求 的16次方,最後由 的16次方得到 的32次方。
除了這個問題之外,還需要考慮一種情況就是**指數爲int型最大值()和最小值()**的情況。但是書中爲什麼沒有分析這種情況呢?下面代碼實現部分具體來看。
代碼
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;
}
這裏有以下幾個點需要注意:
- 首先,通過
unsigned int
將指數轉化爲其絕對值,正數的絕對值爲(unsigned int)(exponent)
,負數的絕對值爲(unsigned int)(-exponent)
,並且由於unsigned int
類型的數據範圍爲04294967295($2^{32}-1$),`int`型數據範圍爲-21474836482147483647(),數據轉換也很好的處理了邊界指數的問題; - 不能直接用
==
判斷兩個double
類型數據相等,因爲計算機在處理浮點數的時候會有精度誤差; - 由於位運算的效率非常高,因此程序中通過右移運算符代替除以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;
}
}
乍一看好像沒問題,運行一般的值也正常,直到將指數設置爲,發現運行報錯,如下圖
棧溢出???在遞歸的時候,如果棧溢出首先要考慮的就是遞歸是不是沒有返回,這種情況下會一直遞歸下去造成棧溢出。但是程序裏爲什麼會出現棧溢出呢?最後調試發現。。。。。分明對exponent取了相反數,爲什麼相反數還是本身?
到這裏就發現問題了,對於int型的值,在取相反數後會超出int型的範圍,因此又成了自己,然後在移位過程中有符號的int
型負數,最高位一直是1,因此永遠無法滿足遞歸的返回條件。這樣就理解爲什麼c++程序中使用了unsigned int
,但是Java沒有unsigned int
類型,因此用移位代替除以2的操作在指數爲是不可行的,需要特別注意。
因此,需要將程序中的exponent >> 1
改爲exponent / 2
,此外,將移位操作符換爲除以2之後,if(exponent < 0)
中的執行語句也可以去掉exponent = -exponent;
。