[Java源碼]Float

此次咱們來看看Float類的源代碼,基於 jdk1.8.0_181.jdk 版本 。html

前言

由於Floatfloat數據類型的包裝類,因此先介紹一下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 for byte and short, use a float (instead of double) 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 covers BigDecimal and other useful classes provided by the Java platform.git

Java中的float數據類型是個單精度 32bitIEEE 754 標準的浮點數。而IEEE 754是目前最普遍使用的浮點數運算標準,定義了表示浮點數的格式(包括-0)與反常值,一些特殊數值(+/-∞與NaN),以及這些數值的"浮點數運算符"。另外規定了四種表示浮點數值的方式:單精度(32位)、雙精度(64位)、延伸單精度(43位以上)與延伸雙精度(79位以上)。具體更加詳細的標準說明能夠參考 IEEE 754 維基百科github

單精度(32bit)

借用維基百科的圖進行說明, express

  • sign: 符號位,1位。0表示正數,1表示負數。
  • exponent: 指數位,8位。單精度的指數部分是−126~+127加上偏移值127,指數值的大小從1~254(0和255是特殊值)
  • fraction: 尾數位,23位

舉個簡單的例子,如今有個"01000001001100010100011110101110"字符串進行簡單的分析:api

  1. 符號位爲0,表示正數
  2. 指數位爲10000010,結果爲130,減去偏移量127後爲3
  3. 尾數位爲01100010100011110101110,對應的值爲1.0110001010001111010111
  4. 因而獲得浮點數爲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,尾數域全爲0
  • NEGATIVE_INFINITY 表示負無窮大,對應的值爲1 1111 1111 000 0000 0000 0000 0000 0000標準定義爲指數域全爲1,尾數域全爲0
  • NaN 英文縮寫,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;
複製代碼

定義了Floatbit位數

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
複製代碼

由於Floatfloat的包裝類,因此這裏就存放了對應的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類型則直接強制類型轉換(可能會出現精度丟失的問題)。

parseFloat 方法

public static float parseFloat(String s) throws NumberFormatException {
  return FloatingDecimal.parseFloat(s);
}

複製代碼

經過調用FloatingDecimal.parseFloat方法對字符串進行轉換,FloatingDecimal類來自於sun.misc.FloatingDecimal,並不在 src.zip 的源碼中包含,另外代碼較長,主要實現了IEEE 754標準的一些計算,這裏就不復制了,若是有興趣能夠去 FloatingDecimal 查看源碼,介紹一下大體邏輯:

  1. 去除兩邊空格,判斷是否爲空字符串或者null,是則拋出異常
  2. 判斷是否-/+開頭,肯定正負號
  3. 判斷是否符合NaN字符串,是則返回
  4. 判斷是否符合Infinity字符串,是則根據正負性,返回正無窮或者負無窮
  5. 判斷是否0x / 0X開頭的16進制數,是的話調用parseHexString處理。而後正則匹配是否符合格式要求,是的話按照規範進行轉換返回
  6. 處理中間的數字字符串
  7. 判斷是否存在對應的e或者E的科學計數,中間還須要考慮處理溢出的問題。
  8. fF結束標誌判斷,判斷是否還有剩餘字符,否返回處理結果

返回處理結果的過程比較有意思,當非0數字小於7位時,會直接進行結果處理;不然當符合必定要求時,會採用double類型處理,而後強制轉換成 float類型返回結果;最複雜的就是非0數字加上指數過大,超過一個能夠一步完成操做的界限時,會經過一個近似正確的答案,而後按10的冪進行縮放來遞進縮小偏差,當偏差小於一個承認值時就返回結果,這個過程當中間使用double類型來避免產生上溢/下溢的問題。

這個過程的代碼比較複雜,可能是數學計算,不是很好理解,本文的敘述可能會有錯誤,若是有錯誤或者你有好的源代碼分析相關資料,歡迎聯繫指出。

toString 方法

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之間,會返回正常的十進制數,而不在這個範圍時,會採用科學計數法表示。

toHexString 方法

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方法實現

valueOf 方法

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一個新的對象返回。

isNaN 方法

public boolean isNaN() {
  return isNaN(value);
}

public static boolean isNaN(float v) {
  return (v != v);
}

複製代碼

兩個isNaN的方法,用於判斷傳入的float是不是NaN,第一個方法內部調用了第二個方法實現。而第二個方法內部直接使用了(v != v)的邏輯。這是由NaN相關標準決定的,NaN無序的,因此

  1. 當一個或者兩個操做數爲NaN時,<, <=,>, and >=均返回false
  2. 若是任一操做數爲NaN時,==返回false。特別是,若是x或者y是NaN,那麼(x<y) == !(x>=y)false
  3. 若是任一操做數爲NaN時,!= 返回true。特別是,當且僅當x爲NaN時,x != xtrue

具體也能夠參考官方資料 oracel 的詳細說明。

isInfinite 方法

public boolean isInfinite() {
  return isInfinite(value);
}

public static boolean isInfinite(float v) {
  return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}

複製代碼

判斷一個數是否是無窮數,包括正無窮和負無窮。

isFinite 方法

public static boolean isFinite(float f) {
  return Math.abs(f) <= FloatConsts.MAX_VALUE;
}

複製代碼

判斷輸入的數是否是有限浮點數,經過判斷輸入參數絕對值是否小於最大浮點數。

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;
}

複製代碼

獲取各類類型的值,內部直接強制轉換成對應類型的值返回。

hashCode 方法

@Override
public int hashCode() {
  return Float.hashCode(value);
}

public static int hashCode(float value) {
  return floatToIntBits(value);
}

複製代碼

調用floatToIntBits方法,也就是直接將對應浮點數轉換成整數做爲其hashCode

floatToRawIntBits 方法

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進行統一值處理。

intBitsToFloat 方法

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數據的位信息。

floatToIntBits 方法

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的標準定義。

equals 方法

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

複製代碼

compare 方法

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

複製代碼

sum、min、max 方法

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標準,由於實際代碼中不少都是根據標準而來,若是對標準有所瞭解總體思路理解起來就會簡單不少。另外其中一些方法的計算(如compareequals)等也是比較有意思的,看了代碼你就瞭解了爲何new Float(0.0f).equals(new Float(-0.0f))是不成立的。

另外中間由於考慮浮點數的有效位數這個問題,網上搜索了好久的資料,五花八門的,各類答案都有,不過仍是看英文資料敘述的詳細,有興趣的能夠看看下面提供的參考資料,強烈推薦!!!

參考資料

  1. 維基百科
  2. Single-precision_floating-point_format
  3. Is the most significant decimal digits precision that can be converted to binary and back to decimal without loss of significance 6 or 7.225?
  4. What's the reason why 「text-float-text」 guarantee 6 digit but 「float-text-float」 does 9?
  5. Decimal Precision of Binary Floating-Point Numbers

最後

博客地址原文路徑:blog.renyijiu.com/post/java源碼…

相關文章
相關標籤/搜索