Double 与 Float ------ IEEE 754 浮点数在 Java 中的实现
适用版本: JDK 8 难度等级: 进阶 核心概念: IEEE 754 标准、NaN 与无穷大、位布局、精度损失
一、IEEE 754 浮点数标准概要
Java 的 float 和 double 严格遵循 IEEE 754 标准。理解这个标准是掌握浮点包装类的关键。
c
double (64 位) 的位布局:
63 62 ─────────── 52 51 ───────────────────────── 0
┌────┐┌───────────────────┐┌────────────────────────────────┐
│sign│ exponent │ mantissa │
│ 1 │ 11 bits │ 52 bits │
└────┘└───────────────────┘└────────────────────────────────┘
float (32 位):
31 30 ──────── 23 22 ──────────────────────── 0
┌────┐┌───────────────┐┌─────────────────────────────┐
│sign│ exponent │ mantissa │
│ 1 │ 8 bits │ 23 bits │
└────┘┘───────────────┘└─────────────────────────────┘
二、Double 的核心属性
java
public final class Double extends Number implements Comparable<Double> {
// 正无穷大: 指数全1,尾数全0,符号位0
public static final double POSITIVE_INFINITY = 1.0 / 0.0;
// 负无穷大: 指数全1,尾数全0,符号位1
public static final double NEGATIVE_INFINITY = -1.0 / 0.0;
// NaN: 指数全1,尾数非0
public static final double NaN = 0.0d / 0.0;
public static final double MAX_VALUE = 0x1.fffffffffffffP+1023;
public static final double MIN_NORMAL = 0x1.0p-1022;
public static final double MIN_VALUE = 0x0.0000000000001P-1022;
public static final int MAX_EXPONENT = 1023;
public static final int MIN_EXPONENT = -1022;
public static final int SIZE = 64;
public static final int BYTES = SIZE / Byte.SIZE;
public static final Class<Double> TYPE
= (Class<Double>) Class.getPrimitiveClass("double");
private final double value;
}
三、特殊值判断方法
java
// NaN 的判定------利用了 NaN != NaN 的性质
public static boolean isNaN(double v) {
return (v != v);
}
public static boolean isInfinite(double v) {
return (v == POSITIVE_INFINITY) || (v == NEGATIVE_INFINITY);
}
public static boolean isFinite(double d) {
return Math.abs(d) <= Double.MAX_VALUE;
}
java
public class SpecialValuesDemo {
public static void main(String[] args) {
double nan = Double.NaN;
double inf = Double.POSITIVE_INFINITY;
System.out.println("NaN != NaN: " + (nan != nan)); // true
System.out.println("isNaN: " + Double.isNaN(nan)); // true
System.out.println("isFinite(NaN): " + Double.isFinite(nan)); // false
System.out.println("isInfinite(inf): " + Double.isInfinite(inf)); // true
// NaN 的哈希码是固定值
System.out.println("NaN hashCode: " + Double.hashCode(nan));
}
}
四、doubleToLongBits ------ 位布局解析
java
public static long doubleToLongBits(double value) {
long result = doubleToRawLongBits(value);
// 将所有 NaN 规范化为同一个"规范 NaN"值
if (((result & DoubleConsts.EXP_BIT_MASK) == DoubleConsts.EXP_BIT_MASK) &&
(result & DoubleConsts.SIGNIF_BIT_MASK) != 0L) {
result = 0x7ff8000000000000L;
}
return result;
}
// 原生方法,不做 NaN 规范化
public static native long doubleToRawLongBits(double value);
五、浮点数精度问题
java
public class PrecisionDemo {
public static void main(String[] args) {
// 经典案例1: 0.1 无法精确表示
System.out.println(0.1 + 0.2); // 0.30000000000000004
System.out.println(0.1 + 0.2 == 0.3); // false
// 经典案例2: 浮点数比较应使用容差
double a = 0.1 + 0.2;
double b = 0.3;
double epsilon = 1e-10;
System.out.println(Math.abs(a - b) < epsilon); // true
// 经典案例3: BigDecimal 用于精确计算
java.math.BigDecimal bd1 = new java.math.BigDecimal("0.1");
java.math.BigDecimal bd2 = new java.math.BigDecimal("0.2");
java.math.BigDecimal bd3 = bd1.add(bd2);
System.out.println(bd3); // 0.3------精确
}
}
六、compare 的特殊逻辑
java
public static int compare(double d1, double d2) {
if (d1 < d2) return -1;
if (d1 > d2) return 1;
long thisBits = Double.doubleToLongBits(d1);
long anotherBits = Double.doubleToLongBits(d2);
return (thisBits == anotherBits ? 0 :
(thisBits < anotherBits ? -1 : 1));
}
这个实现确保了:
-0.0 < 0.0(-0.0 的 longBits 是0x8000000000000000L,比 0.0 的0L大)NaN之间视为相等(规范化为相同的 longBits)NaN >任何非 NaN 值
七、Float 的特殊之处
Float 与 Double 高度类似但有几个关键差异:
| 特征 | Double | Float |
|---|---|---|
| 位数 | 64 | 32 |
| 指数位 | 11 | 8 |
| 尾数位 | 52 | 23 |
| SIZE | 64 | 32 |
| 精度 | ~15 位十进制 | ~7 位十进制 |
八、综合实战:高精度计算器
java
import java.math.BigDecimal;
import java.math.RoundingMode;
public class PreciseCalculator {
public static double add(double a, double b) {
return BigDecimal.valueOf(a).add(BigDecimal.valueOf(b)).doubleValue();
}
public static double subtract(double a, double b) {
return BigDecimal.valueOf(a).subtract(BigDecimal.valueOf(b)).doubleValue();
}
public static double multiply(double a, double b) {
return BigDecimal.valueOf(a).multiply(BigDecimal.valueOf(b)).doubleValue();
}
public static double divide(double a, double b, int scale) {
return BigDecimal.valueOf(a)
.divide(BigDecimal.valueOf(b), scale, RoundingMode.HALF_UP)
.doubleValue();
}
public static String formatScientific(double value, int decimals) {
if (Double.isNaN(value)) return "NaN";
if (Double.isInfinite(value))
return value > 0 ? "+Infinity" : "-Infinity";
return String.format("%." + decimals + "e", value);
}
public static void main(String[] args) {
System.out.println("0.1 + 0.2 = " + add(0.1, 0.2)); // 0.3
System.out.println("10 / 3 = " + divide(10, 3, 4)); // 3.3333
double huge = 1e308;
System.out.println("科学计数: " + formatScientific(huge, 2));
}
}
九、面试要点
| 问题 | 关键要点 |
|---|---|
| 为什么 0.1+0.2 != 0.3 | 0.1 和 0.2 在二进制中都是无限循环小数 |
| NaN 判断为何用 v != v | NaN 是唯一不等于自身的值 |
| -0.0 和 0.0 的区别 | 位表示不同,compare 中 -0.0 < 0.0 |
| doubleToLongBits vs doubleToRawLongBits | 前者规范化 NaN,后者保留原始位 |