在 Java 中,double 类型遵循 IEEE 754 浮点数标准,支持两个特殊的非数值概念:NaN (Not-a-Number)无穷大 (Infinity)。正确理解和处理这些特殊值对于编写健壮的数值计算程序至关重要。


📚 一、核心概念

1. NaN (Not-a-Number)

  • 定义:表示“不是一个数字”的结果,通常由未定义或不可表示的数学运算产生。
  • Java 常量Double.NaN
  • 特点
    • 不等于任何值,包括它自己(NaN != NaN
    • 任何涉及 NaN 的算术运算结果通常为 NaN
    • 用于表示无效、未定义或无法表示的计算结果

2. 无穷大 (Infinity)

  • 正无穷大Double.POSITIVE_INFINITY
  • 负无穷大Double.NEGATIVE_INFINITY
  • 产生原因
    • 非零数除以零
    • 运算结果超出 double 表示范围(上溢)
  • 特点
    • 有符号(正/负)
    • 可以参与比较和算术运算

🧩 二、如何产生 NaN 和 无穷大

✅ 产生 NaN 的运算

运算 示例代码 结果
0.0 / 0.0 0.0 / 0.0 NaN
∞ - ∞ Double.POSITIVE_INFINITY - Double.POSITIVE_INFINITY NaN
∞ × 0 Double.POSITIVE_INFINITY * 0.0 NaN
√(-1) Math.sqrt(-1.0) NaN
log(-1) Math.log(-1.0) NaN
NaN 参与任何运算 NaN + 1.0 NaN
System.out.println(0.0 / 0.0);  // NaN
System.out.println(Math.sqrt(-1.0)); // NaN
System.out.println(Double.NaN + 10); // NaN

✅ 产生 无穷大的运算

运算 示例代码 结果
正数 ÷ 0 1.0 / 0.0 POSITIVE_INFINITY
负数 ÷ 0 -1.0 / 0.0 NEGATIVE_INFINITY
上溢(过大) Double.MAX_VALUE * 2 POSITIVE_INFINITY
下溢(过小负数) -Double.MAX_VALUE * 2 NEGATIVE_INFINITY
log(0) Math.log(0.0) NEGATIVE_INFINITY
System.out.println(1.0 / 0.0);           // Infinity
System.out.println(-1.0 / 0.0);          // -Infinity
System.out.println(Double.MAX_VALUE * 2); // Infinity
System.out.println(Math.log(0.0));       // -Infinity

⚖️ 三、比较规则(核心难点)

1. NaN 的比较规则(反直觉!)

比较表达式 结果 说明
NaN == NaN false 最易错! NaN 不等于任何值,包括自己
NaN != NaN true 因为 NaN 不等于任何值
NaN > 5 false 所有比较运算符对 NaN 返回 false
NaN < 5 false
NaN >= 5 false
NaN <= 5 false
x != x truexNaN 判断 NaN 的技巧
Double nan = Double.NaN;

System.out.println(nan == nan);      // false ❌
System.out.println(nan != nan);      // true  ✅
System.out.println(nan > 5);         // false
System.out.println(nan < 5);         // false

// ✅ 正确判断 NaN 的方式
System.out.println(Double.isNaN(nan)); // true
System.out.println(nan.isNaN());       // true
System.out.println(nan != nan);        // true (技巧)

2. 无穷大的比较规则

比较 结果
POSITIVE_INFINITY > 任何有限值 true
NEGATIVE_INFINITY < 任何有限值 true
POSITIVE_INFINITY == POSITIVE_INFINITY true
NEGATIVE_INFINITY == NEGATIVE_INFINITY true
POSITIVE_INFINITY > NEGATIVE_INFINITY true
double inf = Double.POSITIVE_INFINITY;
double negInf = Double.NEGATIVE_INFINITY;

System.out.println(inf > 1_000_000);     // true
System.out.println(negInf < -1_000_000);  // true
System.out.println(inf == Double.POSITIVE_INFINITY); // true

🔄 四、算术运算规则

1. NaN 参与运算

  • 规则:任何涉及 NaN 的算术运算结果通常为 NaN
  • 示例
    System.out.println(NaN + 1.0);     // NaN
    System.out.println(NaN * 2.0);     // NaN
    System.out.println(NaN / 3.0);     // NaN
    System.out.println(Math.max(NaN, 5.0)); // NaN
    

2. 无穷大参与运算

运算 结果 说明
∞ + ∞ 正无穷大
∞ - ∞ NaN 未定义
∞ × ∞ 符号相乘
∞ × 0 NaN 未定义
∞ / ∞ NaN 未定义
c / ∞ 0.0 c 为有限值
∞ + c c 为有限值
System.out.println(∞ + ∞);        // ∞
System.out.println(∞ - ∞);        // NaN
System.out.println(∞ * 2);        // ∞
System.out.println(1 / ∞);        // 0.0

🔍 五、判断方法(正确方式)

✅ 推荐的判断方法

判断目标 正确方法 错误方法
是否为 NaN Double.isNaN(x)x.isNaN() x == Double.NaN
是否为无穷大 Double.isInfinite(x)x.isInfinite() 手动比较
是否为正无穷 x == Double.POSITIVE_INFINITY x > 0 && isInfinite()
是否为负无穷 x == Double.NEGATIVE_INFINITY x < 0 && isInfinite()
是否为有限值 Double.isFinite(x) (Java 8+) !isInfinite() && !isNaN()
double x = getSomeValue();

if (Double.isNaN(x)) {
    System.out.println("x is NaN");
} else if (Double.isInfinite(x)) {
    if (x == Double.POSITIVE_INFINITY) {
        System.out.println("x is +∞");
    } else {
        System.out.println("x is -∞");
    }
} else if (Double.isFinite(x)) {
    System.out.println("x is finite: " + x);
}

🛠️ 六、使用技巧与最佳实践

1. 安全的数值比较(防 NaN)

public static boolean safeEquals(double a, double b) {
    if (Double.isNaN(a) && Double.isNaN(b)) return true;
    if (Double.isInfinite(a) && Double.isInfinite(b)) {
        return a == b; // ±∞ 需要符号相同
    }
    return a == b;
}

2. 安全的最大值/最小值计算

public static double safeMax(double a, double b) {
    if (Double.isNaN(a) || Double.isNaN(b)) return Double.NaN;
    if (Double.isInfinite(a) && a > 0) return a;
    if (Double.isInfinite(b) && b > 0) return b;
    return Math.max(a, b);
}

3. 在集合中的行为

Double nan1 = Double.NaN;
Double nan2 = Double.NaN;

Set<Double> set = new HashSet<>();
set.add(nan1);
set.add(nan2);

System.out.println(set.size()); // 可能是 1 或 2? 
// 实际上:HashSet 使用 hashCode(),而 Double.hashCode(NaN) 是固定的,所以 size=1

DoublehashCode() 对所有 NaN 返回相同值,确保集合行为一致。

4. 字符串转换

System.out.println(String.valueOf(Double.NaN));           // "NaN"
System.out.println(String.valueOf(Double.POSITIVE_INFINITY)); // "Infinity"
System.out.println(String.valueOf(Double.NEGATIVE_INFINITY)); // "-Infinity"

// 解析
Double d1 = Double.valueOf("NaN");     // → Double.NaN
Double d2 = Double.valueOf("Infinity"); // → POSITIVE_INFINITY
Double d3 = Double.valueOf("-Infinity"); // → NEGATIVE_INFINITY

⚠️ 七、常见陷阱与错误

错误 问题 正确做法
if (x == Double.NaN) 永远为 false if (Double.isNaN(x))
if (x != x) 虽然正确但不直观 优先用 isNaN()
忽略 NaN 传播 导致后续计算全部为 NaN 早期检测和处理
== 比较浮点数 精度问题 + NaN 陷阱 equals() 或误差比较
假设 ∞ - ∞ = 0 实际为 NaN 检查是否为无穷大

📊 八、总结:关键规则速查表

概念 规则 正确方法
NaN 定义 未定义或无效的数值结果 0/0, √(-1), ∞-∞
NaN 比较 NaN != NaN(永远成立) Double.isNaN(x)
NaN 运算 任何运算含 NaNNaN 早期检测
正无穷大 > 任何有限值 x == Double.POSITIVE_INFINITY
负无穷大 < 任何有限值 x == Double.NEGATIVE_INFINITY
判断有限值 NaN 且非 Double.isFinite(x)
字符串表示 "NaN", "Infinity", "-Infinity" valueOf() 可解析

一句话掌握

记住 NaN != NaN,永远用 Double.isNaN(x) 判断 NaN;无穷大有符号,可比较;涉及 NaN 的运算结果通常为 NaN