Float型別出現舍入誤差的原因(round 取位)
在練習時,輸入如下程式碼:
結果不準確。
原因:https://blog.csdn.net/bitcarmanlee/article/details/51179572
浮點數一個普遍的問題就是在計算機的世界中,浮點數並不能準確地表示十進位制。並且,即便是最簡單的數學運算,也會帶來不可控制的後果。因為,在計算機的世界中只認識0與1
python中的decimal模組可以解決上面的煩惱
decimal模組中,可以通過整數,字串或原則構建decimal.Decimal物件。如果是浮點數,特別注意因為浮點數本身存在誤差,需要先將浮點數轉化為字串。
當然精度提升的同時,肯定帶來的是效能的損失。在對資料要求特別精確的場合(例如財務結算),這些效能的損失是值得的。但是如果是大規模的科學計算,就需要考慮執行效率了。畢竟原生的float比Decimal物件肯定是要快很多的。
使用上述辦法解決後:
知識點總結:
1. decimal模組:
Python提供了decimal模組用於十進位制數學計算,它具有以下特點:
- 提供十進位制資料型別,並且儲存為十進位制數序列;
- 有界精度:用於儲存數字的位數是固定的,可以通過decimal.getcontext().prec=x 來設定,不同的數字可以有不同的精度
- 浮點:十進位制小數點的位置不固定(但位數是固定的)
首先是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,
那麼為什麼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
- 1
虛擬碼如上
解決方法就是把多餘的誤差部分算出來(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);
- }
- 1
二分法遞迴計算加法,這樣會沒有誤差,但是函式呼叫消耗大(尤其是多次)
方法三
使用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;
- }