JVM支持IEEE-754浮點數(shù)標準(1985)。該標準定義了32位和64位浮點數(shù)的格式,以及在此之上的各種運算。在JVM中,浮點運算是基于32位float數(shù)和64位double數(shù)的。對每個操作float數(shù)的字節(jié)碼,都有一個對應的操作double數(shù)的版本。
浮點數(shù)由4部分組成:數(shù)符(sign),尾數(shù)(mantissa),基數(shù)(radix)和階碼(exponent)。數(shù)符取1或-1。尾數(shù)是一個正數(shù),它持有浮點數(shù)的有效位。階碼是尾數(shù)和符號位應該乘以的基數(shù)的正(負)冪數(shù)。這4個部分用下面的公式得到浮點數(shù)的值:
sign * mantissa * radixexponent |
浮點數(shù)有很多種表現(xiàn)形式,因為你總是可以把浮點數(shù)的尾數(shù)乘以基數(shù)的某次冪,然后通過改變階碼的方式獲得原先的值。例如,-5可以寫成如下以10為基數(shù)的形式:
Sign |
Mantissa |
Radix |
Exponent |
-1 |
50 |
10 |
-1 |
-1 |
5 |
10 |
0 |
-1 |
0.5 |
10 |
1 |
-1 |
0.05 |
10 |
2 |
每個浮點數(shù)都有一個規(guī)范化的表示形式。如果浮點數(shù)的尾數(shù)符合下面的公式,我們就說這個浮點數(shù)是規(guī)范化的。
規(guī)范化的以10為基數(shù)的浮點數(shù),尾數(shù)的小數(shù)點出現(xiàn)在第一個非0的有效位前面。-5的規(guī)范化形式是-1*0.5*10 1。也就是說,規(guī)范化的浮點數(shù)中,小數(shù)點的左邊是0,右邊第一位不是0。其他不是這種形式的浮點數(shù)都是非規(guī)范化數(shù)。注意,0沒有規(guī)范化的形式,因為它沒有非0數(shù)放在小數(shù)點后面。數(shù)字0們總是感嘆“為什么要規(guī)范化我們呢?”。
JVM中的浮點數(shù)以2為基數(shù),所以JVM中的浮點數(shù)值用下面的公式獲得:
sign * mantissa * 2exponent |
JVM中的浮點數(shù)的尾數(shù)用2進制數(shù)表示。規(guī)范化的二進制數(shù)小數(shù)點出現(xiàn)在非0最高有效位前面。由于二進制數(shù)系統(tǒng)只有2個數(shù)字,1和0,所以最高有效位上的數(shù)字總是1。
float和double數(shù)的最高有效位是它的符號位,float的最后23位是尾數(shù)位,而double則為最后52位。階碼處在符號位和尾數(shù)中間,float為8位,double為11位。float的完整2進制形式如下,s表示符號位,e表示階碼,m表示尾數(shù)。
s eeeeeeee mmmmmmmmmmmmmmmmmmmmmmm |
正數(shù)的符號位為0,負數(shù)的符號位為1。尾數(shù)總是一個正的二進制數(shù),但不是二進制補碼數(shù)。如果符號位為1,浮點數(shù)的值是負的,但尾數(shù)仍然是正的。
階碼有3種解釋方式。如果階碼位全是1,意味著這是一個特殊的浮點數(shù),表示正無窮或負無窮,或者不是一個數(shù)(NaN)。NaN是特定運算的結(jié)果,比如除法的除數(shù)是0。階碼的所有位為0,表示這是一個非規(guī)范化的浮點數(shù)。除上面2種情況之外的階碼,都是規(guī)范化浮點數(shù)的一部分。
尾數(shù)部分其實隱式包含了一個額外的精度位。float數(shù)的尾數(shù)占23位,卻有24個精度位;同一樣的,double數(shù)的尾數(shù)占52位,卻有53個精度位。因為尾數(shù)部分的最高有效位是可以被預測的,所以并沒有包含在尾數(shù)中。JVM中,浮點數(shù)的階碼可以指明該數(shù)是不是一個規(guī)范化浮點數(shù)。如果階碼位全0,則為非規(guī)范化數(shù),且最高有效位肯定是0。其他情況下,則為規(guī)范化浮點數(shù),且最高有效位肯定是1。
在JVM中,任何浮點運算都不會拋出異常。類似除數(shù)為0的問題操作,JVM會返回一些特殊值,比如正/負無窮,或NaN。尾數(shù)位全是0的情況下,如果階碼位全是1,符號位是0,則表示正無窮;如果階碼位全是1,符號位是1,則表示負無窮。如果階碼位全是1,尾數(shù)位不全是0,則表示NaN。JVM總是為NaN使用相同的尾數(shù):最高有效位是1,其他全為0。下表列出了上面提到的3中特殊值:
Special float values |
Float bits (sign exponent mantissa) |
+Infinity |
0 11111111 00000000000000000000000 |
-Infinity |
1 11111111 00000000000000000000000 |
NaN |
1 11111111 10000000000000000000000 |
非全0和非全1的階碼表示規(guī)范化尾數(shù)要乘以的2的冪數(shù)。可以把階碼當做一個正數(shù),然后減去一個偏移量,這樣就能得到實際的冪數(shù)。對float數(shù)來說,偏移量是126;而double數(shù),則為1023。例如,一個float數(shù)的階碼為00000001,則冪數(shù)為-125(1 – 126),這是float中最小的冪數(shù)。再看一個例子,如果階碼是11111110,則冪數(shù)為128(254 – 126),這是float中最大的冪數(shù)。下表列出了一些正規(guī)化的浮點數(shù):
Normalized float values |
Float bits (sign exponent mantissa) |
Unbiased exponent |
Largest positive (finite) float |
0 11111110 11111111111111111111111 |
128 |
Largest negative (finite) float |
1 11111110 11111111111111111111111 |
128 |
Smallest normalized float |
1 00000001 00000000000000000000000 |
-125 |
Pi |
0 10000000 10010010000111111011011 |
2 |
階碼位全0,說明尾數(shù)沒有規(guī)范化,也隱含說明了最高有效為是0,而不是1。這種情況下,冪數(shù)為浮點數(shù)的最小冪數(shù)。對float來說,是-125。這意味著,規(guī)范化尾數(shù)乘以2 -125的浮點數(shù),它的階碼為00000001,而非規(guī)范化尾數(shù)乘以2 -125的浮點數(shù),它的階碼為00000000。階碼范圍底端的非規(guī)范化數(shù)修正值,使得下溢出較為平緩。如果最小階碼用來表示規(guī)范化數(shù),下溢成0的最小數(shù)值會更大一些。 換句話說,讓最小階碼表示非規(guī)范化數(shù),可以使浮點數(shù)能表示更小的數(shù)值。雖然非規(guī)范化數(shù)的精度沒有規(guī)范化數(shù)高,但是,這相對于階碼一旦達到最小規(guī)范化數(shù)值,浮點數(shù)就會下溢成0來說,非規(guī)范化數(shù)更好一些。
Denormalized float values |
Float bits (sign exponent mantissa) |
Smallest positive (non-zero) float |
0 00000000 00000000000000000000001 |
Smallest negative (non-zero) float |
1 00000000 00000000000000000000001 |
Largest denormalized float |
1 00000000 11111111111111111111111 |
Positive zero |
0 00000000 00000000000000000000000 |
Negative zero |
1 00000000 00000000000000000000000 |
本文譯自:Floating-point arithmetic