浮點數float累加誤差解決方式總結
首先是float累加產生誤差的原因,該部分轉自:http://blog.csdn.net/zhrh0096/article/details/38589067
1. 浮點數IEEE 754表示方法
要搞清楚float累加為什麽會產生誤差,必須先大致理解float在機器裏怎麽存儲的,具體的表示參考[1] 和 [2], 這裏只介紹一下組成
由上圖可知(摘在[2]), 浮點數由: 符號位 + 指數位 + 尾數部分, 三部分組成。由於機器中都是由二進制存儲的,那麽一個10進制的小數如何表示成二進制。例如: 8.25轉成二進制為1000.01, 這是因為 1000.01 = 1*2^3 + 0*2^2 + 0*2^1 + 0*2^0 + 0*2^-1 + 2*2^-2 = 1000.01.
(2)float的有效位數是6-7位,這是為什麽呢?因為位數部分只有23位,所以最小的精度為1*2^-23 在10^-6和10^-7之間,接近10^-7,[3]中也有解釋
那麽為什麽float累加會產生誤差呢,主要原因在於兩個浮點數累加的過程。
2. 兩個浮點數相加的過程
兩浮點數X,Y進行加減運算時,必須按以下幾步執行(可參考 [4] 中插圖):
(1)對階,使兩數的小數點位置對齊,小的階碼向大的階碼看齊。
(2)尾數求和,將對階後的兩尾數按定點加減運算規則求和(差)。
(3)規格化,為增加有效數字的位數,提高運算精度,必須將求和(差)後的尾數規格化。
(4)舍入,為提高精度,要考慮尾數右移時丟失的數值位。
(5)判斷結果,即判斷結果是否溢出。
關鍵就在與對階這一步驟,由於float的有效位數只有7位有效數字,如果一個大數和一個小數相加時,會產生很大的誤差,因為尾數得截掉好多位。例如:
123 + 0.00023456 = 1.23*10^2 + 0.000002 * 10^2 = 123.0002
那麽此時就會產生0.00003456的誤差,如果累加多次,則誤差就會進一步加大。
解決方式有幾種,但都不是最佳方式,參考:http://bbs.csdn.net/topics/390549664
3.解決方法
方法一
Kahan summation算法
https://en.wikipedia.org/wiki/Kahan_summation_algorithm
function KahanSum(input) var sum = 0.0 var c = 0.0 // A running compensation for lost low-order bits. for i = 1 to input.length do var y = input[i] - c // So far, so good: c is zero. var t = sum + y // Alas, sum is big, y small, so low-order digits of y are lost. c = (t - sum) - y // (t - sum) cancels the high-order part of y; subtracting y recovers negative (low part of y) sum = t // Algebraically, c should always be zero. Beware overly-aggressive optimizing compilers! next i // Next time around, the lost low part will be added to y in a fresh attempt. return sum
偽代碼如上
解決方法就是把多余的誤差部分算出來(c),再在下一次循環減去這個誤差
方法二
int main() { float f = 0.1; float sum = 0; sum+=add(f,4000000); cout<<sum<<endl; return 0; } float add(float f,int count) { if(count==1) return f; else return add(f,count/2)+add(f,count-count/2); }
二分法遞歸計算加法,這樣會沒有誤差,但是函數調用消耗大(尤其是多次)
方法三
使用double,精度更高,但是本來是沒有必要用這麽高精度的
方法四
ieee浮點數,為了規格化,精度每超過2的整數次冪,精度要下降一位,
你的f是0.1,float位數是23,當sum足夠大的時候,會出現 sum+f==sum 的情況,這個是ieee標準,
和C++沒關系,事實上編譯器應該已經做了浮點精度調整了,你這結果誤差算小的了.
避免這種誤差的方法就是浮點數,永遠不要讓一個很大的數去加上一個很小的數.不知你這段代碼的目的是
什麽,但如果你改成這樣,誤差會小很多:
float f = 0.1; float sum = 0; for( i=0; i<100; i++) { int sumEachBig=0; for(....k<400....) { int sumEachSmall=0; for(....j<100.....) sumEachSmall += f; sumEachBig+=sumEachSmall; } sum += sumEachBig; }
來自manzi11的回答。多次用多次循環,小循環的計算結果加上大循環的運算結果
by wolf96 2017/7/10
浮點數float累加誤差解決方式總結