这次来看看 Double
的源代码,基于 jdk1.8.0_181.jdk 版本,如有错误,欢迎联系指出。
Double
是 double
基础数据类型的包装类,而 double
是 IEEE 754
标准的 双精度 64bit 的浮点数,具体 IEEE 754
标准的一些信息这里就不再详细的介绍了,建议可以看看我的上一篇文章[Java源码]Float 对此有个大致的了解,两者的逻辑基本是一致的。Java中对于十进制浮点数, double
是默认的类型。
继续使用类似的图说明下,
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.0d / 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
方法进行处理。
public static double parseDouble(String s) throws NumberFormatException { return FloatingDecimal.parseDouble(s); } 复制代码
内部调用了 FloatingDecimal.parseDouble
实现具体逻辑,其中具体的处理过程和 Float
类似,可以查看parsefloat 方法 了解,这里就不再重复叙述了。结果返回对应的 double
类型数据值。
public static String toString(double d) { return FloatingDecimal.toJavaFormatString(d); } 复制代码
依然是调用了 FloatingDecimal.toJavaFormatString
的方法,处理过程和 Float
也基本一致,结果返回对应的字符串格式。
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(); } } 复制代码
整体的逻辑在代码注释中进行了说明,清晰且简单,结果返回对应的十六进制字符串。
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一个新对象返回。
public static boolean isNaN(double v) { return (v != v); } public boolean isNaN() { return isNaN(value); } 复制代码
判断是否是 NaN
,使用 (v != v)
判断;具体 NaN
的规则描述可以参考isNaN 方法
public static boolean isInfinite(double v) { return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY); } public boolean isInfinite() { return isInfinite(value); } 复制代码
判断是不是无穷数,包含正无穷和负无穷
public static boolean isFinite(double d) { return Math.abs(d) <= DoubleConsts.MAX_VALUE; } 复制代码
通过输入参数绝对值是否小于 double
类型的最大值,判断是不是有限数
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; } 复制代码
返回对应类型的值,直接进行强制类型转换
@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
public static native double longBitsToDouble(long bits); 复制代码
longBitsToDouble
是个 native
方法,由c代码实现。返回对应 double
数据值
参数为 0x7ff0000000000000L
时,结果为正无穷
参数为 0xfff0000000000000L
时,结果为负无穷
参数在 0x7ff0000000000001L ~ 0x7fffffffffffffffL
或者 0xfff0000000000001L ~ 0xffffffffffffffffL
之间时,结果为 NaN
。
public static native long doubleToRawLongBits(double value); 复制代码
doubleToRawLongBits
是个 native
方法,由对应的c代码实现。
结果会保留 NaN
值,正无穷结果为 0x7ff0000000000000L
;负无穷结果为 0xfff0000000000000L
;当参数为 NaN
时,结果会是输入参数对应的实际整数值,该方法不会像 doubleToLongBits
,对 NaN
进行统一的返回值处理
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 方法 说明。
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 复制代码
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
这类的特殊值,可以自行编写几个进行测试一下。
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
or double
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
and double
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
or double
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 to long
and double
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时,这个问题一般是忽略的。但是当你使用的环境不符合要求时, 请注意这个问题的存在 。