疑難雜症小記 浮點運算的精度問題

2021-08-17 20:47:32 字數 2537 閱讀 1531

先上一段c#**,有興趣的朋友可以人腦執行一遍~

int num = 160;

float test = 1.3f;

float result = num * test;

int result_1 = (int)result;

int result_2 = (int)(num * test);

int result_3 = (int)(float)(num * test);

console.writeline(" ", result, result_1, result_2, result_3);

**看上去非常簡單,執行結果卻出人意料 : 208 208207208 !

result_2 = (int)(num * test) = (int)(160 * 1.3) = 208, 為什麼程式會輸出 207, wtf ?

更加詭異的是, result_3 的計算方式與 result_2 一模一樣,只是中間多了一步float的轉換,然後計算結果便正確了, wtf ??

so上請教了一下,自己也去了解了一些相關知識,大抵弄清楚了原因,這裡一步步的講下,算作筆記了~

二進位制小數無法精確表達十進位制小數

拿上面的 test 為例,雖然**中我們將他初始化為了十進位制小數 1.3f, 但實際上,由於二進位制小數無法精確表達十進位制小數 1.3f, 所以浮點數 test 實際表達的是 1.3 的近似值.

(細節來講, test 的二進位制表示為0 01111111 01001100110011001100110,實際表示的數值為1.29999995231628)

浮點數乘法可能是以高精度執行的

考慮上面的** float result = num * test, 實際的運算過程可能是在 double 精度下(或者更高精度下)進行的,翻譯成**,大概是這個樣子:

float result = (float)((double)num * (double)test);
首先我們來計算 (double)num * (double)test = 160 * 1.29999995231628 = 207.9999923706048, 其二進位制表示為0 10000000110 1001111111111111111111110000000000000000

接著我們要將計算結果轉為 float, 由於 float 精度有限(23位精度),但是計算結果需要更高精度(24位精度),所以轉化使結果被近似到了(0舍1入?)0 10000110 10100000000000000000000(即208)

浮點數轉整數採用的是截斷方式

承接上面的說明, 我們計算出了高精度下的乘法數值 (double)num * (double)test = 160 * 1.29999995231628 = 207.9999923706048 (二進位制表示為0 10000000110 1001111111111111111111110000000000000000)

不同於浮點數轉化,整數轉換採用的是截斷方式: 首先將上述結果的二進位制轉換為定點二進位制小數11001111.11111111111111111, 然後直接截斷小數部分,得到:11001111(即207)

綜合上面所述的三點原因,我們就可以解釋上面的遺留問題了:

result_2 = (int)(num * test) = (int)(160 * 1.3) = 208, 為什麼程式會輸出 207 ?

因為 1.3 的實際二進位制表示為 1.29999995231628,與 160 相乘後結果為 207.9999923706048,轉換為整數時進行了截斷,所以 result_2 的結果為 207

result_3 的計算方式與 result_2 一模一樣,只是中間多了一步float的轉換,為什麼計算結果便正確了?

因為 1.3 的實際二進位制表示為 1.29999995231628,與 160 相乘後結果為 207.9999923706048,轉換為浮點數是採用了近似方式,得到了208,之後再轉化為整數自然仍然是 208

如果你大抵明白了上面的講述,試一試這道練習題吧,想想最後的輸出會是什麼:

int num = 160;

double test = 1.3f;

double result = num * test;

int result_1 = (int)result;

int result_2 = (int)(num * test);

int result_3 = (int)(double)(num * test);

console.writeline(" ", result, result_1, result_2, result_3);

更多知識:

1.so有個關於該問題的詳細回答

2.浮點數二進位制表達的更多知識

疑難雜症小記 POCO的Android編譯

想編譯下poco的android版本,發現官方只支援linux或者mac os x,簡單嘗試了下cygwin,發現ndk的windows版本工具鏈因為使用windows風格的檔案路徑,所以基本不能與cygwin配合使用,遂而索性裝了乙個ubuntu,前期準備工作完畢後,編譯卻總是失敗 arm lin...

mybatis foreach的疑難雜症

背景 今天寫 的時候,寫了一段sql,如下所示 select product name,product type,subscription id,product period,subscription begin,interest begin,interest end,subscription st...

快速排序的疑難雜症

最近要準備藍橋杯和學校的程式設計的比賽,所以拿起了多少年沒看過的演算法了,一看發現自己的知識網全是漏洞了 畢竟兩年了 所以開始重新複習和學習,希望自己能重新拾起自己原來的本領並甚至更進一步。重新摸起演算法,就不得不說不說起演算法的入門虎之一的快速排序,當年也是被折磨的死去活來的,但是如今看看網路上的...