float,double等精度丢失问题
來自MSDN的解釋:
http://msdn.microsoft.com/zh-cn/c151dt3s.aspx
為何浮點數可能丟失精度浮點十進制值通常沒有完全相同的二進制表示形式。 這是 CPU 所采用的浮點數據表示形式的副作用。 為此,可能會經歷一些精度丟失,并且一些浮點運算可能會產生意外的結果。
導致此行為的原因是下面之一:
十進制數的二進制表示形式可能不精確。
使用的數字之間類型不匹配(例如,混合使用浮點型和雙精度型)。
為解決此行為,大多數程序員或是確保值比需要的大或者小,或是獲取并使用可以維護精度的二進制編碼的十進制 (BCD) 庫。
現在我們就詳細剖析一下浮點型運算為什么會造成精度丟失?
1、小數的二進制表示問題
???????首先我們要搞清楚下面兩個問題:
???? (1) 十進制整數如何轉化為二進制數
?????????? 算法很簡單。舉個例子,11表示成二進制數:
???????????????????? 11/2=5 余?? 1
?????????????????????? 5/2=2?? 余?? 1
?????????????????????? 2/2=1?? 余?? 0
?????????????????????? 1/2=0?? 余?? 1
????????????????????????? 0結束???????? 11二進制表示為(從下往上):1011
????????? 這里提一點:只要遇到除以后的結果為0了就結束了,大家想一想,所有的整數除以2是不是一定能夠最終得到0。換句話說,所有的整數轉變為二進制數的算法會不會無限循環下去呢?絕對不會,整數永遠可以用二進制精確表示?,但小數就不一定了。
????? (2) 十進制小數如何轉化為二進制數
?????????? 算法是乘以2直到沒有了小數為止。舉個例子,0.9表示成二進制數
???????????????????? 0.9*2=1.8?? 取整數部分 1
???????????????????? 0.8(1.8的小數部分)*2=1.6??? 取整數部分 1
???????????????????? 0.6*2=1.2?? 取整數部分 1
???????????????????? 0.2*2=0.4?? 取整數部分 0
???????????????????? 0.4*2=0.8?? 取整數部分 0
???????????????????? 0.8*2=1.6 取整數部分 1
???????????????????? 0.6*2=1.2?? 取整數部分 0
????????????????????????????? .........????? 0.9二進制表示為(從上往下): 1100100100100......
?????????? 注意:上面的計算過程循環了,也就是說*2永遠不可能消滅小數部分,這樣算法將無限下去。很顯然,小數的二進制表示有時是不可能精確的?。其實道理很簡單,十進制系統中能不能準確表示出1/3呢?同樣二進制系統也無法準確表示1/10。這也就解釋了為什么浮點型減法出現了"減不盡"的精度丟失問題。
?
2、?float型在內存中的存儲?
???? 眾所周知、?Java 的float型在內存中占4個字節。float的32個二進制位結構如下
??????????
float內存存儲結構?
???????????? 4bytes ???? 31 ?? 30 ?? 29----23 ?? 22----0 ????????
????????????表示?????? 實數符號位 ?? 指數符號位??????? 指數位????????? 有效數位
??????? 其中符號位1表示正,0表示負。有效位數位24位,其中一位是實數符號位。
?
?????????將一個float型轉化為內存存儲格式的步驟為:
????????(1)先將這個實數的絕對值化為二進制格式,注意實數的整數部分和小數部分的二進制方法在上面已經探討過了。?
???? (2)將這個二進制格式實數的小數點左移或右移n位,直到小數點移動到第一個有效數字的右邊。?
???? (3)從小數點右邊第一位開始數出二十三位數字放入第22到第0位。?
???? (4)如果實數是正的,則在第31位放入“0”,否則放入“1”。?
???? (5)如果n 是左移得到的,說明指數是正的,第30位放入“1”。如果n是右移得到的或n=0,則第30位放入“0”。?
???? (6)如果n是左移得到的,則將n減去1后化為二進制,并在左邊加“0”補足七位,放入第29到第23位。如果n是右移得到的或n=0,則將n化為二進制后在左邊加“0”補足七位,再各位求反,再放入第29到第23位。
?
?????? ???舉例說明: 11.9的內存存儲格式
?????? (1) 將11.9化為二進制后大約是"?1011.?1110011001100110011001100..."。
?????? (2) 將小數點左移三位到第一個有效位右側:?"1.?011?11100110011001100110?"。?保證有效位數24位,右側多余的截取(誤差在這里產生了?)。
?????? (3)?這已經有了二十四位有效數字,將最左邊一位“1”去掉,得到“?011?11100110011001100110?”共23bit。將它放入float存儲結構的第22到第0位。
?????? (4) 因為11.9是正數,因此在第31位實數符號位放入“0”。
?????? (5) 由于我們把小數點左移,因此在第30位指數符號位放入“1”。
?????? (6) 因為我們是把小數點左移3位,因此將3減去1得2,化為二進制,并補足7位得到0000010,放入第29到第23位。
?
?????????? 最后表示11.9為:?0?1?0000010?011?11100110011001100110
?
???????????再舉一個例子:0.2356的內存存儲格式
?? ?? (1)將0.2356化為二進制后大約是0.00111100010100000100100000。?
????? (2)將小數點右移三位得到1.11100010100000100100000。?
????? (3)從小數點右邊數出二十三位有效數字,即11100010100000100100000放
入第22到第0位。?
????? (4)由于0.2356是正的,所以在第31位放入“0”。?
????? (5)由于我們把小數點右移了,所以在第30位放入“0”。?
????? (6)因為小數點被右移了3位,所以將3化為二進制,在左邊補“0”補足七
位,得到0000011,各位取反,得到1111100,放入第29到第23位。?
???????
???????????最后表示0.2356為:0?0?1111100?11100010100000100100000
?
???????????將一個內存存儲的float二進制格式轉化為十進制的步驟:?
???? (1)將第22位到第0位的二進制數寫出來,在最左邊補一位“1”,得到二十四位有效數字。將小數點點在最左邊那個“1”的右邊。?
???? (2)取出第29到第23位所表示的值n。當30位是“0”時將n各位求反。當30位是“1”時將n增1。?
???? (3)將小數點左移n位(當30位是“0”時)或右移n位(當30位是“1”時),得到一個二進制表示的實數。?
???? (4)將這個二進制實數化為十進制,并根據第31位是“0”還是“1”加上正號或負號即可。
?
3、浮點型的減法運算
?
??????????浮點加減運算過程比定點運算過程復雜。完成浮點加減運算的操作過程大體分為四步:?
?(1) 0操作數的檢查;
????????????????如果判斷兩個需要加減的浮點數有一個為0,即可得知運算結果而沒有必要再進行有序的一些列操作。?
(2) 比較階碼(指數位)大小并完成對階;
????????????????兩浮點數進行加減,首先要看兩數的?指數位?是否相同,即小數點位置是否對齊。若兩數?指數位?相同,表示小數點是對齊的,就可以進行尾數的加減運算。反之,若兩數階碼不同,表示小數點位置沒有對齊,此時必須使兩數的階碼相同,這個過程叫做對階?。
????????????????如何對?階(假設兩浮點數的指數位為?Ex?和 Ey?):
????????通過尾數的移位以改變?Ex?或 Ey?,使之相等。 由于浮點表示的數多是規格化的,尾數左移會引起最高有位的丟失,造成很大誤差;而尾數右移雖引起最低有效位的丟失,但造成的誤差較小,因此,對階操作規定使尾數右移,尾數右移后使階碼作相應增加,其數值保持不變。很顯然,一個增加后的階碼與另一個相等,所增加的階碼一定是小階。因此在對階時,總是使小階向大階看齊?,即小階的尾數向右移位?( 相當于小數點左移 ) ,每右移一位,其階碼加 1 ,直到兩數的階碼相等為止,右移的位數等于階差 △?E?。?
(3) 尾數(有效數位)進行加或減運算;
???????????????對階完畢后就可?有效數位?求和。 不論是加法運算還是減法運算,都按加法進行操作,其方法與定點加減運算完全一樣。?
(4) 結果規格化并進行舍入處理。
????????????????略
4、 計算12.0f-11.9f
???????12.0f 的內存存儲格式為:?0?1?0000010?10000000000000000000000 ???
???? 11.9f 的內存存儲格式為:???0?1?0000010?011?11100110011001100110
???? 可見兩數的指數位完全相同,只要對有效數位進行減法即可。
???? 12.0f-11.9f?? 結果:?????????0?1?0000010?00000011001100110011010
?
???? 將結果還原為十進制為: 0.000?11001100110011010=?0.10000038
詳細的分析
由于對float或double 的使用不當,可能會出現精度丟失的問題。問題大概情況可以通過如下代碼理解:
view plaincopy to clipboardprint?
public class FloatDoubleTest {???
public static void main(String[] args) {???
float f = 20014999;???
double d = f;???
double d2 = 20014999;???
System.out.println("f=" + f);???
System.out.println("d=" + d);???
System.out.println("d2=" + d2);???
}???
}?
public class FloatDoubleTest {
public static void main(String[] args) {
float f = 20014999;
double d = f;
double d2 = 20014999;
System.out.println("f=" + f);
System.out.println("d=" + d);
System.out.println("d2=" + d2);
}
}
得到的結果如下:
f=2.0015E7
d=2.0015E7
d2=2.0014999E7
從輸出結果可以看出double 可以正確的表示20014999 ,而float 沒有辦法表示20014999 ,得到的只是一個近似值。這樣的結果很讓人訝異。20014999 這么小的數字在float下沒辦法表示。于是帶著這個問題,做了一次關于float和double學習,做個簡單分享,希望有助于大家對java 浮點數的理解。
?
關于 java 的 float 和 double
Java 語言支持兩種基本的浮點類型: float 和 double 。java 的浮點類型都依據 IEEE 754 標準。IEEE 754 定義了32 位和 64 位雙精度兩種浮點二進制小數標準。
IEEE 754 用科學記數法以底數為 2 的小數來表示浮點數。32 位浮點數用 1 位表示數字的符號,用 8 位來表示指數,用 23 位來表示尾數,即小數部分。作為有符號整數的指數可以有正負之分。小數部分用二進制(底數 2 )小數來表示。對于64 位雙精度浮點數,用 1 位表示數字的符號,用 11 位表示指數,52 位表示尾數。如下兩個圖來表示:
float(32位):
?
?
?
double(64位):??
?
?
?
都是分為三個部分:
(1) 一個單獨的符號位s 直接編碼符號s 。
(2)k 位的冪指數E ,移碼表示 。
(3)n 位的小數,原碼表示 。
那么 20014999 為什么用 float 沒有辦法正確表示?
結合float和double的表示方法,通過分析 20014999 的二進制表示就可以知道答案了。
以下程序可以得出 20014999 在 double 和 float 下的二進制表示方式。
view plaincopy to clipboardprint?
public class FloatDoubleTest3 {???
public static void main(String[] args) {???
double d = 8;???
long l = Double.doubleToLongBits(d);???
System.out.println(Long.toBinaryString(l));???
float f = 8;???
int i = Float.floatToIntBits(f);???
System.out.println(Integer.toBinaryString(i));???
}???
}?
public class FloatDoubleTest3 {
public static void main(String[] args) {
double d = 8;
long l = Double.doubleToLongBits(d);
System.out.println(Long.toBinaryString(l));
float f = 8;
int i = Float.floatToIntBits(f);
System.out.println(Integer.toBinaryString(i));
}
}
輸出結果如下:
Double:100000101110011000101100111100101110000000000000000000000000000
Float:1001011100110001011001111001100
對于輸出結果分析如下。對于都不 double 的二進制左邊補上符號位 0 剛好可以得到 64 位的二進制數。根據double的表示法,分為符號數、冪指數和尾數三個部分如下:
0 10000010111 0011000101100111100101110000000000000000000000000000
對于 float 左邊補上符號位 0 剛好可以得到 32 位的二進制數。 根據float的表示法, 也分為 符號數、冪指數和尾數三個部分如下 :
0 10010111 00110001011001111001100
綠色部分是符號位,紅色部分是冪指數,藍色部分是尾數。
對比可以得出:符號位都是 0 ,冪指數為移碼表示,兩者剛好也相等。唯一不同的是尾數。
在 double 的尾數為: 001100010110011110010111 0000000000000000000000000000 ,省略后面的零,至少需要24位才能正確表示 。
而在 float 下面尾數為: 00110001011001111001100 ,共 23 位。
為什么會這樣?原因很明顯,因為 float尾數 最多只能表示 23 位,所以 24 位的 001100010110011110010111 在 float 下面經過四舍五入變成了 23 位的 00110001011001111001100 。所以 20014999 在 float 下面變成了 20015000 。
也就是說 20014999 雖然是在float的表示范圍之內,但 在 IEEE 754 的 float 表示法精度長度沒有辦法表示出 20014999 ,而只能通過四舍五入得到一個近似值。?
總結
以上是生活随笔為你收集整理的float,double等精度丢失问题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: yii的多个相同modle表单提交问题(
- 下一篇: chmod g+s 、chmod o+t