題目取自:《數據結構與算法分析:C語言描述_原書第二版》——Mark Allen Weiss
練習1.3 如題。
補充說明:假設僅有的I/O例程只處理單個數字並將其輸出到終端,我們將這個例程命名為PrintDigit;例如"PrintDigit(4)"
將輸出一個"4"到終端。
思路:根據先簡后繁的原則,程序各版本完成的功能依次為:處理正整數—>處理所有整數—>處理double—>double舍入。
版本一:
// 正整數版(更大的范圍可以使用long long int) #include<stdio.h> void PrintOut(int number); void PrintDigit(int number); int main(void) { int n = 123; PrintOut(n); return 0; } void PrintOut(int number) { int value = number / 10; if(value != 0) // 考慮用while會出現什么情況,如果數字不是個位數,那么程序死循環輸出首位數字。 PrintOut(value); PrintDigit(number % 10); } void PrintDigit(int number) // 對處理單個數字的I/O例程進行模擬 { printf("%d", number); }
版本二:
// 強化版:所有整數 #include<stdio.h> void PrintOut(int number); void PrintDigit(int number); void PreDispose(int number); // 對傳入的參數做一些預處理工作,然后調用PrintOut函數 int main(void) { int n = -45689; PreDispose(n); return 0; } void PreDispose(int number) { if(number < 0) { putchar('-'); number = -number; } PrintOut(number); } void PrintOut(int number) { int value = number / 10; if(value != 0) PrintOut(value); PrintDigit(number % 10); } void PrintDigit(int number) { printf("%d", number); }
講述版本三之前先來看一下double類型在內存中的存儲情況,在code::blocks中定義如下變量:
設置斷點,調試,a、b、c初始化之前的值如圖一,賦值后的值如圖二
圖一 圖二
不難看出,double類型在內存中存儲是有誤差的。比如我們定義的c = 9.1,內存中實際值為9.099999999...6。其實這個值也是四舍五入得來的,那么如何看到它在內存中的全貌呢,請看版本三:
版本三:
// 強化版二:double類型 #include<stdio.h> #include<math.h> void PrintOut(int number); void PrintDigit(int digit); void PreDispose(double number); int main(void) { double n = 9.1; PreDispose(n); return 0; } void PreDispose(double number) { double ip; // 函數modf把傳入的第一個參數分為整數和小數兩部分,整數部分保存在第二個參數中 // 兩部分的正負號均勻x相同,該函數返回小數部分 double fraction = modf(number, &ip); // 一個更加簡便的分離小數位與整數位的方法如下: // double ip, fraction; // fraction = number - (int)number; // ip = (int)number; if(ip < 0) { putchar('-'); ip = -ip; fraction = -fraction; } PrintOut(ip); putchar('.'); // 對小數部分逐位輸出,理論上可以輸出到小數點后任意多的數位,就這幾行代碼還耗了不少腦細胞呢Orz int N = 70; // 希望輸出到小數點多少位,就設定N為多少(想不到更好的解釋了:-) while(N--) { fraction *= 10; PrintOut((int)fraction%10); // 因為傳入的參數是單個數字,所以這里也可以直接調用PrintDigit函數 fraction = fraction - (int)fraction; // 防止fraction數據過大,導致整型溢出 } } void PrintOut(int number) { int value = number / 10; if(value != 0) PrintOut(value); PrintDigit(number % 10); } void PrintDigit(int digit) { printf("%d", digit); } //輸出結果:9.0999999999999996447286321199499070644378662109375000000000000000000000
對b=1.1而言,輸出結果為:1.1000000000000000888178419700125232338905334472656250000000000000000000,對比圖二不難得出結論:code::blocks中所示的數值就是原double值四舍五入並且精確到小數點后16位得到的。
但是,存在一個問題,比如拿c=9.1來說事,我們令版本三中程序中的變量N的值為1,則輸出結果為9.0。所以這個程序的一個問題就是:沒有考慮舍入誤差。那么如何處理舍入誤差呢,還好有Weiss 提供的core->)。
版本四:終極進化版
//下面是我根據作者提供的核心代碼補全后的版本,考慮了舍入誤差(四舍五入) #include<stdio.h> int IntPart(double N); // 得到N的整數部分 double DecPart(double N); // 得到N的小數部分 void PrintReal(double N, int DecPlaces); // 該函數打印double值,其中第二個參數為精確到小數點后的位數 void PrintFractionPart(double FractionPart, int DecPlaces); // 打印小數部分 double RoundUp( double N, int DecPlaces ); // 實現四舍五入的函數 void PrintOut(int number); void PrintDigit(int number); int main(void) { double value = -9.1; PrintReal(value, 1); return 0; } double RoundUp( double N, int DecPlaces ) // 竊以為該函數為整個程序的畫龍點睛之筆。 { int i; double AmountToAdd = 0.5; for( i = 0; i < DecPlaces; i++ ) AmountToAdd /= 10; return N + AmountToAdd; } void PrintReal(double N, int DecPlaces) { int IntegerPart; double FractionPart; if( N < 0 ) { putchar('-'); N = -N; } N = RoundUp(N, DecPlaces); IntegerPart = IntPart( N ); FractionPart = DecPart( N ); PrintOut(IntegerPart); // 假設錯誤檢查已經完成,即輸入是常規的文本 if(DecPlaces > 0) putchar('.'); PrintFractionPart(FractionPart, DecPlaces); } void PrintFractionPart(double FractionPart, int DecPlaces) // 程序三中輸出小數部分的實現思路與之類似,不過其提供了對外接口——函數外部可以設定要輸出的小數位數 { int i, Adigit; for( i = 0; i < DecPlaces; i++ ) { FractionPart *= 10; Adigit = IntPart(FractionPart); PrintDigit(Adigit); FractionPart = DecPart(FractionPart); } } int IntPart(double N) { return (int)N; } double DecPart(double N) { return N - IntPart(N); } void PrintOut(int number) { int value = number / 10; if(value != 0) PrintOut(value); PrintDigit(number % 10 ); } void PrintDigit(int digit) { printf("%d", digit); } //輸出結果:9.1
可以看到,該程序不僅解決了版本三中遺留的小數點舍入問題,而且通過設定PrintReal函數的第二個參數的值為70可以得到和版本三中相同的結果。End。
—————————————————————————^_^我是分隔線^_^—————————————————————————
All Rights Reserved.
Author:海峰:)
Copyright © xp_jiang.