- 編寫高質量代碼:改善C程序代碼的125個建議
- 馬偉 著
- 306字
- 2019-01-01 01:33:10
建議3-2:避免使用浮點數進行精確計算
前面已經闡述過,由于計算機的字長有限,浮點數能夠精確表示的數是有限的。因此在進行數值計算時,有可能要對計算得到的中間結果數據使用相關的舍入規則來取近似值,而這就會導致計算過程產生誤差。示例如代碼清單1-18所示。
代碼清單1-18 浮點數計算示例
#include <stdio.h> #include <limits.h> float average(float *arr,size_t size); enum { array_size = 10 }; int main(void) { float arr[array_size]; size_t i=0; for (i = 0; i < array_size; i++) { arr[i] = 5.1; } printf("total / size = %f\n", average(arr,array_size)); return 0; } float average(float *arr,size_t size) { float total = 0.0; size_t i=0; if (size > 0&&size<=SIZE_MAX) { for (i = 0; i < size; i++) { total += arr[i]; printf("arr[%d] = %f , total = %f\n", i, arr[i], total); } return total / size; } else { return 0.0; } }
在代碼清單1-18中,程序取了10個相同的浮點數(5.1)來計算其平均值。理論上,由于這10個浮點數是相同的,都是5.1,因此所計算得出的平均值也應該是5.1。但實際計算結果并非如此,如圖1-28所示。

圖1-28 代碼清單1-18在GCC中的運行結果
是什么原因導致產生圖1-28所示的結果呢?
如果你詳細看完建議3-1中的內容,相信找出答案并不難。其實,之所以得到這樣的運行結果,歸根結底就是因為5.1無法精確地表達為相應的浮點數,而只能保存為經過舍入計算的近似值。這個近似值再進行累加運算之后,自然無法產生精確的結果,最后的平均值結果也就自然而然地產生了誤差。
為了讓大家能夠更加清楚地看見其結果的舍入情況,筆者把代碼清單1-18里的浮點數保留到小數點22位,如下所示:
printf("total / size = %.22f\n", average(arr,array_size));… printf("arr[%d] = %.22f , total = %.22f\n", i, arr[i], total);
運行調整后的代碼清單1-18,你就可以清楚地看見其舍入結果,如圖1-29所示。

圖1-29 調整后的代碼清單1-18在GCC中的運行結果(浮點數保留22位小數)
當然,這種結果并不是唯一的,如果編譯器不同,舍入的值也有可能會不同。例如,在VC++2010中運行調整后的代碼清單1-18的結果如圖1-30所示。

圖1-30 調整后的代碼清單1-18在VC++2010中的運行結果(浮點數保留22位小數)
由此可以看出,浮點數據會因為其舍入誤差而導致運算結果產生誤差,從而失去精確性。因此,我們應該盡量避免使用浮點數進行精確計算。
當然,對于代碼清單1-18,我們還可以通過整數代替浮點數的方法來執行內部加法,以保證其結果的精確性。而浮點數只用在打印結果及執行除法計算算術平均值的時候,如代碼清單1-19所示。
代碼清單1-19 整數代替浮點數示例
#include <stdio.h> #include <limits.h> float average(int *arr,size_t size); enum { array_size = 10 }; int main(void) { int arr[array_size]; size_t i=0; for (i = 0; i < array_size; i++) { arr[i] = 510; } printf("total / size = %f\n", average(arr,array_size)); return 0; } float average(int *arr,size_t size) { int total = 0; size_t i=0; if (size > 0&&size<=SIZE_MAX) { for (i = 0; i < size; i++) { total += arr[i]; printf("arr[%d] = %f , total = %f\n", i, (float)arr[i]/100, (float)total/100); } return (float)(total/ size)/100; } else { return 0.0; } }
上面代碼的運行結果如圖1-31所示。雖然采用這種方法就可以避免浮點數的舍入誤差帶來的不精確性,但在一般情況下不建議這樣做。

圖1-31 代碼清單1-19在GCC中的運行結果
- 基于粒計算模型的圖像處理
- Java程序設計實戰教程
- Learn to Create WordPress Themes by Building 5 Projects
- Android開發精要
- 架構不再難(全5冊)
- 響應式架構:消息模式Actor實現與Scala、Akka應用集成
- 現代C++編程實戰:132個核心技巧示例(原書第2版)
- Statistical Application Development with R and Python(Second Edition)
- Android群英傳
- 實戰Java高并發程序設計(第2版)
- Advanced UFT 12 for Test Engineers Cookbook
- ABAQUS6.14中文版有限元分析與實例詳解
- 實驗編程:PsychoPy從入門到精通
- 從零開始學Unity游戲開發:場景+角色+腳本+交互+體驗+效果+發布
- Java 9:Building Robust Modular Applications