Double.doubleToLongBits()Double.doubleToRawLongBits() 是 Java 中用于将 double 类型的二进制表示转换为 long 值的两个关键方法。它们允许开发者直接访问 double 在内存中的 IEEE 754 浮点表示,对于理解浮点数内部结构、序列化、哈希计算等底层操作至关重要。


📚 一、核心概念与区别

特性 doubleToLongBits(double value) doubleToRawLongBits(double value)
功能 double 转换为等价的 long 位模式 double 转换为原始的 long 位模式
NaN 处理 所有 NaN 值映射为同一个 long 保留 NaN原始位模式(可能不同)
用途 保证 NaN一致性(如 equals()hashCode() 保留浮点数的完整原始信息
推荐场景 哈希计算、集合比较、序列化一致性 精确位操作、调试、反序列化原始数据

一句话区别
doubleToLongBits()NaN 进行“标准化”,而 doubleToRawLongBits() 保留原始位模式。


🧩 二、IEEE 754 双精度浮点格式回顾

double 是 64 位双精度浮点数,格式如下:

|  sign (1 bit)  |  exponent (11 bits)  |  mantissa (52 bits)  |
|     bit 63     |     bits 62-52       |      bits 51-0       |
  • 符号位 (sign):0 = 正,1 = 负
  • 指数 (exponent):偏移量为 1023
  • 尾数 (mantissa):隐含前导 1(规格化数)

特殊值:

  • 无穷大:指数全 1,尾数全 0
  • NaN:指数全 1,尾数非 0
  • :指数和尾数全 0(有 +0 和 -0)

💡 三、方法详解

1. Double.doubleToLongBits(double value)

public static long doubleToLongBits(double value)
  • 功能:返回表示 double 值的 64 位 long 整数。
  • 关键行为
    • 所有 NaN 值(无论位模式如何)都返回相同的 long 值:0x7ff8000000000000L
    • +0.0-0.0 返回不同的值(保留符号)
    • 正常数、无穷大等保持原位模式

用途:用于 Double 类的 hashCode()equals() 实现,确保所有 NaN 被视为相等。

2. Double.doubleToRawLongBits(double value)

public static long doubleToRawLongBits(double value)
  • 功能:返回 double 值在内存中的原始位模式
  • 关键行为
    • 完全保留原始位,包括 NaN 的不同表示形式
    • 不进行任何“标准化”处理
    • +0.0-0.0 也保留各自符号

用途:当你需要精确的位级复制,如序列化/反序列化、调试、与 C/C++ 交互时。


🧪 四、示例代码

示例 1:基本使用与 NaN 区别

public class DoubleBitsExample {
    public static void main(String[] args) {
        double normal = 3.14;
        double posZero = 0.0;
        double negZero = -0.0;
        double posInf = Double.POSITIVE_INFINITY;
        double negInf = Double.NEGATIVE_INFINITY;
        
        // 两种不同的 NaN(位模式不同)
        double nan1 = Double.NaN;
        double nan2 = Math.sqrt(-1.0); // 也是 NaN,但位模式可能不同
        
        System.out.printf("%-15s | %-20s | %-20s%n", "Value", "toLongBits", "toRawLongBits");
        System.out.println("-".repeat(60));
        
        printBits("3.14", normal);
        printBits("+0.0", posZero);
        printBits("-0.0", negZero);
        printBits("Infinity", posInf);
        printBits("-Infinity", negInf);
        printBits("NaN-1", nan1);
        printBits("NaN-2", nan2);
    }
    
    static void printBits(String label, double d) {
        long bits1 = Double.doubleToLongBits(d);
        long bits2 = Double.doubleToRawLongBits(d);
        
        // 如果两个方法结果不同,说明是 NaN 且位模式被标准化了
        String diff = (bits1 != bits2) ? " ⚠️" : "";
        
        System.out.printf("%-15s | 0x%016x | 0x%016x%s%n", 
                         label, bits1, bits2, diff);
    }
}

输出示例

Value           | toLongBits           | toRawLongBits       
------------------------------------------------------------
3.14            | 0x40091eb851eb851f   | 0x40091eb851eb851f   
+0.0            | 0x0000000000000000   | 0x0000000000000000   
-0.0            | 0x8000000000000000   | 0x8000000000000000   
Infinity        | 0x7ff0000000000000   | 0x7ff0000000000000   
-Infinity       | 0xfff0000000000000   | 0xfff0000000000000   
NaN-1           | 0x7ff8000000000000   | 0x7ff8000000000000   
NaN-2           | 0x7ff8000000000000   | 0x7ff8000000000000 ⚠️

⚠️ 注意:NaN-1NaN-2toLongBits 相同,但 toRawLongBits 可能不同(如果 JVM 生成了不同的 NaN 位模式)。


示例 2:+0.0 vs -0.0

double posZero = 0.0;
double negZero = -0.0;

System.out.println("0.0 == -0.0 ? " + (0.0 == -0.0)); // true (数值相等)

long pBits = Double.doubleToLongBits(posZero);
long nBits = Double.doubleToLongBits(negZero);

System.out.printf("0.0   bits: 0x%016x%n", pBits);   // 0x0000000000000000
System.out.printf("-0.0  bits: 0x%016x%n", nBits);   // 0x8000000000000000

// 但 Double.equals() 认为它们相等
Double d1 = 0.0, d2 = -0.0;
System.out.println("d1.equals(d2): " + d1.equals(d2)); // true

doubleToLongBits() 保留了符号位,但 Double.equals() 在比较时认为 +0.0-0.0 相等。


示例 3:反向转换(longBitsToDouble

long bits = Double.doubleToLongBits(3.14);
double recovered = Double.longBitsToDouble(bits);

System.out.println("Original: 3.14");
System.out.println("Recovered: " + recovered); // 3.14
System.out.println("Equal? " + (3.14 == recovered)); // true

Double.longBitsToDouble(long bits) 是逆操作,可用于精确重建 double 值。


⚠️ 五、注意事项

注意点 说明
NaN 一致性 doubleToLongBits() 保证所有 NaN 返回相同值,用于 hashCode()
性能 位操作非常快,适合高频计算
平台无关性 Java 的 double 遵循 IEEE 754,结果跨平台一致
不可变性 返回的是 long 值,不影响原 double
精度 完全精确,无精度损失
null 安全 方法参数是 double 基本类型,不可能为 null

🛠️ 六、使用技巧

1. 自定义 hashCode() 实现(模仿 Double

@Override
public int hashCode() {
    long bits = Double.doubleToLongBits(value);
    return (int)(bits ^ (bits >>> 32));
}

✅ 这是 Double.hashCode() 的标准实现,确保 NaN 的哈希码一致。

2. 序列化与反序列化

// 序列化
void writeDouble(double d, DataOutput out) throws IOException {
    long bits = Double.doubleToRawLongBits(d);  // 保留原始位
    out.writeLong(bits);
}

// 反序列化
double readDouble(DataInput in) throws IOException {
    long bits = in.readLong();
    return Double.longBitsToDouble(bits);
}

✅ 使用 doubleToRawLongBits() 确保位级精确复制。

3. 调试浮点数内部结构

void printDoubleStructure(double d) {
    long bits = Double.doubleToRawLongBits(d);
    
    int sign = (int)(bits >>> 63);
    int exponent = (int)((bits >>> 52) & 0x7FFL);
    long mantissa = bits & 0xFFFFFFFFFFFFFL;
    
    System.out.printf("Sign: %d, Exponent: %d, Mantissa: 0x%013x%n", 
                     sign, exponent, mantissa);
}

🔄 七、与相关方法对比

方法 用途 是否推荐
Double.doubleToLongBits(d) 哈希、比较,NaN 标准化 ✅ 推荐用于 equals/hashCode
Double.doubleToRawLongBits(d) 精确位操作、序列化 ✅ 推荐用于位级复制
Double.longBitsToDouble(l) 逆操作,重建 double ✅ 必需
Double.doubleToRawLongBits(d) vs Double.doubleToLongBits(d) 仅在 NaN 上有区别 视需求选择

📝 八、总结

项目 内容
核心功能 double 的二进制表示转换为 long
关键区别 doubleToLongBits() 标准化 NaNdoubleToRawLongBits() 保留原始位
典型应用 hashCode() 实现、序列化、调试、位操作
注意事项 NaN 处理差异、+0.0/-0.0 的位表示
最佳实践
性能 位操作,极快
线程安全 无状态,线程安全

一句话掌握

Double.doubleToLongBits() 保证 NaN 一致性(用于哈希),用 Double.doubleToRawLongBits() 保留原始位模式(用于精确复制),再用 Double.longBitsToDouble() 重建原值。