有關Java浮點數的一個有趣的例子以及一個尚未解決的疑惑(java在存儲float值時採用的什麼策略,是否存在舍入優化?)

有關Java浮點數精度丟失的一個有趣的例子以及尚未解決的疑惑

本文章重點在於表述最後的例子和尚未解決的疑問,故對於浮點數相關概念只是簡單進行敘述

一、浮點數的存儲模式

Java 語言支持兩種基本的浮點類型: float 和 double 。java 的浮點類型都依據 IEEE 754 標準。IEEE 754 定義了32 位和 64 位雙精度兩種浮點二進制小數標準。

IEEE 754 用科學記數法以底數爲 2 的小數來表示浮點數。

對於32 位浮點數float用 第1 位表示數字的符號,用第2至9位來表示指數,用 最後23 位來表示尾數,即小數部分。

  • float(32位):
    float

對於64 位雙精度浮點數,用 第1 位表示數字的符號,用 11 位表示指數,52 位表示尾數。

  • double(64位):
    double

(1)一個單獨的符號位s 直接編碼符號s 。

(2)k 位的冪指數E ,移碼錶示 。

(3)n 位的小數,原碼錶示 。

底數部分 使用2進制數來表示此浮點數的實際值。
指數部分 佔用8-bit的二進制數,可表示數值範圍爲0-255。 但是指數應可正可負,所以IEEE規定,此處算出的次方須減去127纔是真正的指數。所以float的指數可從 -126到128.
底數部分實際是佔用24-bit的一個值,由於其最高位始終爲 1 ,所以最高位省去不存儲,在存儲中只有23-bit。

  • 規格化(標準化)與移位
    (以float爲例進行闡述)
    規格化的原因:在存儲時,需要進行規格化操作,使用尾數來存儲數值信息,而指數部分只記錄偏移量,所以進行規格化之後能夠統一存儲格式

  • 移位原因:
    根據IEEE 754標準,需要在指數部分+127
    指數有正有負,+127之後統一存儲爲正數,節省一位有效位

二、什麼時候出現無法表示?

任何一個浮點數字,在底層表示都必須轉換成這種科學計數法來表示,那麼我們來想想看什麼時候這個數字會無法表示呢?那麼只有兩種情形:

  1. 冪數不夠表示了:這種情況往往出現在數字太大了,超過冪數所能承受的範圍,那麼這個數字就無法表示了。如冪數最大隻能是10,但是這個數字用科學計數法表示時,冪數一定會超過10,就沒辦法了。

  2. 尾數不夠表示了:這種情況往往出現在數字精度太長了,如1.3434343233332這樣的數字,雖然很小,還不超過2,這種情況下冪數完全滿足要求,但是尾數已經不能表示出來了這麼長的精度。

三、表示範圍及有效位

  • float的範圍爲-2^128 ~ +2^127,也即-3.40E+38 ~ +3.40E+38
  • double的範圍爲-2^1024 ~ +2^1023,也即-1.79E+308 ~ +1.79E+308

四、例子及疑惑

在上課的時候,隨意瞎敲了一個例子,根據二進制轉換想要理解浮點數的存儲過程,計算得到的32bit的結果進行比對,發現有一位是不同的,原以爲輸出該是false,可輸出的結果卻是true。這是在是讓人不解
例子

	float d = 10.0000005f;
	float f = 10.000001f;
	
	System.out.println("(d=10.0000005) == (f=10.000001) ? "+(d == f));

運行結果爲:true

可能第一反應就是 丟精度了唄,最後精度丟了唄,有啥稀奇的。當時我也是這麼想的,接下來我們手動進行一下二進制存儲的轉換

d = 10.0000005
直接準換爲二進制:
1010.0000000000000000000010000110001101111011110100001
進行規格化,移動三位 E11
1.0100000000000000000000010000110001101111011110100001 E11
根據IEEE 754標準 指數+127,爲
	00000011
   +01111111
   ———————————
   	10000010
加上符號位 0 
存儲內容應爲(尾數取後23位):
0  10000010  01000000000000000000000--10.0000005f 


f = 10.000001
直接準換爲二進制:
1010.0000000000000000000100001100011011110111101000001
進行規格化,移動三位 E11
1.0100000000000000000000100001100011011110111101000001 E11
根據IEEE 754標準 指數+127,爲
	00000011
   +01111111
   ———————————
   	10000010
加上符號位 0 
存儲內容應爲(尾數取後23位):
0 10000010 01000000000000000000001--10.000001f


然後進行對比:
0  10000010  01000000000000000000000--10.0000005f 
0  10000010  01000000000000000000001--10.000001f

?????問題來了,這tm,判斷結果是true?嗯????
哈哈哈,不對啊,32bit最後一位存進去,一個是0,一個是1,結果是 true???
ei?不太對啊這個。

然後直接轉成二進制來一把:

public static void main(String[] args){

		float d = 10.0000005f;
		float f = 10.000001f;

		//System.out.println("(d=10.0000005) == (f=10.000001) ? "+(d == f));
		
		int k = Float.floatToIntBits(f);
		System.out.println(Integer.toBinaryString(k));
		k = Float.floatToIntBits(d);
		System.out.println(Integer.toBinaryString(k));
}

結果:
在這裏插入圖片描述
emmmm,這幾個意思???

沒招了,再debug一把?
在這裏插入圖片描述

阿嘞?? debug出來d的值就是10.000001,f的值還是10.000001。
難道是在存儲的時候被四捨五入掉了?所以纔會返回true嗎??可是這個進的一位是從哪來的?jvm是怎麼進行的取捨呢?這裏面究竟是怎麼個一個情況呢???
如果有大佬看到了這裏,並且瞭解相關的內容,一定要來幫我解解惑啊~~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章