1. 程式人生 > >Float型別出現舍入誤差的原因(round 取位)

Float型別出現舍入誤差的原因(round 取位)

在練習時,輸入如下程式碼:

結果不準確。

原因:https://blog.csdn.net/bitcarmanlee/article/details/51179572

浮點數一個普遍的問題就是在計算機的世界中,浮點數並不能準確地表示十進位制。並且,即便是最簡單的數學運算,也會帶來不可控制的後果。因為,在計算機的世界中只認識0與1

python中的decimal模組可以解決上面的煩惱 

decimal模組中,可以通過整數,字串或原則構建decimal.Decimal物件。如果是浮點數,特別注意因為浮點數本身存在誤差,需要先將浮點數轉化為字串。

 

當然精度提升的同時,肯定帶來的是效能的損失。在對資料要求特別精確的場合(例如財務結算),這些效能的損失是值得的。但是如果是大規模的科學計算,就需要考慮執行效率了。畢竟原生的float比Decimal物件肯定是要快很多的。

 

使用上述辦法解決後:

 

 

知識點總結:

1. decimal模組:

Python提供了decimal模組用於十進位制數學計算,它具有以下特點:

  1. 提供十進位制資料型別,並且儲存為十進位制數序列;
  2. 有界精度:用於儲存數字的位數是固定的,可以通過decimal.getcontext().prec=x 來設定,不同的數字可以有不同的精度
  3. 浮點:十進位制小數點的位置不固定(但位數是固定的)

首先是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

 

 

  1.   function KahanSum(input)
  2.   var sum = 0.0
  3.   var c = 0.0 // A running compensation for lost low-order bits.
  4.   for i = 1 to input.length do
  5.   var y = input[i] - c // So far, so good: c is zero.
  6.   var t = sum + y // Alas, sum is big, y small, so low-order digits of y are lost.
  7.   c = (t - sum) - y // (t - sum) cancels the high-order part of y; subtracting y recovers negative (low part of y)
  8.   sum = t // Algebraically, c should always be zero. Beware overly-aggressive optimizing compilers!
  9.   next i // Next time around, the lost low part will be added to y in a fresh attempt.
  10.   return sum
  • 1


虛擬碼如上

 

 

解決方法就是把多餘的誤差部分算出來(c),再在下一次迴圈減去這個誤差

方法二

 

 

  1.   int main()
  2.   {
  3.   float f = 0.1;
  4.   float sum = 0;
  5.   sum+=add(f, 4000000);
  6.   cout<<sum<<endl;
  7.   return 0;
  8.   }
  9.    
  10.   float add(float f,int count)
  11.   {
  12.   if(count==1)
  13.   return f;
  14.   else
  15.   return add(f,count/2)+add(f,count-count/2);
  16.   }
  • 1


二分法遞迴計算加法,這樣會沒有誤差,但是函式呼叫消耗大(尤其是多次)

 

 

方法三

使用double,精度更高,但是本來是沒有必要用這麼高精度的

 

方法四

ieee浮點數,為了規格化,精度每超過2的整數次冪,精度要下降一位,
你的f是0.1,float位數是23,當sum足夠大的時候,會出現 sum+f==sum 的情況,這個是ieee標準,
和C++沒關係,事實上編譯器應該已經做了浮點精度調整了,你這結果誤差算小的了.
避免這種誤差的方法就是浮點數,永遠不要讓一個很大的數去加上一個很小的數.不知你這段程式碼的目的是

什麼,但如果你改成這樣,誤差會小很多:

 

    1.   float f = 0.1;
    2.   float sum = 0;
    3.   for( i=0; i<100; i++)
    4.   {
    5.   int sumEachBig=0;
    6.   for(....k<400....)
    7.   {
    8.   int sumEachSmall=0;
    9.   for(....j<100.....)
    10.   sumEachSmall += f;
    11. sumEachBig+=sumEachSmall;
    12.  
    13. }  
    14. sum += sumEachBig;
    15.  
    16. }