1. 程式人生 > >浮點數float累加誤差解決方式總結

浮點數float累加誤差解決方式總結

turn ble should 也有 方法 git 如果 precision https

首先是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累加誤差解決方式總結