1. 程式人生 > 程式設計 >[Java原始碼]Double

[Java原始碼]Double

這次來看看Double的原始碼,基於 jdk1.8.0_181.jdk 版本,如有錯誤,歡迎聯絡指出。

前言

Doubledouble基礎資料型別的包裝類,而doubleIEEE 754標準的雙精度 64bit 的浮點數,具體IEEE 754標準的一些資訊這裡就不再詳細的介紹了,建議可以看看我的上一篇文章 [Java原始碼]Float 對此有個大致的瞭解,兩者的邏輯基本是一致的。Java中對於十進位制浮點數,double是預設的型別。

雙精度(64 bit)

繼續使用類似的圖說明下,

  • sign: 符號位,1位。0表示正數,1表示負數。
  • exponent: 指數位,11位。雙精度的指數部分是−1022~+1024加上偏移值1023,指數值的大小從1~2046(0和2047是特殊值)
  • fraction: 尾數位,52位。

類資訊

public final class Double extends Number implements Comparable<Double>
複製程式碼

定義中帶有final標識,表示是不可繼承的,另外繼承了Number類,實現了Comparable介面

屬性

public static final double POSITIVE_INFINITY = 1.0 / 0.0;

public static final double NEGATIVE_INFINITY = -1.0 / 0.0;

public static final double NaN = 0.0
d / 0.0; 複製程式碼
  • POSITIVE_INFINITY 表示正無窮,值為0x7ff0000000000000L標準定義指數域全為1,尾數域全為0
  • NEGATIVE_INFINITY 表示負無窮,值為0xfff0000000000000L標準定義指數域全為1,尾數域全為0
  • NaN 英文縮寫,Not-a-Number標準定義為 指數域全為1,尾數域不全為0
public static final double MAX_VALUE = 0x1.fffffffffffffP+1023; // 1.7976931348623157e+308

public static final double MIN_NORMAL = 0x1.0p-1022; // 2.2250738585072014E-308
public static final double MIN_VALUE = 0x0.0000000000001P-1022; // 4.9e-324 複製程式碼
  • MAX_VALUE 最大規約數為0x1.fffffffffffffP+1023,這裡是十六進位制浮點數表示,也就是0x7fefffffffffffffL,也是(2 - Math.pow(2,-52) * Math.pow(2,1023)),計算值為1.7976931348623157e+308
  • MIN_NORMAL 最小的規約數為0x1.0p-1022,這裡是十六進位制浮點數表示,也就是0x0010000000000000L,也是Math.pow(2,-1022),計算值為2.2250738585072014E-308
  • MIN_VALUE 最小非規約數為0x0.0000000000001P-1022,這裡是十六進位制浮點數表示,也就是0x1L,也是Math.pow(2,-1074),計算值為4.9e-324
public static final int MAX_EXPONENT = 1023;

public static final int MIN_EXPONENT = -1022;
複製程式碼
  • MAX_EXPONENT 表示了最大的指數值,為1023
  • MIN_EXPONENT 表示了最小的指數值,為-1022
public static final int SIZE = 64;
複製程式碼

定義了 bit 位數

public static final int BYTES = SIZE / Byte.SIZE;
複製程式碼

定義了Double物件的位元組數,計算值固定為8

@SuppressWarnings("unchecked")
public static final Class<Double>   TYPE = (Class<Double>) Class.getPrimitiveClass("double");
複製程式碼

獲取類資訊,Double.TYPE == double.class兩者是等價的

private final double value;
複製程式碼

Doubledouble的包裝類,這裡存放了對應的double資料值

private static final long serialVersionUID = -9172774392245257468L;
複製程式碼

方法

構造方法

public Double(double value) {
  this.value = value;
}

public Double(String s) throws NumberFormatException {
  value = parseDouble(s);
}
複製程式碼

可以傳入double或者String型別引數,String引數的構造方法內部會呼叫parseDouble方法進行處理。

parseDouble 方法

public static double parseDouble(String s) throws NumberFormatException {
  return FloatingDecimal.parseDouble(s);
}
複製程式碼

內部呼叫了FloatingDecimal.parseDouble實現具體邏輯,其中具體的處理過程和Float類似,可以檢視 parsefloat 方法 瞭解,這裡就不再重複敘述了。結果返回對應的double型別資料值。

toString 方法

public static String toString(double d) {
  return FloatingDecimal.toJavaFormatString(d);
}
複製程式碼

依然是呼叫了FloatingDecimal.toJavaFormatString的方法,處理過程和Float也基本一致,結果返回對應的字串格式。

toHexString 方法

