在 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 |
true 当 x 为 NaN |
判断 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
参与运算
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
✅ Double
的 hashCode()
对所有 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 运算 |
任何运算含 NaN → NaN |
早期检测 |
正无穷大 |
> 任何有限值 |
x == Double.POSITIVE_INFINITY |
负无穷大 |
< 任何有限值 |
x == Double.NEGATIVE_INFINITY |
判断有限值 |
非 NaN 且非 ∞ |
Double.isFinite(x) |
字符串表示 |
"NaN" , "Infinity" , "-Infinity" |
valueOf() 可解析 |
✅ 一句话掌握:
记住 NaN != NaN
,永远用 Double.isNaN(x)
判断 NaN
;无穷大有符号,可比较;涉及 NaN
的运算结果通常为 NaN
。