这次来看看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.0d / 0.0;
复制代码
POSITIVE_INFINITY
表示正无穷,值为0x7ff0000000000000L
;标准定义指数域全为1,尾数域全为0NEGATIVE_INFINITY
表示负无穷,值为0xfff0000000000000L
;标准定义指数域全为1,尾数域全为0NaN
英文缩写,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
表示了最大的指数值,为1023MIN_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
,那再理解这个其实很简单。