这次来看看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位。

类信息

1
public final class Double extends Number implements Comparable<Double>

定义中带有final标识,表示是不可继承的,另外继承了Number类,实现了Comparable接口

属性

1
2
3
4
5
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
1
2
3
4
5
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
1
2
3
public static final int MAX_EXPONENT = 1023;

public static final int MIN_EXPONENT = -1022;
  • MAX_EXPONENT 表示了最大的指数值,为1023
  • MIN_EXPONENT 表示了最小的指数值,为-1022
1
public static final int SIZE = 64;

定义了 bit 位数

1
public static final int BYTES = SIZE / Byte.SIZE;

定义了Double对象的字节数,计算值固定为8

1
2
@SuppressWarnings("unchecked")
public static final Class<Double>   TYPE = (Class<Double>) Class.getPrimitiveClass("double");

获取类信息,Double.TYPE == double.class两者是等价的

1
private final double value;

Doubledouble的包装类,这里存放了对应的double数据值

1
private static final long serialVersionUID = -9172774392245257468L;

方法

构造方法

1
2
3
4
5
6
7
public Double(double value) {
  this.value = value;
}

public Double(String s) throws NumberFormatException {
  value = parseDouble(s);
}

可以传入double或者String类型参数,String参数的构造方法内部会调用parseDouble方法进行处理。

parseDouble 方法

1
2
3
public static double parseDouble(String s) throws NumberFormatException {
  return FloatingDecimal.parseDouble(s);
}

内部调用了FloatingDecimal.parseDouble实现具体逻辑,其中具体的处理过程和Float类似,可以查看 parsefloat 方法 了解,这里就不再重复叙述了。结果返回对应的double类型数据值。

toString 方法

1
2
3
public static String toString(double d) {
  return FloatingDecimal.toJavaFormatString(d);
}

依然是调用了FloatingDecimal.toJavaFormatString的方法,处理过程和Float也基本一致,结果返回对应的字符串格式。

toHexString 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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 方法

1
2
3
4
5
6
7
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 方法

1
2
3
4
5
6
7
public static boolean isNaN(double v) {
  return (v != v);
}

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

判断是否是NaN,使用(v != v)判断;具体NaN的规则描述可以参考 isNaN 方法

isInfinite 方法

1
2
3
4
5
6
7
public static boolean isInfinite(double v) {
  return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}

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

判断是不是无穷数,包含正无穷和负无穷

isFinite 方法

1
2
3
public static boolean isFinite(double d) {
  return Math.abs(d) <= DoubleConsts.MAX_VALUE;
}

通过输入参数绝对值是否小于double类型的最大值,判断是不是有限数

xxxValue 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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 方法

1
2
3
4
5
6
7
8
9
@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 方法

1
public static native double longBitsToDouble(long bits);

longBitsToDouble是个native方法,由c代码实现。返回对应double数据值

  • 参数为0x7ff0000000000000L时,结果为正无穷

  • 参数为0xfff0000000000000L时,结果为负无穷

  • 参数在0x7ff0000000000001L ~ 0x7fffffffffffffffL或者0xfff0000000000001L ~ 0xffffffffffffffffL之间时,结果为NaN

doubleToRawLongBits 方法

1
public static native long doubleToRawLongBits(double value);

doubleToRawLongBits是个native方法,由对应的c代码实现。

结果会保留NaN值,正无穷结果为0x7ff0000000000000L;负无穷结果为0xfff0000000000000L;当参数为NaN时,结果会是输入参数对应的实际整数值,该方法不会像doubleToLongBits,对NaN进行统一的返回值处理

doubleToLongBits 方法

1
2
3
4
5
6
7
8
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 方法

1
2
3
4
5
public boolean equals(Object obj) {
  return (obj instanceof Double)
    && (doubleToLongBits(((Double)obj).value) ==
        doubleToLongBits(value));
}

首先判断是不是Double对象实例,然后通过doubleToLongBits获取两个对应的长整型数,判断两者是否一致;值得注意的是一些特殊值的判断逻辑。

1
2
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 方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
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方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
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,那再理解这个其实很简单。