public static String toHexString(double d) {
	// 判斷是否是有限數值
  if (!isFinite(d) )
    // 對於 infinity 和 NaN,直接呼叫 toString 返回
    return Double.toString(d);
  else {
    // 使用最大輸出長度初始化StringBuilder容量
    StringBuilder answer = new StringBuilder(24);
		
    // 負數,增加符號標識
    if (Math.copySign(1.0,d) == -1.0)
      answer.append("-");            

    answer.append("0x");

    d = Math.abs(d);
		// 如果是0.0,直接輸出返回
    if(d == 0.0) {
      answer.append("0.0p0");
    } else {
      // 判斷是否為非規約數
      boolean subnormal = (d < DoubleConsts.MIN_NORMAL);

      // DoubleConsts.SIGNIF_BIT_MASK = 0x000FFFFFFFFFFFFFL
      // & 操作保留尾數位資料
      // | 操作是將最高位設為1,為了保留指數位的0,保留原來的長度,因為是個long型別整數
      long signifBits = (Double.doubleToLongBits(d)
                         & DoubleConsts.SIGNIF_BIT_MASK) |
        0x1000000000000000L;
	
      // 規約數為1.開頭,非規約數為0.開頭
      answer.append(subnormal ? "0." : "1.");

      // 使用Long.toHexString獲取十六進位制字串,提取尾數位對應的字串資訊
      // 判斷如果全為0,使用一個0替換
      // 如若不是,去除字串尾部的所有0
      String signif = Long.toHexString(signifBits).substring(3,16);
      answer.append(signif.equals("0000000000000") ? // 13 zeros
                    "0":
                    signif.replaceFirst("0{1,12}$",""));

      answer.append('p');
			
      // DoubleConsts.MIN_EXPONENT = -1022
      // 如果是非規約數,使用最小的指數位替換
      // 規約數,獲取對應的指數值替代
      answer.append(subnormal ?
                    DoubleConsts.MIN_EXPONENT:
                    Math.getExponent(d));
    }
    return answer.toString();
  }
}
複製程式碼

整體的邏輯在程式碼註釋中進行了說明,清晰且簡單,結果返回對應的十六進位制字串。

valueOf 方法

public static Double valueOf(double d) {
  return new Double(d);
}

public static Double valueOf(String s) throws NumberFormatException {
  return new Double(parseDouble(s));
}
複製程式碼

存在兩個valueOf方法,當引數為double型別時,直接new Double(d) 然後返回;對於字串引數,呼叫parseDouble轉換成double資料值,然後new一個新物件返回。

isNaN 方法

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

public boolean isNaN() {
  return isNaN(value);
}
複製程式碼

判斷是否是NaN,使用(v != v)判斷;具體NaN的規則描述可以參考 isNaN 方法

isInfinite 方法

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

public boolean isInfinite() {
  return isInfinite(value);
}
複製程式碼

判斷是不是無窮數,包含正無窮和負無窮

isFinite 方法

public static boolean isFinite(double d) {
  return Math.abs(d) <= DoubleConsts.MAX_VALUE;
}
複製程式碼

通過輸入引數絕對值是否小於double型別的最大值,判斷是不是有限數

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 (float)value;
}

public double doubleValue() {
  return value;
}
複製程式碼

返回對應型別的值,直接進行強制型別轉換

hashCode 方法

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

public static int hashCode(double value) {
  long bits = doubleToLongBits(value);
  return (int)(bits ^ (bits >>> 32));
}
複製程式碼

>>>為無符號右移,高位以0補齊。(bits ^ (bits >>> 32))邏輯為高32位與低32位異或計算返回int整數值作為hashCode

longBitsToDouble 方法

public static native double longBitsToDouble(long bits);
複製程式碼

longBitsToDouble是個native方法,由c程式碼實現。返回對應double資料值

  • 引數為0x7ff0000000000000L時,結果為正無窮

  • 引數為0xfff0000000000000L時,結果為負無窮

  • 引數在0x7ff0000000000001L ~ 0x7fffffffffffffffL或者0xfff0000000000001L ~ 0xffffffffffffffffL之間時,結果為NaN

doubleToRawLongBits 方法

public static native long doubleToRawLongBits(double value);
複製程式碼

doubleToRawLongBits是個native方法,由對應的c程式碼實現。

結果會保留NaN值,正無窮結果為0x7ff0000000000000L;負無窮結果為0xfff0000000000000L;當引數為NaN時,結果會是輸入引數對應的實際整數值,該方法不會像doubleToLongBits,對NaN進行統一的返回值處理

doubleToLongBits 方法

