[Java原始碼]Double
這次來看看Double
的原始碼,基於 jdk1.8.0_181.jdk 版本,如有錯誤,歡迎聯絡指出。
前言
Double
是double
基礎資料型別的包裝類,而double
是IEEE 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;
複製程式碼
Double
是double
的包裝類,這裡存放了對應的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.0
,0.0
,Double.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.0
,0.0
,Double.NaN
和-Double.NaN
這類的特殊值,可以自行測試下結果,也許會出乎你的意料哦。
特別說明
因為double
是64bit,需要注意下double
的原子性邏輯,這裡是官方檔案的具體說明Non-Atomic Treatment of double
and long
,引用解釋一下:
For the purposes of the Java programming language memory model,a single write to a non-volatile
long
ordouble
value 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 volatile
long
anddouble
values 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
long
ordouble
value 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 tolong
anddouble
values 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
volatile
or synchronize their programs correctly to avoid possible complications.
出於Java程式語言記憶體模型的原因,對於沒有volatile
修飾的long
或者double
的單個寫操作,會被分成兩次寫操作,每次對32位操作。因此可能會導致執行緒會讀到來自不同執行緒寫入的32位資料組合的錯誤結果。
對於volatile
修飾的long
和double
而言,寫和讀操作都是原子的。
對於引用的讀寫,不管是32位或者64的資料值,都是原子操作。
一些實現方案中也許會發現將64位資料的單次寫操作分成兩次相鄰32位資料的寫操作很方便。出於效率的緣故,這種是比較特殊的實現;JVM的實現可以自由的選擇對long
和value
的寫入採用原子邏輯或者分成兩步。
鼓勵JVM的實現在可能的情況下避免拆分64位的邏輯。對於程式設計師而言,鼓勵在共享的64位值上新增volatile
或者synchronize
的宣告修飾,避免複雜問題的出現。
從上面的描述可以看出來,原子性的問題是可能存在的。不過對於現在絕大部分的64位的機器以及使用64位的JVM時,這個問題一般是忽略的。但是當你使用的環境不符合要求時,請注意這個問題的存在。
總結
總的程式碼邏輯來看,Double
和Float
的邏輯基本一致,因為都是IEEE 754
標準的浮點數,主要還是使用的bit數不同帶來的一些差距。如果你已經瞭解了float
,那再理解這個其實很簡單。
最後
部落格原文地址:blog.renyijiu.com/post/java原始碼…