文中相關源碼:java
Float.javagit
Float.cgithub
0.3f - 0.2f = ?
複製代碼
相信不少人會不假思索的填上 0.1f
,那麼,打開 IDEA
,默默的執行一下:數組
0.10000001
複製代碼
若是你對這個答案抱有疑問,那麼在閱讀 Float
源碼以前,咱們先來看一下 Float
在內存中是如何表示的。bash
從熟悉的十進制浮點數提及,以 12.34
爲例,顯然下面這個等式是成立的:微信
12.34 = 1 * 10^1 + 2 * 10^0 + 3 * 10^-1 + 4 * 10^-2
複製代碼
一樣的,對於二進制浮點數,也有以下等式,這裏以 10.11b
(代碼塊裏面好像打不了下標,本文中以 b
結尾的均表示二進制浮點數)爲例:數據結構
10.11 b = 1 * 2^1 + 0 * 2^0 + 1 * 2^-1 + 1 * 2^-2
= 2 + 1/2 + 1/4
= 2.75
複製代碼
這樣,二進制浮點數 10.11b
就轉換成了十進制浮點數 2.75
。ide
再看一個十進制小數 1.75
轉換爲二進制小數的例子:函數
1.75 = 1 + 3/4
= 7/4
= 7 * 2^-2
複製代碼
7
的二進制表示爲 111
,* 2^-2
表示將小數點左移兩位,獲得 1.11
。因此,1.75 = 1.11b
。this
下圖列舉一些常見小數的值:
二進制 | x^y | 十進制 |
---|---|---|
0.1 | 2 ^ -1 | 0.5 |
0.01 | 2 ^ -2 | 0.25 |
0.001 | 2 ^ -3 | 0.125 |
0.0001 | 2 ^ -4 | 0.0625 |
0.00001 | 2 ^ -5 | 0.03125 |
你發現問題的所在了嗎?咱們再回到 0.3f - 0.2f
的問題上。不論是整數仍是浮點數,最終在內存中都是以二進制形式存在的,那麼 0.3f
如何以二進制表示呢?顯而易見,沒有辦法以 x * 2^y
的形式來準確表示 0.3f
,也就是說,咱們並不能將 0.3f
準確的表示爲一個二進制小數,只能近似的表示它,增長二進制的長度能夠提升精確度。一樣,對於 0.2f
,咱們也無法準確的表示爲二進制小數,因此最後的計算結果纔不是 0.1f
。
最後再看一個減法,0.5f - 0.25f = ?
。答案是 0.25f
,我想這時候你應該不會再答錯了。由於 0.5f
和 0.25f
均可以準確的表示爲二進制小數,分別是 0.1b
和 0.01b
。
說到這裏,其實咱們仍是不瞭解 float
在內存中究竟是什麼樣的?int
型的 1
, 內存中就是 00000000000000000000000000000001
,那麼 0.75f
呢?關於浮點數,有一個普遍使用的運算標準,叫作 IEEE 754-1985,全稱 IEEE 二進制浮點數算數標準, 由 IEEE(電氣和電子工程師協會)指定,全部的計算機都支持 IEEE 浮點數標準。
本文後面都只針對 32 位單精度浮點數,對應 Java 中的 Float
。先來看維基百科上的一張圖:
這張圖描述了單精度浮點數在內存中具體的二進制表示方法:
1
位 。0
表示正數, 1
表示負數。用 s
表示8
位。用 E
表示,一般 E = exponent - 127
,exponent
爲無符號數23
位。用 M
表示,一般 M = 1.fraction
一般狀況下,一個浮點數能夠表示以下:
V = (-1)^s * M * 2^E
複製代碼
以上圖中的 0.15625f
爲例。符號位爲 0
,表示爲正數。階碼域爲 1111100
,等於十進制 124
,則 階碼 E = 124 - 127 = -3
。尾數域爲 01
,則 M = 1.01
。代入公式得:
V = (-1)^0 * 1.01 * 2^-3 = 0.00101 b = 0.15625
複製代碼
注意,* 2^-3
,等價於將小數點左移三位。
對於雙精度浮點數來講,exponent
是 11
位,fraction
是 52
位。
關於浮點數的詳細介紹能夠閱讀 《深刻理解計算機系統》
第二章第四節的相關內容。下面就進入 Float
的源碼部分。
public final class Float extends Number implements Comparable<Float>{}
複製代碼
不可變類,繼承了 Number
類,實現了 Comparable
接口。
private final float value;
private static final long serialVersionUID = -2671257302660747028L;
public static final Class<Float> TYPE = (Class<Float>) Class.getPrimitiveClass("float");
複製代碼
final
修飾的 value
字段保證其不可變性,value
也是 Float
類所包裝的浮點數。
// 0 11111111 00000000000000000000000
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
// 1 11111111 00000000000000000000000
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
複製代碼
正無窮和負無窮。階碼域都爲 1
,尾數域都爲 0
。
// 0 11111111 10000000000000000000000
public static final float NaN = 0.0f / 0.0f;
複製代碼
Not a number
,非數字。階碼域都爲 1
,尾數域不全爲 0
。
// 0 11111110 11111111111111111111111
public static final float MAX_VALUE = 0x1.fffffeP+127f;
複製代碼
最大值。階碼域爲 11111110
,即 127
。按公式計算,V = 1.11...1 * 2^127
。
/* * 0 00000001 00000000000000000000000 * 最小的規格化數(正數) */
public static final float MIN_NORMAL = 0x1.0p-126f; // 1.17549435E-38f
/* * 0 00000000 00000000000000000000001 * 最小的非規格化數(正數) */
public static final float MIN_VALUE = 0x0.000002P-126f;
複製代碼
這裏出現了兩個新名詞,規格化數
和 非規格化數
。上文中一直在說 一般狀況下
,這個一般狀況指的就是 規格化數
。那麼什麼是規格化數呢?階碼域 exponent != 0 && exponent != 255
,即階碼域即不全爲 0
,也不全爲 1
,這樣的浮點數就成爲規格化數。對於規格化數,有以下規則:
E = exponent - 127
M = 1.fraction
V = (-1)^s * M * 2^E
複製代碼
階碼域全爲 0
的浮點數是 非規格化數
。對於非規格化數,對應規則也發生了改變:
E = 1 - 127 = -126
M = 0.fraction
V = (-1)^s * M * 2^E
複製代碼
浮點數的計算方法並無發生改變,階碼 E
和尾數 M
的計算方法與規格化數不一樣了。非規格化數有兩個用途,第一,它能夠表示 0
。因爲規格化的尾數域 M = 1.fraction
,因此規格化數是無法表示零值的。除了符號位外,其餘域全爲 0
,就表示 0.0f
。根據符號位的不一樣,還有 +0.0f
和 -0.0f
,它們被認爲是不一樣的。第二,非規格數的存在使得浮點數可能表示的數值分佈更加均勻的接近於 0.0
,它能夠表示那些很是接近於 0 的數。
public static final int MAX_EXPONENT = 127; // 指數域(階碼)最大值
public static final int MIN_EXPONENT = -126; // 指數域(階碼)最小值
public static final int SIZE = 32; // float 佔 32 bits
public static final int BYTES = SIZE / Byte.SIZE; // float 佔 4 bytes
複製代碼
public Float(float value) {
this.value = value;
}
public Float(double value) {
this.value = (float)value;
}
public Float(String s) throws NumberFormatException {
value = parseFloat(s);
}
複製代碼
Float
有三個構造函數。第一個直接傳入 float
值。第二個傳入 double
值,再強轉 float
。第三個傳入 String
,調用 parseFloat()
函數轉換成 float
。下面就來看看這個 parseFloat
函數。
public static float parseFloat(String s) throws NumberFormatException {
return FloatingDecimal.parseFloat(s);
}
複製代碼
調用了 FloatDecimal
的 parseFloat(String)
方法。這個方法源碼至關的長,邏輯也比較複雜,我也只是大概看了一下流程。我就不貼源碼了,捋一下大體流程:
NaN
Infinity
0x
或 0X
開頭的十六進制浮點數。如果,調用 parseHexString()
方法處理0
e
或者 E
,須要注意科學計數法的處理ASCIIToBinaryBuffer
對象,調用其 floatValue()
方法,獲取最終結果這塊源碼看的只知其一;不知其二,有功夫再慢慢跟進。String
轉 float
的方法除此以外,還有 valueOf()
方法。
public static Float valueOf(String s) throws NumberFormatException {
return new Float(parseFloat(s));
}
複製代碼
沒啥好說的,仍是調用 parseFloat()
方法。
下面看一下 float
轉 String
的相關方法。
public String toString() {
return Float.toString(value);
}
public static String toString(float f) {
return FloatingDecimal.toJavaFormatString(f);
}
複製代碼
最終調用了 FloatDecimal
的 toJavaFormatString()
方法。這個方法也是源碼至關長,邏輯很複雜。首先會經過 floatToRawIntBits()
方法轉換成其符合 IEEE 754
標準的二進制形式對應的 int
值,再轉換爲相應的十進制字符串。
最後看一下 Float
中提供的其餘一些方法。
public static boolean isNaN(float v) {
return (v != v);
}
複製代碼
這個判斷頗有意思,v != v
。據此咱們能夠推斷出,對於任意不是 NaN
的 v
,一定知足 v == v
。對於爲 NaN
的 v
,一定知足 v != v
。
public boolean isInfinite() {
return isInfinite(value);
}
public static boolean isInfinite(float v) {
return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}
複製代碼
判斷是否爲正無窮或負無窮。
public static boolean isFinite(float f) {
return Math.abs(f) <= FloatConsts.MAX_VALUE;
}
複製代碼
判斷浮點數是不是一個有限值。
public byte byteValue() { return (byte)value; }
public short shortValue() { return (short)value; }
public int intValue() { return (int)value; }
public long longValue() { return (long)value; }
public float floatValue() { return value; }
public double doubleValue() { return (double)value; }
複製代碼
public static native int floatToRawIntBits(float value);
複製代碼
這是一個 native
方法,將 float
浮點數轉換爲其 IEEE 754
標準二進制形式對應的 int
值。由於 float
和 int
都是佔 32 位,因此每個 float 總有對應的 int 值。具體實如今 native/java/lang/Float.c
中:
JNIEXPORT jint JNICALL Java_java_lang_Float_floatToRawIntBits(JNIEnv *env, jclass unused, jfloat v) {
union {
int i;
float f;
} u;
u.f = (float)v;
return (jint)u.i;
}
複製代碼
union
是一種數據結構,它能在同一個內存空間中儲存不一樣的數據類型,也就是說同一塊內存,它能夠表示 float
, 也能夠表示 int
。經過 union
將 float
轉換爲其二進制對應的 int
值。
public static int floatToIntBits(float value) {
int result = floatToRawIntBits(value);
// Check for NaN based on values of bit fields, maximum
// exponent and nonzero significand.
if ( ((result & FloatConsts.EXP_BIT_MASK) ==
FloatConsts.EXP_BIT_MASK) &&
(result & FloatConsts.SIGNIF_BIT_MASK) != 0)
result = 0x7fc00000;
return result;
}
複製代碼
基本等同於 floatToRawIntBits()
方法,區別在於這裏對於 NaN
做了檢測,若是結果爲 NaN
, 直接返回 0x7fc00000
,也就是 Java 中的 Float.NaN
。乍看一下,這不是在畫蛇添足嗎?若是是 NaN
就直接返回 NaN
。還記得前面對 NaN
的說明嗎,階碼域都爲 1
,尾數域不全爲 0
,因此 IEEE 754
中的 NaN
並非一個固定的值,而是一個值域,可是在 Java 中將 Float.NaN
定義爲了 0x7fc00000
,相應二進制爲 0 11111111 10000000000000000000000
。因此方法參數中的 NaN
值並不必定就是 0x7fc00000
。從檢測 NaN
的條件中也能夠看出一二:
((result & FloatConsts.EXP_BIT_MASK) == FloatConsts.EXP_BIT_MASK) &&
(result & FloatConsts.SIGNIF_BIT_MASK) != 0
複製代碼
前半段是檢測階碼域的。FloatConsts.EXP_BIT_MASK
值爲 0x7F800000
, 二進制爲 0 11111111 00000000000000000000000
,若知足 (result & FloatConsts.EXP_BIT_MASK) == FloatConsts.EXP_BIT_MASK
,則 result
階碼域一定全爲 1
。
後半段是檢測尾數域的。FloatConsts.SIGNIF_BIT_MASK
值爲 0x007FFFFF
,二進制爲 0 00000000 11111111111111111111111
,若要知足 (result & FloatConsts.SIGNIF_BIT_MASK) != 0
, 則 result
尾數域不全爲 0
便可。
根據這裏兩個檢測條件也能夠知道這裏的 NaN
並非一個固定的值。可是 Float.NaN
又是一個固定的值,那麼如何獲取其餘不一樣的 NaN
呢?答案就是 intBitsToFloat(int)
方法。
public static native float intBitsToFloat(int bits);
Java_java_lang_Float_intBitsToFloat(JNIEnv *env, jclass unused, jint v)
{
union {
int i;
float f;
} u;
u.i = (long)v;
return (jfloat)u.f;
}
複製代碼
native
方法,也是經過聯合體 union
來實現的。只要參數中的 int
值知足 IEEE 754
對於 NaN
的標準,就能夠產生值不爲 Float.NaN
的 NaN
值了。
@Override
public int hashCode() {
return Float.hashCode(value);
}
public static int hashCode(float value) {
return floatToIntBits(value);
}
複製代碼
hashCode()
函數直接調用 floatToIntBits()
方法,返回其二進制對應的 int
值。
public boolean equals(Object obj) {
return (obj instanceof Float)
&& (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
}
複製代碼
equals
的條件是其 IEEE 754
標準的二進制形式相等。
Float
就說到這裏了。這篇源碼解釋不是不少,主要說明了 Float
在內存中的二進制形式,也就是 IEEE 754
標準。瞭解了 IEEE 754
,對於浮點數也就瞭然於心了。最後再推薦一下 《深刻理解計算機系統》 中 2.4
節關於浮點數的介紹。
文章同步更新於微信公衆號:
秉心說
, 專一 Java 、 Android 原創知識分享,LeetCode 題解,歡迎關注!