public static long doubleToLongBits(double value) {
  long result = doubleToRawLongBits(value);
  if ( ((result & DoubleConsts.EXP_BIT_MASK) ==
        DoubleConsts.EXP_BIT_MASK) &&
      (result & DoubleConsts.SIGNIF_BIT_MASK) != 0L)
    result = 0x7ff8000000000000L;
  return result;
}
複製程式碼

基本與doubleToRawLongBits方法一致,只是增加了對NaN的判斷。若是NaN則直接返回0x7ff8000000000000L(對所有的NaN值進行了統一返回值處理)。這裡識別NaN的邏輯符合指數域全為1,尾數域不全為0的標準規範,具體說明可以參考 floatToIntBits 方法 說明。

equals 方法

public boolean equals(Object obj) {
  return (obj instanceof Double)
    && (doubleToLongBits(((Double)obj).value) ==
        doubleToLongBits(value));
}
複製程式碼

首先判斷是不是Double物件例項,然後通過doubleToLongBits獲取兩個對應的長整型數,判斷兩者是否一致;值得注意的是一些特殊值的判斷邏輯。

System.out.println(new Double(0.0d).equals(new Double(-0.0d))); // false
System.out.println(new Double(Double.NaN).equals(new Double(-Double.NaN))); // true
複製程式碼

compare 方法

public int compareTo(Double anotherDouble) {
  return Double.compare(value,anotherDouble.value);
}

public static int compare(double d1,double d2) {
  if (d1 < d2)
    return -1;           // Neither val is NaN,thisVal is smaller
  if (d1 > d2)
    return 1;            // Neither val is NaN,thisVal is larger

  // Cannot use doubleToRawLongBits because of possibility of NaNs.
  long thisBits    = Double.doubleToLongBits(d1);
  long anotherBits = Double.doubleToLongBits(d2);

  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)
}
複製程式碼

Float一致,可以參考 compare方法 段落說明,需要注意的依然是-0.00.0Double.NaN-Double.NaN這類的特殊值,可以自行編寫幾個進行測試一下。

sum、min、max方法

public static double sum(double a,double b) {
  return a + b;
}

public static double max(double a,double b) {
  return Math.max(a,b);
}

public static double min(double a,double b) {
  return Math.min(a,b);
}
複製程式碼

邏輯很簡單,依舊需要注意的是-0.00.0Double.NaN-Double.NaN這類的特殊值,可以自行測試下結果,也許會出乎你的意料哦。

特別說明

因為double是64bit,需要注意下double的原子性邏輯,這裡是官方檔案的具體說明Non-Atomic Treatment of doubleand long,引用解釋一下:

For the purposes of the Java programming language memory model,a single write to a non-volatile longordoublevalue is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write,and the second 32 bits from another write.

Writes and reads of volatilelongand doublevalues are always atomic.

Writes to and reads of references are always atomic,regardless of whether they are implemented as 32-bit or 64-bit values.

Some implementations may find it convenient to divide a single write action on a 64-bit longor doublevalue into two write actions on adjacent 32-bit values. For efficiency's sake,this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to longand doublevalues atomically or in two parts.

Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatileor synchronize their programs correctly to avoid possible complications.

出於Java程式語言記憶體模型的原因,對於沒有volatile修飾的long或者double的單個寫操作,會被分成兩次寫操作,每次對32位操作。因此可能會導致執行緒會讀到來自不同執行緒寫入的32位資料組合的錯誤結果。

對於volatile修飾的longdouble而言,寫和讀操作都是原子的。

對於引用的讀寫,不管是32位或者64的資料值,都是原子操作。

一些實現方案中也許會發現將64位資料的單次寫操作分成兩次相鄰32位資料的寫操作很方便。出於效率的緣故,這種是比較特殊的實現;JVM的實現可以自由的選擇對longvalue的寫入採用原子邏輯或者分成兩步。

鼓勵JVM的實現在可能的情況下避免拆分64位的邏輯。對於程式設計師而言,鼓勵在共享的64位值上新增volatile或者synchronize的宣告修飾,避免複雜問題的出現。

從上面的描述可以看出來,原子性的問題是可能存在的。不過對於現在絕大部分的64位的機器以及使用64位的JVM時,這個問題一般是忽略的。但是當你使用的環境不符合要求時,請注意這個問題的存在

總結

總的程式碼邏輯來看,DoubleFloat的邏輯基本一致,因為都是IEEE 754標準的浮點數,主要還是使用的bit數不同帶來的一些差距。如果你已經瞭解了float,那再理解這個其實很簡單。

最後

部落格原文地址:blog.renyijiu.com/post/java原始碼…