關於IEEE 754
在看Float前需要先了解IEEE 754標準,該標準定義了浮點數的格式還有一些特殊值,它規定了計算機中二進位制與十進位制浮點數轉換的格式及方法。規定了四種表示浮點數值的方法,單精確度(32位)、雙精確度(64位)、延伸單精確度(43位以上)與延伸雙精確度(79位以上)。多數程式語言支援單精確度和雙精確度,這裡討論的Float就是Java的單精確度的實現。
浮點數的表示
浮點數由三部分組成,如下圖,符號位s、指數e和尾數f。
對於求值我們是有一個公式對應的,根據該公式來看會更簡單點,某個浮點數的值為:
可以看到32位的最高位為符號識別符號,1表示負數,0表示正數。指數部分為8位,其實可以是0到255,但是為了可正可負,這裡需要減去127後才是真正的指數,而底數固定為2。剩下的23位表示尾數,但預設前面都會加上1.。所以通過上面就可以將一個浮點數表示出來了。
我們舉個例子來看,二進位制的“01000001001101100000000000000000”表示的浮點數是啥?
- 符號位為0,表示正數。
- 指數為“10000010”,減去127後為3。
- 尾數對應的值為“1.011011”。
- 於是最終得到浮點數為“1011.011”,轉成十進位制為“11.375”。
Float概況
Java的Float類主要的作用就是對基本型別float進行封裝,提供了一些處理float型別的方法,比如float到String型別的轉換方法或String型別到float型別的轉換方法,當然也包含與其他型別之間的轉換方法。
繼承結構
--java.lang.Object
--java.lang.Number
--java.lang.Float複製程式碼
主要屬性
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;
public static final float MAX_VALUE = 0x1.fffffeP+127f;
public static final float MIN_NORMAL = 0x1.0p-126f;
public static final float MIN_VALUE = 0x0.000002P-126f;
public static final int MAX_EXPONENT = 127;
public static final int MIN_EXPONENT = -126;
public static final int SIZE = 32;
public static final int BYTES = SIZE / Byte.SIZE;
public static final Class<Float> TYPE = (Class<Float>) Class.getPrimitiveClass("float");複製程式碼
POSITIVE_INFINITY
用來表示正無窮大,按照IEEE 754浮點標準規定,任何有限正數除以0為正無窮大,正無窮的值為0x7f800000。NEGATIVE_INFINITY
用來表示負無窮大,任何有限負數除以0為負無窮的,負無窮的值為0xff800000。NaN
用來表示處理計算中出現的錯誤情況,比如0除以0或負數平方根。對於單精度浮點數,IEEE 標準規定 NaN 的指數域全為 1,且尾數域不等於零的浮點數。它並沒有要求具體的尾數域,所以 NaN 實際上不非是一個,而是一族。Java這裡定義的值為0x7fc00000。MAX_VALUE
用來表示最大的浮點數值,它定義為0x1.fffffeP+127f,這裡0x表示十六進位制,1.fffffe表示十六進位制的小數,P表示2,+表示幾次方,這裡就是2的127次方,最後的f是轉成浮點型。所以最後最大值為3.4028235E38。MIN_NORMAL
用來表示最小標準值,它定義為0x1.0p-126f,這裡其實就是2的-126次方的了,值為1.17549435E-38f。MIN_VALUE
用來表示浮點數最小值,它定義為0x0.000002P-126f,最後的值為1.4e-45f。MAX_EXPONENT
用來表示指數的最大值,這裡定為127,這個也是按照IEEE 754浮點標準的規定。MIN_EXPONENT
用來表示指數的最小值,按照IEEE 754浮點標準的規定,它為-126。SIZE
用來表示二進位制float值的位元數,值為32,靜態變數且不可變。BYTES
用來表示二進位制float值的位元組數,值為SIZE
除於Byte.SIZE
,結果為4。TYPE
的toString的值是float
。
Class的getPrimitiveClass
是一個native方法,在Class.c
中有個Java_java_lang_Class_getPrimitiveClass
方法與之對應,所以JVM層面會通過JVM_FindPrimitiveClass
函式根據”float”字串獲得jclass,最終到Java層則為Class<Float>
。
JNIEXPORT jclass JNICALL
Java_java_lang_Class_getPrimitiveClass(JNIEnv *env,
jclass cls,
jstring name)
{
const char *utfName;
jclass result;
if (name == NULL) {
JNU_ThrowNullPointerException(env, 0);
return NULL;
}
utfName = (*env)->GetStringUTFChars(env, name, 0);
if (utfName == 0)
return NULL;
result = JVM_FindPrimitiveClass(env, utfName);
(*env)->ReleaseStringUTFChars(env, name, utfName);
return result;
}複製程式碼
當TYPE
執行toString時,邏輯如下,則其實是getName
函式決定其值,getName
通過native方法getName0
從JVM層獲取名稱,
public String toString() {
return (isInterface() ? "interface " : (isPrimitive() ? "" : "class "))
+ getName();
}複製程式碼
getName0
根據一個陣列獲得對應的名稱,JVM根據Java層的Class可得到對應型別的陣列下標,比如這裡下標為6,則名稱為”float”。
const char* type2name_tab[T_CONFLICT+1] = {
NULL, NULL, NULL, NULL,
"boolean",
"char",
"float",
"double",
"byte",
"short",
"int",
"long",
"object",
"array",
"void",
"*address*",
"*narrowoop*",
"*conflict*"
};複製程式碼
主要方法
parseFloat
public static float parseFloat(String s) throws NumberFormatException {
return FloatingDecimal.parseFloat(s);
}複製程式碼
通過呼叫FloatingDecimal的parseFloat方法來實現對字串的轉換,FloatingDecimal類主要提供了對 IEEE-754,該方法的實現程式碼實在是太長,這裡不再貼出了,說下它的處理思想及步驟。
- 判斷開頭是否為“-”或“+”符號,即正數或負數。
- 判斷是不是一個NaN,如果是則返回NaN。
- 判斷是不是一個Infinity,如果是則返回Infinity。
- 判斷是不是一個0x開頭的十六進位制的Java浮點數,如果是則將該十六進位制浮點數按照IEEE-754標準轉換成十進位制浮點數。比如字串為 0x12.3512p+11f ,則轉換後為37288.562。
- 判斷字串中是否有包含了E字元,即是否是科學計數法,如果有則需要處理。比如字串為 10001.222E+2 ,則轉換後為1000122.2。
- 還要處理浮點數精度問題,這個處理比較複雜,我們知道浮點數的精度是7位有效數字,這裡為什麼是7呢?還要回到IEEE-754標準上,32位二進位制轉換成浮點數是根據公式
$(-1)^s*(1.f)*2^{(e-127)}$
轉換的,可以看到它的精度由尾數來決定,尾數有23位,那麼$2^{23}=8388608$
,該值介於$10^{6}$
到$10^{7}$
,所以它能保證6位精確的數,但是7位就不一定了,這裡是相對小數點來說的,所以對應整個浮點型的精確值為有效位數就是7位,8位的不一定能準確表示。這裡對比幾個例子,字串30.200019轉換後為30.20002,一共七位有效位;字串30.200001轉換後為30.2,一共七位有效位,但後面都為0,所以省略;字串30000.2196501轉換後為30000.219,一共八位有效位,剛好能準確表示八位。
建構函式
public Float(String s) throws NumberFormatException {
value = parseFloat(s);
}
public Float(float value) {
this.value = value;
}
public Float(double value) {
this.value = (float)value;
}複製程式碼
提供三種建構函式,都比較簡單,可傳入String、float和double型別值,其中String型別會呼叫parseFloat方法進行轉換,double則直接轉成float型別。
toString
public String toString() {
return Float.toString(value);
}
public static String toString(float f) {
return FloatingDecimal.toJavaFormatString(f);
}複製程式碼
兩個toString方法,主要看第二個,通過FloatingDecimal類的toJavaFormatString方法轉成字串。這個轉換過程也是比較複雜,這裡不再貼程式碼,它處理的過程是先將浮點數轉成IEEE-754標準的二進位制形式,並且還要判斷是否是正負無窮大,是否是NaN。然後再按照IEEE-754標準從二進位制轉換成十進位制,此過程十分複雜,需要考慮的點相當多。最後生成浮點數對應的字串。
valueOf方法
public static Float valueOf(float f) {
return new Float(f);
}
public static Float valueOf(String s) throws NumberFormatException {
return new Float(parseFloat(s));
}複製程式碼
有兩個valueOf方法,對於float型的直接new一個Float物件返回,而對於字串則先呼叫parseFloat方法轉成float後再new一個Float物件返回。
xxxValue方法
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;
}複製程式碼
包括byteValue、shortValue、intValue、longValue、floatValue和doubleValue等方法,其實就是轉換成對應的型別。
floatToRawIntBits方法
public static native int floatToRawIntBits(float value);
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是一個本地方法,該方法主要是將一個浮點數轉成IEEE 754標準的二進位制形式對應的整型數。對應的本地方法的處理邏輯簡單而且有效,就是通過一個union實現了int和float的轉換,最後再轉成java的整型jint。
floatToIntBits方法
public static native float intBitsToFloat(int bits);
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;
}複製程式碼
該方法與floatToRawIntBits方法對應,floatToIntBits同樣是一個本地方法,該方法主要是將一個IEEE 754標準的二進位制形式對應的整型數轉成一個浮點數。可以看到其本地實現也是通過union來實現的,完成int轉成float,最後再轉成java的浮點型jfloat。
floatToIntBits方法
public static int floatToIntBits(float value) {
int result = floatToRawIntBits(value);
if ( ((result & FloatConsts.EXP_BIT_MASK) ==
FloatConsts.EXP_BIT_MASK) &&
(result & FloatConsts.SIGNIF_BIT_MASK) != 0)
result = 0x7fc00000;
return result;
}複製程式碼
該方法主要先通過呼叫floatToRawIntBits獲取到IEEE 754標準對應的整型數,然後再分別用FloatConsts.EXP_BIT_MASK和FloatConsts.SIGNIF_BIT_MASK兩個掩碼去判斷是否為NaN,0x7fc00000對應的即為NaN。
hashCode方法
public int hashCode() {
return Float.hashCode(value);
}
public static int hashCode(float value) {
return floatToIntBits(value);
}複製程式碼
主要看第二個hashCode方法即可,它是通過呼叫floatToIntBits來實現的,所以它返回的雜湊碼其實就是某個浮點數的IEEE 754標準對應的整型數。
isFinite 和 isInfinite
public static boolean isFinite(float f) {
return Math.abs(f) <= FloatConsts.MAX_VALUE;
}
public static boolean isInfinite(float v) {
return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}複製程式碼
這兩個方法分別用於判斷一個浮點數是否為有窮數或無窮數。邏輯很簡單,絕對值小於FloatConsts.MAX_VALUE的數則為有窮數,FloatConsts.MAX_VALUE的值為3.4028235e+38f,它其實與前面Float類中定義的MAX_VALUE相同。而是否為無窮數則通過POSITIVE_INFINITY和NEGATIVE_INFINITY進行判斷。
isNaN方法
public static boolean isNaN(float v) {
return (v != v);
}複製程式碼
用於判斷是個浮點數是否為NaN,該方法邏輯很簡單,直接(v != v),為啥能這樣做?因為規定一個NaN與任何值都不相等,包括它自己。所以這部分邏輯在JVM或本地中會做,於是可以直接通過比較來判斷。
max 和 min方法
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);
}複製程式碼
用於獲取兩者較大或較小值,直接交由Math類完成。
compare方法
public static int compare(float f1, float f2) {
if (f1 < f2)
return -1;
if (f1 > f2)
return 1;
int thisBits = Float.floatToIntBits(f1);
int anotherBits = Float.floatToIntBits(f2);
return (thisBits == anotherBits ? 0 :
(thisBits < anotherBits ? -1 :
1));
}複製程式碼
f1小於f2則返回-1,反之則返回1。無法通過上述直接比較時則使用floatToIntBits方法分別將f1和f2轉成IEEE 754標準對應的整型數,然後再比較。相等則返回0,否則返回-1或1。
以下是廣告和相關閱讀
========廣告時間========
鄙人的新書《Tomcat核心設計剖析》已經在京東銷售了,有需要的朋友可以到 item.jd.com/12185360.ht… 進行預定。感謝各位朋友。
=========================
相關閱讀:
從JDK原始碼角度看Object
從JDK原始碼角度看Long
從JDK原始碼角度看Integer
volatile足以保證資料同步嗎
談談Java基礎資料型別
從JDK原始碼角度看併發鎖的優化
從JDK原始碼角度看執行緒的阻塞和喚醒
從JDK原始碼角度看併發競爭的超時
從JDK原始碼角度看java併發執行緒的中斷
從JDK原始碼角度看Java併發的公平性
從JDK原始碼角度看java併發的原子性如何保證
從JDK原始碼角度看Byte
從JDK原始碼角度看Boolean
從JDK原始碼角度看Short
有幫助,可打賞:
歡迎關注: