创建BigDecimal触发Infinity NumberFormatException异常排查
异常根因
这个异常是传入currency方法的float参数为**无穷大(Infinity)或NaN(非数值)**导致的,这类值是float类型的合法取值,不会在方法调用前触发任何错误,只有执行到new BigDecimal(number)时才会抛出异常。
- float类型遵循IEEE 754浮点数标准,原生支持三个特殊值:正无穷(
Float.POSITIVE_INFINITY)、负无穷(Float.NEGATIVE_INFINITY)、非数值(Float.NaN)。这三个值本身是合法的float基础类型值,方法接收参数时不会做额外校验,自然不会提前抛出异常。只有BigDecimal的构造方法不接受这三个特殊值,会主动抛出NumberFormatException,报错信息就是你捕获到的Infinity or NaN。 - 你在
setCurrentPrice里写的前置判断if(currentPrice < 0.0001)根本拦不住这两个特殊值:- 按照浮点数运算规则,任何数值和NaN做<、>、==、<=、>=比较,结果一律为false,所以NaN传入时会直接走else分支调用
currency方法 - 正无穷比所有有限数值都大,
POSITIVE_INFINITY < 0.0001的结果也是false,同样会进入else分支传到currency方法里
- 按照浮点数运算规则,任何数值和NaN做<、>、==、<=、>=比较,结果一律为false,所以NaN传入时会直接走else分支调用
- 线上出现这类特殊值的常见原因:服务端接口返回了非法运算结果(比如除以0得到的无穷值),Gson默认会把字符串
"Infinity"解析为Float.POSITIVE_INFINITY;或者本地做价格计算时出现除以0、非法数学运算,结果直接赋值给了currentPrice字段。
修复方案
- 在
setCurrentPrice入口加特殊值校验,从源头拦截非法值,避免脏值入库:
public void setCurrentPrice(float currentPrice) { // 拦截NaN、无穷大这类非法值,按业务需求给默认值 if (Float.isNaN(currentPrice) || Float.isInfinite(currentPrice)) { currentPrice = 0f; } if(currentPrice < 0.0001) { priceName = "$" + NumberFormatter.roundToLastDecimalDigits(currentPrice, 3); } else { priceName = NumberFormatter.currency(currentPrice); } this.currentPrice = currentPrice; }
- 在
currency方法内部加兜底校验,同时修复原代码里的潜在索引越界问题:
public static String currency(float number) { // 兜底拦截非法浮点数,避免构造BigDecimal崩溃 if (Float.isNaN(number) || Float.isInfinite(number)) { return "$0.00"; // 可根据业务调整默认展示值,比如"--" } NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(); currencyFormatter.setCurrency(Currency.getInstance("USD")); String[] floatParts = new BigDecimal(number).toPlainString().split("\\."); if(number < 1 && floatParts.length == 2) { String decimalPortion = floatParts[1]; int numDecimalPlaces = 0; // 补长度判断,避免小数部分全为0时触发索引越界 while (numDecimalPlaces < decimalPortion.length() && decimalPortion.charAt(numDecimalPlaces) == '0') numDecimalPlaces++; currencyFormatter.setMaximumFractionDigits(numDecimalPlaces > 2 ? numDecimalPlaces + 2 : 2); } else { currencyFormatter.setMaximumFractionDigits(2); } return currencyFormatter.format(number); }
注意:原代码里遍历小数部分前置0的逻辑没有做长度边界判断,如果传入0.0000这类小数部分全为0的值,
charAt会访问到字符串外的索引,触发StringIndexOutOfBoundsException,上面的修复代码已经补上了这个判断。
内容的提问来源于stack exchange,提问作者AtomicallyBeyond
相关产品推荐
相关产品推荐

