单精度浮点型 float
本章定位:float是32位IEEE 754单精度浮点数,有效数字约7位十进制数。在飞翔科技的电商和工资系统中,商品价格188.88f、基本工资8888.00f、气温26.5f等都用float表示。白歌提醒小崔:"浮点数天生不精确,算钱记得用BigDecimal——但面试考float,你得把IEEE 754的存储格式讲清楚。"
定义与语法
| 属性 | 说明 |
|---|---|
| 关键字 | float(单精度浮点型) |
| 位数 | 32 bits(4字节) |
| 标准 | IEEE 754 单精度浮点数 |
| 有效数字 | 约6~7位十进制数字 |
| 取值范围(正数) | 约 1.4×10⁻⁴⁵ ~ 3.4×10³⁸ |
| 默认值 | 0.0f |
| 包装类 | java.lang.Float |
| 字面量后缀 | 必须加 f 或 F(浮点字面量默认是double) |
基本声明
float price = 188.88f; // 商品单价
float discount = 88.8f; // 折扣价
float shippingFee = 8.8f; // 运费
float basicSalary = 8888.00f; // 基本工资
float morningTemp = 26.5f; // 早晨气温
float bodyTemp = 36.5f; // 体温
float ticketPrice = 100.00f; // 门票价格
float drugPrice = 23.20f; // 药品价格
深度原理:IEEE 754 单精度的二进制存储
float的32位布局
计算公式:
value = (-1)^S × (1.M) × 2^(E-127)
特殊情况:
- E=0, M=0 → ±0
- E=0, M≠0 → 非规格化数(subnormal)
- E=255, M=0 → ±∞(无穷大)
- E=255, M≠0 → NaN(Not a Number)
示例:188.88f的实际存储
188.88 的二进制转换过程:
1. 188的二进制:10111100
2. 0.88的二进制:0.11100001010001111010111...(无限循环!)
188.88 ≈ 1.0111100011100001010001111 × 2^7
符号位 S = 0(正数)
指数位 E = 7 + 127 = 134 = 10000110
尾数位 M = 01111000111000010100011(取前23位)
最终 32 位:0 10000110 01111000111000010100011
关键认知:0.88无法用二进制精确表示,就像1/3无法用十进制精确表示。float存储的是近似值,这就是浮点数精度问题的数学根源。
完整代码示例
示例一:飞翔商城购物结算
/**
* 飞翔科技 - float 类型演示
* 场景:飞翔商城购物车结算,使用float表示价格
*/
public class FloatShoppingDemo {
public static void main(String[] args) {
// 商品信息
float price = 188.88f; // 单价
float discount = 88.8f; // 折扣价
float shippingFee = 8.8f; // 运费
int quantity = 2; // 购买数量(int)
// 计算总金额
float subtotal = price * quantity;
float totalAmount = subtotal + shippingFee;
// 余额
float accountBalance = 6666.66f;
float afterPayment = accountBalance - totalAmount;
System.out.println("【飞翔商城 - 购物结算】");
System.out.println("单价:¥" + price);
System.out.println("数量:" + quantity + "件");
System.out.println("小计:¥" + subtotal);
System.out.println("运费:¥" + shippingFee);
System.out.println("实付:¥" + totalAmount);
System.out.println("账户余额(支付前):¥" + accountBalance);
System.out.println("账户余额(支付后):¥" + afterPayment);
System.out.println("\n【精度观察】");
System.out.println("188.88 × 2 = " + (188.88f * 2)); // 结果可能不是精确的377.76
}
}
运行输出:
【飞翔商城 - 购物结算】
单价:¥188.88
数量:2件
小计:¥377.76
运费:¥8.8
实付:¥386.56
账户余额(支付前):¥6666.66
账户余额(支付后):¥6280.1006
【精度观察】
188.88 × 2 = 377.76
注意:
6280.1006并非精确值,而是二进制近似值的最接近十进制表示。这就是float的精度限制。
示例二:float的特殊值
/**
* 飞翔科技 - float 特殊值演示
* 场景:演示无穷大、NaN、正负零等特殊值
*/
public class FloatSpecialDemo {
public static void main(String[] args) {
// 无穷大
float positiveInfinity = 1.0f / 0.0f;
float negativeInfinity = -1.0f / 0.0f;
// NaN(Not a Number)
float nan1 = 0.0f / 0.0f;
float nan2 = (float) Math.sqrt(-1.0);
// 正负零
float positiveZero = 0.0f;
float negativeZero = -0.0f;
// Float 常量
System.out.println("【float 特殊值】");
System.out.println("正无穷:" + positiveInfinity + " (等于Float.POSITIVE_INFINITY:"
+ (positiveInfinity == Float.POSITIVE_INFINITY) + ")");
System.out.println("负无穷:" + negativeInfinity);
System.out.println("NaN(0/0):" + nan1);
System.out.println("NaN(√-1):" + nan2);
System.out.println("NaN == NaN:" + (nan1 == nan2)); // NaN不等于任何值,包括自己!
System.out.println("NaN isNaN:" + Float.isNaN(nan1));
System.out.println("\n正零 == 负零:" + (positiveZero == negativeZero)); // true
System.out.println("1.0/正零 = " + (1.0f / positiveZero)); // Infinity
System.out.println("1.0/负零 = " + (1.0f / negativeZero)); // -Infinity
System.out.println("\n【float 范围常量】");
System.out.println("最小值(正数): " + Float.MIN_VALUE); // 1.4E-45
System.out.println("最大值: " + Float.MAX_VALUE); // 3.4028235E38
System.out.println("最小规格化正数: " + Float.MIN_NORMAL); // 1.17549435E-38
}
}
运行输出:
【float 特殊值】
正无穷:Infinity (等于Float.POSITIVE_INFINITY:true)
负无穷:-Infinity
NaN(0/0):NaN
NaN(√-1):NaN
NaN == NaN:false
NaN isNaN:true
正零 == 负零:true
1.0/正零 = Infinity
1.0/负零 = -Infinity
【float 范围常量】
最小值(正数): 1.4E-45
最大值: 3.4028235E38
最小规格化正数: 1.17549435E-38
易错场景
反例一:忘记f后缀——float最经典的编译错误
// ❌ 错误:浮点字面量默认是 double,不能直接赋给 float
float price = 188.88; // 编译错误:incompatible types: possible lossy conversion from double to float
// ✅ 正确:加 f/F 后缀
float price = 188.88f;
float price2 = 188.88F; // 大写也可以
反例二:用float做精确的金额计算
// ❌ 危险:float 累加会累积精度误差
float total = 0.0f;
for (int i = 0; i < 1000; i++) {
total += 0.1f; // 期望100.0,实际是 99.99905 左右
}
System.out.println(total); // 输出 99.99905(不是精确的100)
// ✅ 正确:金融计算使用 BigDecimal
import java.math.BigDecimal;
BigDecimal exactTotal = BigDecimal.ZERO;
for (int i = 0; i < 1000; i++) {
exactTotal = exactTotal.add(new BigDecimal("0.1"));
}
System.out.println(exactTotal); // 输出 100.0(精确)
反例三:直接用==比较float
// ❌ 错误:直接 == 比较浮点数
float a = 0.1f + 0.2f;
float b = 0.3f;
System.out.println(a == b); // 输出 false!(期望true)
// ✅ 正确:用误差范围比较
float epsilon = 0.000001f;
boolean isEqual = Math.abs(a - b) < epsilon;
System.out.println(isEqual); // 输出 true
适用场景与选择指南
| 场景 | 类型选择 | 原因 |
|---|---|---|
| 商品价格/工资(演示) | float | 教学演示,实际项目用BigDecimal |
| 气温/体温 | float | 精度要求不高 |
| 3D游戏坐标 | float | GPU运算默认float,省内存 |
| 传感器数据 | float | 原始精度有限 |
| 科学计算 | double | 需要更高精度 |
| 金融计算 | BigDecimal | 需要精确十进制 |
面试考点
Q1:float的f后缀为什么不能省略?
Java中浮点数字面量(如
3.14)默认是double类型(64位)。double→float是窄化转换(narrowing),可能有精度损失,编译器不允许隐式进行。必须显式加f后缀告诉编译器"我知道这是float"。
Q2:float f = 1.0f / 0.0f; 会抛异常吗?
不会。IEEE 754标准定义了浮点除零的行为:正数÷0 =
Infinity,负数÷0 =-Infinity,0÷0 =NaN。这与整数除零(会抛出ArithmeticException)完全不同。因此浮点运算不需要try-catch除零异常。
Q3:为什么0.1f + 0.2f ≠ 0.3f?
0.1和0.2在二进制中都是无限循环小数,float只能存储23位尾数的近似值。两个近似值相加得到的近似值,与0.3的近似值不相等。这不是Java的bug,而是所有遵循IEEE 754标准的语言(C、Python、JavaScript等)都有的现象。
Q4:float和double在JVM的原子性方面有什么区别?
与long类似,32位JVM上非volatile的float和double的64位读写不保证原子性(float是32位,通常原子;double是64位,不保证)。但在64位JVM的HotSpot实现中,对float的读写通常是原子的。规范建议:多线程共享的double变量应声明为volatile或使用
AtomicLong的Double.longBitsToDouble方式。