浮點運算潛在的結果不一致問題
作者:云風
昨天發現了項目中的一個 bug ,是因為浮點運算的前后不一致導致的。明明是完全相同的 C 代碼,參數也嚴格一致,但是計算出了不相同的結果。我對這個現象非常感興趣,仔細研究了一下成因。
昨天阿楠發現了項目中的一個 bug ,是因為浮點運算的前后不一致導致的。明明是完全相同的 C 代碼,參數也嚴格一致,但是計算出了不相同的結果。我對這個現象非常感興趣,仔細研究了一下成因。
原始代碼比較繁雜。在弄清楚原理后,我簡化了出問題的代碼,重現了這個問題:
- static void
- foo(float x) {
- float xxx = x * 0.01f;
- printf("%d\n", (int)(x * 0.01f));
- printf("%d\n", (int)xx);
- }
- int
- main() {
- foo(2000.0f);
- return 0;
- }
使用 gcc 4.9.2 ,強制使用 x87 浮點運算編譯運行,你會發現令人詫異的結果。
- gcc a.c -mfpmath=387
- 19
- 20
前一次的輸出是 19 ,后一次是 20 。
這是為什么呢?讓我們來看看 gcc 生成的代碼,我截取了相關的段落:
- flds 16(%rbp)
- flds .LC0(%rip)
- fmulp %st, %st(1)
- fstps -4(%rbp) ; 1. x * 0.01f 結果保存到內存中的 float 變量中
- flds 16(%rbp)
- flds .LC0(%rip)
- fmulp %st, %st(1)
- fisttpl -20(%rbp) ; 2. x * 0.01f 結果直接轉換為整型
- movl -20(%rbp), %eax
- movl %eax, %edx
- leaq .LC1(%rip), %rcx
- call printf
- flds -4(%rbp) ; 3. 讀出 1. 保存的乘法結果
- fisttpl -20(%rbp)
- movl -20(%rbp), %eax
- movl %eax, %edx
- leaq .LC1(%rip), %rcx
- call printf
這里我做了三行注釋。
首先,0.01 是無法精確表示成 2 進制的,所以 * 0.01 這個操作一定會存在誤差。
兩次運算都是 x * 0.01f ,雖然按 C 語言的轉換規則,表達式中都是 float 時,按 float 精度運算。但這里 gcc 生成的代碼并沒有嚴格設置 FPU 的精度控制,在注釋 2 這個地方,乘法結果是直接從浮點寄存器轉換為整數的。而在注釋 1 這個地方,把乘法結果通過 fstps 以低精度形式保存到內存,再在注釋 3 的地方 flds 讀回。
所以在注釋 2 和注釋 3 的地方,浮點寄存器 st 內的值其實是有差別的,這導致了 fisttpl 轉換為整數后結果不同。
原文鏈接:https://blog.codingnow.com/2017/07/float_inconsistence.html#more
【本文為51CTO專欄作者“云風”的原創稿件,轉載請通過51CTO聯系原作者獲取授權】
責任編輯:xinxiaoliang
來源:
51CTO專欄


相關推薦
2025-04-03 09:51:37
2013-03-29 11:16:17
2010-06-02 10:53:28




