從JDK源碼角度看Float

關於IEEE 754

在看Float前須要先了解IEEE 754標準,該標準定義了浮點數的格式還有一些特殊值,它規定了計算機中二進制與十進制浮點數轉換的格式及方法。規定了四種表示浮點數值的方法,單精確度(32位)、雙精確度(64位)、延伸單精確度(43位以上)與延伸雙精確度(79位以上)。多數編程語言支持單精確度和雙精確度,這裏討論的Float就是Java的單精確度的實現。html

浮點數的表示

浮點數由三部分組成,以下圖,符號位s、指數e和尾數f。java

這裏寫圖片描述
這裏寫圖片描述

對於求值咱們是有一個公式對應的,根據該公式來看會更簡單點,某個浮點數的值爲:編程

能夠看到32位的最高位爲符號標識符,1表示負數,0表示正數。指數部分爲8位,其實能夠是0到255,可是爲了可正可負,這裏須要減去127後纔是真正的指數,而底數固定爲2。剩下的23位表示尾數,但默認前面都會加上1.。因此經過上面就能夠將一個浮點數表示出來了。數組

咱們舉個例子來看,二進制的「01000001001101100000000000000000」表示的浮點數是啥?bash

  1. 符號位爲0,表示正數。
  2. 指數爲「10000010」,減去127後爲3。
  3. 尾數對應的值爲「1.011011」。
  4. 因而最終獲得浮點數爲「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,該方法的實現代碼實在是太長,這裏再也不貼出了,說下它的處理思想及步驟。oop

  1. 判斷開頭是否爲「-」或「+」符號,即正數或負數。
  2. 判斷是否是一個NaN,若是是則返回NaN。
  3. 判斷是否是一個Infinity,若是是則返回Infinity。
  4. 判斷是否是一個0x開頭的十六進制的Java浮點數,若是是則將該十六進制浮點數按照IEEE-754標準轉換成十進制浮點數。好比字符串爲 0x12.3512p+11f ,則轉換後爲37288.562。
  5. 判斷字符串中是否有包含了E字符,便是否是科學計數法,若是有則須要處理。好比字符串爲 10001.222E+2 ,則轉換後爲1000122.2。
  6. 還要處理浮點數精度問題,這個處理比較複雜,咱們知道浮點數的精度是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… 進行預約。感謝各位朋友。

爲何寫《Tomcat內核設計剖析》

=========================

相關閱讀:

從JDK源碼角度看Object
從JDK源碼角度看Long
從JDK源碼角度看Integer
volatile足以保證數據同步嗎
談談Java基礎數據類型
從JDK源碼角度看併發鎖的優化
從JDK源碼角度看線程的阻塞和喚醒
從JDK源碼角度看併發競爭的超時
從JDK源碼角度看java併發線程的中斷
從JDK源碼角度看Java併發的公平性
從JDK源碼角度看java併發的原子性如何保證
從JDK源碼角度看Byte
從JDK源碼角度看Boolean
從JDK源碼角度看Short

有幫助,可打賞:

歡迎關注:

相關文章
相關標籤/搜索