此次咱們來看看Float
類的源代碼,基於 jdk1.8.0_181.jdk 版本 。html
由於Float
是float
數據類型的包裝類,因此先介紹一下float
的一些基礎知識點,咱們先看下官方文檔的基本介紹。java
The
float
data type is a single-precision 32-bit IEEE 754 floating point. Its range of values is beyond the scope of this discussion, but is specified in the Floating-Point Types, Formats, and Values section of the Java Language Specification. As with the recommendations forbyte
andshort
, use afloat
(instead ofdouble
) if you need to save memory in large arrays of floating point numbers. This data type should never be used for precise values, such as currency. For that, you will need to use the java.math.BigDecimal class instead. Numbers and Strings coversBigDecimal
and other useful classes provided by the Java platform.git
Java中的float
數據類型是個單精度 32bit 的 IEEE 754 標準的浮點數。而IEEE 754是目前最普遍使用的浮點數運算標準,定義了表示浮點數的格式(包括-0)與反常值,一些特殊數值(+/-∞與NaN),以及這些數值的"浮點數運算符"。另外規定了四種表示浮點數值的方式:單精度(32位)、雙精度(64位)、延伸單精度(43位以上)與延伸雙精度(79位以上)。具體更加詳細的標準說明能夠參考 IEEE 754 維基百科。github
借用維基百科的圖進行說明, express
1
位。0表示正數,1表示負數。8
位。單精度的指數部分是−126~+127加上偏移值127,指數值的大小從1~254(0和255是特殊值)23位
。舉個簡單的例子,如今有個"01000001001100010100011110101110"字符串進行簡單的分析:api
0
,表示正數10000010
,結果爲130,減去偏移量127後爲301100010100011110101110
,對應的值爲1.0110001010001111010111
1011.0001010001111010111
,轉成十進制爲11.07999992370605469
,約等於11.08
更加詳細的說明能夠自行搜索,或者查看官方文檔 Floating-Point Types, Formats, and Values數據結構
若是浮點數中指數域的編碼值在0 < exponent <= 2^e - 2
,且在科學表示法的表示方式下,尾數部分的最高有效位爲1,那麼這個浮點數稱爲規約形式的浮點數
。在這種狀況下,尾數有一位隱含的二進制有效數字1。oracle
若是浮點數的指數部分的編碼值爲0,尾數部分不爲0,那麼這個浮點數被稱爲非規約形式的浮點數
。通常是某個數字至關接近於0時纔會使用非規約形式來表示。ide
public final class Float extends Number implements Comparable<Float> 複製代碼
定義中帶有final
標識,表示是不可繼承的,另外繼承了Number
類,實現了Comparable
接口函數
public static final float POSITIVE_INFINITY = 1.0f / 0.0f;
public static final float NEGATIVE_INFINITY = -1.0f / 0.0f;
public static final float NaN = 0.0f / 0.0f;
複製代碼
POSITIVE_INFINITY
表示正無窮大,正無窮的值爲0 1111 1111 000 0000 0000 0000 0000 0000
(爲了方便看,中間保留了空格);標準定義爲指數域全爲1,尾數域全爲0NEGATIVE_INFINITY
表示負無窮大,對應的值爲1 1111 1111 000 0000 0000 0000 0000 0000
;標準定義爲指數域全爲1,尾數域全爲0NaN
英文縮寫,not a number,用來表示錯誤的狀況,例如 0 / 0
的問題;標準定義爲指數域全爲1,尾數域不全爲0,如0 1111 1111 000 0000 0000 0000 0000 0001
public static final float MAX_VALUE = 0x1.fffffeP+127f; // 3.4028235e+38f
public static final float MIN_NORMAL = 0x1.0p-126f; // 1.17549435E-38f
public static final float MIN_VALUE = 0x0.000002P-126f; // 1.4e-45f
複製代碼
MAX_VALUE
表示了float
類型的最大規約數爲0x1.fffffeP+127f
,這裏是十六進制的表示方式,即(2 - Math.pow(2, -23))*Math.pow(2, 127)
,結果即爲3.4028235e+38f
MIN_NORMAL
表示了最小的規約數,爲0x1.0p-126f
,即Math.pow(2, -126)
,結果爲1.17549435E-38f
MIN_VALUE
表示了最小的非規約數,爲0x0.000002P-126f
,即Math.pow(2, -149)
,結果爲1.4e-45f
public static final int MAX_EXPONENT = 127;
public static final int MIN_EXPONENT = -126;
複製代碼
MAX_EXPONENT
表示了最大的指數值,爲127
MIN_EXPONENT
表示了最小的指數值,爲-126
public static final int SIZE = 32;
複製代碼
定義了Float
的 bit
位數
public static final int BYTES = SIZE / Byte.SIZE;
複製代碼
定義了Float
的字節數,計算值固定爲4
@SuppressWarnings("unchecked")
public static final Class<Float> TYPE = (Class<Float>) Class.getPrimitiveClass("float");
複製代碼
獲取類信息,Float.TYPE == float.class
二者是等價的
private final float value
複製代碼
由於Float
是float
的包裝類,因此這裏就存放了對應的float
類型的值
private static final long serialVersionUID = -2671257302660747028L;
複製代碼
public Float(float value) {
this.value = value;
}
public Float(double value) {
this.value = (float)value;
}
public Float(String s) throws NumberFormatException {
value = parseFloat(s);
}
複製代碼
提供三種構造函數,能夠傳入String、float和double類型數據,其中String類型調用parseFloat
方法完成,double類型則直接強制類型轉換(可能會出現精度丟失的問題)。
public static float parseFloat(String s) throws NumberFormatException {
return FloatingDecimal.parseFloat(s);
}
複製代碼
經過調用FloatingDecimal.parseFloat
方法對字符串進行轉換,FloatingDecimal
類來自於sun.misc.FloatingDecimal
,並不在 src.zip 的源碼中包含,另外代碼較長,主要實現了IEEE 754標準的一些計算,這裏就不復制了,若是有興趣能夠去 FloatingDecimal 查看源碼,介紹一下大體邏輯:
-/+
開頭,肯定正負號NaN
字符串,是則返回Infinity
字符串,是則根據正負性,返回正無窮或者負無窮0x / 0X
開頭的16進制數,是的話調用parseHexString
處理。而後正則匹配是否符合格式要求,是的話按照規範進行轉換返回e
或者E
的科學計數,中間還須要考慮處理溢出的問題。f
或F
結束標誌判斷,判斷是否還有剩餘字符,否返回處理結果返回處理結果的過程比較有意思,當非0數字小於7位時,會直接進行結果處理;不然當符合必定要求時,會採用double
類型處理,而後強制轉換成 float
類型返回結果;最複雜的就是非0數字加上指數過大,超過一個能夠一步完成操做的界限時,會經過一個近似正確的答案,而後按10的冪進行縮放來遞進縮小偏差,當偏差小於一個承認值時就返回結果,這個過程當中間使用double
類型來避免產生上溢/下溢的問題。
這個過程的代碼比較複雜,可能是數學計算,不是很好理解,本文的敘述可能會有錯誤,若是有錯誤或者你有好的源代碼分析相關資料,歡迎聯繫指出。
public static String toString(float f) {
return FloatingDecimal.toJavaFormatString(f);
}
public String toString() {
return Float.toString(value);
}
複製代碼
兩個toString
方法,下面的方法內部實現調用了第一個方法,而第一個的實現經過FloatingDecimal
類的方法去實現。這裏的代碼依舊是比較複雜的🤦♂️,將浮點數轉換成二進制形式,而後判斷是不是正負無窮以及NaN邏輯(這比較簡單),而後開始處理邏輯(這裏過於複雜了,有興趣的能夠自行查看對應的源碼)。NaN
會返回對應的字符串"NaN",Infinity
會返回對應的"Infinity"或者"-Infinity",當輸入在10^-3 ~ 10^7
之間,會返回正常的十進制數,而不在這個範圍時,會採用科學計數法表示。
public static String toHexString(float f) {
if (Math.abs(f) < FloatConsts.MIN_NORMAL
&& f != 0.0f ) {// float subnormal
// Adjust exponent to create subnormal double, then
// replace subnormal double exponent with subnormal float
// exponent
String s = Double.toHexString(Math.scalb((double)f,
/* -1022+126 */
DoubleConsts.MIN_EXPONENT-
FloatConsts.MIN_EXPONENT));
return s.replaceFirst("p-1022$", "p-126");
}
else // double string will be the same as float string
return Double.toHexString(f);
}
複製代碼
返回十六進制格式字符串,內部主要使用了Double.toHexString
方法實現
public static Float valueOf(String s) throws NumberFormatException {
return new Float(parseFloat(s));
}
public static Float valueOf(float f) {
return new Float(f);
}
複製代碼
存在兩個valueOf
方法,當參數爲float
類型時,直接new Float(f)
而後返回;對於字符串參數,調用parseFloat
方法轉換成float
,而後new一個新的對象返回。
public boolean isNaN() {
return isNaN(value);
}
public static boolean isNaN(float v) {
return (v != v);
}
複製代碼
兩個isNaN
的方法,用於判斷傳入的float
是不是NaN
,第一個方法內部調用了第二個方法實現。而第二個方法內部直接使用了(v != v)
的邏輯。這是由NaN
相關標準決定的,NaN
是無序的,因此
NaN
時,<
, <=
,>
, and >=
均返回false
NaN
時,==
返回false
。特別是,若是x或者y是NaN
,那麼(x<y) == !(x>=y)
爲false
NaN
時,!=
返回true
。特別是,當且僅當x爲NaN
時,x != x
爲true
具體也能夠參考官方資料 oracel 的詳細說明。
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;
}
複製代碼
獲取各類類型的值,內部直接強制轉換成對應類型的值返回。
@Override
public int hashCode() {
return Float.hashCode(value);
}
public static int hashCode(float value) {
return floatToIntBits(value);
}
複製代碼
調用floatToIntBits
方法,也就是直接將對應浮點數轉換成整數做爲其hashCode
public static native int floatToRawIntBits(float value);
/* * Find the bit pattern corresponding to a given float, NOT collapsing NaNs */
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;
}
複製代碼
floatToRawIntBits
是個native
方法,具體實現由上面提供的c語言代碼實現。union
是個數據結構,能在同一個內存空間儲存不一樣的數據類型,也就是說同一塊內存,能夠表示float
,也能夠表示int
。
結果會保留NaN
值。正無窮結果爲0x7f800000
,負無窮結果爲0xff800000
;當參數爲NaN
時,結果會是實際的NaN
整數值,該方法不會像floatToIntBits
同樣,對NaN進行統一值處理。
public static native float intBitsToFloat(int bits);
/* * Find the float corresponding to a given bit pattern */
JNIEXPORT jfloat JNICALL 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;
}
複製代碼
intBitsToFloat
也是個native
方法,由上方對應的c語言代碼實現,具體就不贅述了。參數爲0x7f800000
時,結果爲正無窮;參數爲0xff800000
,結果爲負無窮;當參數爲0x7f800001 ~ 0x7fffffff
或者0xff800001 ~ 0xffffffff
之間時,結果爲NaN
。由於對於Java而言,相同類型而不一樣bit模式組成的NaN數據是不可分辨的。若是你須要區分,可使用上面的floatToRawIntBits
方法。
值得注意的是,這個方法可能沒法返回與參數數據 bit pattern 一致的NaN
結果。IEEE 754
標準區分了兩種類型的NaN
數據(quiet NaNs and signaling NaNs
),可是在Java中這兩種的處理是不可見的,因此對於某些值floatToRawIntBits(intBitsToFloat(start)) != start
多是會存在的。不過對於上述給出的範圍已經包含了全部可能的NaN
數據的位信息。
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
(這裏對NaN
作了統一處理,全部的都返回0x7fc00000
);正無窮爲0x7f800000
;負無窮爲0xff800000
;其餘的值返回對應的結果。
具體看下兩個判斷條件:
(result & FloatConsts.EXP_BIT_MASK) == FloatConsts.EXP_BIT_MASK
複製代碼
FloatConsts.EXP_BIT_MASK
值爲 0x7F800000
, 二進制爲 0 11111111 00000000000000000000000
,這裏就是對指數域作了判斷,指數域全爲1
result & FloatConsts.SIGNIF_BIT_MASK) != 0
複製代碼
FloatConsts.SIGNIF_BIT_MASK
值爲 0x007FFFFF
,二進制爲 0 00000000 11111111111111111111111
,這裏就是對尾數域進行判斷,尾數域不全爲0
二者的結合符合咱們上面所說的NaN
的標準定義。
public boolean equals(Object obj) {
return (obj instanceof Float)
&& (floatToIntBits(((Float)obj).value) == floatToIntBits(value));
}
複製代碼
首先判斷obj是否是Float
對象的實例,而後經過floatToIntBits
獲取兩個整數值,進行比較判斷是否一致;這裏注意的是當偏差小於精度範圍時,結果是可能返回true
的,例如
Float a = 100000f;
Float b = 100000.001f;
System.out.println(Float.floatToIntBits(a)); // 1203982336
System.out.println(Float.floatToIntBits(b)); // 1203982336
System.out.println(a.equals(b)); // true
System.out.println(new Float(0.0f).equals(new Float(-0.0f))); // false
複製代碼
public int compareTo(Float anotherFloat) {
return Float.compare(value, anotherFloat.value);
}
public static int compare(float f1, float f2) {
if (f1 < f2)
return -1; // Neither val is NaN, thisVal is smaller
if (f1 > f2)
return 1; // Neither val is NaN, thisVal is larger
// Cannot use floatToRawIntBits because of possibility of NaNs.
int thisBits = Float.floatToIntBits(f1);
int anotherBits = Float.floatToIntBits(f2);
return (thisBits == anotherBits ? 0 : // Values are equal
(thisBits < anotherBits ? -1 : // (-0.0, 0.0) or (!NaN, NaN)
1)); // (0.0, -0.0) or (NaN, !NaN)
}
複製代碼
compareTo
方法內部調用了compare
實現,因此看下compare
的具體實現。首先判斷< / >
操做,若是成立直接返回;若不成立,則表示數據存在NaN
狀況,對於NaN
來講,<, <=, >, and >=
判斷結果都是false
,剩下的註釋已經解釋的很清楚了,不贅述了。不過與==
仍是存在區別的,值得注意下。
System.out.println(-0.0f == 0.0f ? true : false); // true
System.out.println(-0.0f < 0.0f ? true : false); // false
System.out.println(-0.0f <= 0.0f ? true : false); // true
System.out.println(Float.compare(-0.0f, 0.0f)); // -1
System.out.println(Float.compare(0.0f, -0.0f)); // 1
System.out.println(Float.compare(-Float.NaN, Float.NaN)); // 0
System.out.println(Float.compare(Float.NaN, -Float.NaN)); // 0
System.out.println(Float.compare(1.0f, Float.NaN)); // -1
System.out.println(Float.compare(1.0f, -Float.NaN)); // -1
複製代碼
public static float sum(float a, float b) {
return a + b;
}
public static float max(float a, float b) {
return Math.max(a, b);
}
public static float min(float a, float b) {
return Math.min(a, b);
}
複製代碼
這個很好理解,不過仍是要注意一些特殊邊界值。
System.out.println(Float.max(0.0f, -0.0f)); // 0.0
System.out.println(Float.min(0.0f, -0.0f)); // -0.0
System.out.println(Float.max(Float.NaN, 1.0f)); // NaN
System.out.println(Float.min(-Float.NaN, 1.0f)); // NaN
複製代碼
從文章各類說明也能夠看出來,Float
代碼比前兩次相對來講複雜多了。其中比較重要的是IEEE 754
標準,由於實際代碼中不少都是根據標準而來,若是對標準有所瞭解總體思路理解起來就會簡單不少。另外其中一些方法的計算(如compare
,equals
)等也是比較有意思的,看了代碼你就瞭解了爲何new Float(0.0f).equals(new Float(-0.0f))
是不成立的。
另外中間由於考慮浮點數的有效位數這個問題,網上搜索了好久的資料,五花八門的,各類答案都有,不過仍是看英文資料敘述的詳細,有興趣的能夠看看下面提供的參考資料,強烈推薦!!!
博客地址原文路徑:blog.renyijiu.com/post/java源碼…