核心原因:
二进制无法精确表示某些十进制小数
计算机使用 IEEE 754 标准的二进制浮点数存储,但不是所有十进制小数都能被精确转换为二进制。
会出现精度问题的数字:

能精确表示的小数特征:
一个小数能被 IEEE 754 double 精确表示,当且仅当它可以表示为:
m / 2^n (其中 m 和 n 都是整数)
示例:
0.5 = 1/2 = 1/2^1 ✅ 可以精确表示
0.25 = 1/4 = 1/2^2 ✅ 可以精确表示
0.75 = 3/4 = 3/2^2 ✅ 可以精确表示
5.5 = 11/2 = 11/2^1 ✅ 可以精确表示
0.1 = 1/10 ❌ 分母含有因子5,无法精确表示
0.2 = 1/5 ❌ 分母含有因子5,无法精确表示
1.1 = 11/10 ❌ 分母含有因子5,无法精确表示
2.2 = 22/10 = 11/5 ❌ 分母含有因子5,无法精确表示
3.3 = 33/10 ❌ 分母含有因子5,无法精确表示
为什么 POI 4.1.2 有时"隐藏"了这个问题?
POI 的 cell.setCellType(CellType.STRING) 方法会对数值进行隐式的格式化 ,它会:
检查数值是否"接近 "某个简单的小数
如果是,就输出那个简单的小数
但如果误差超过某个阈值 ,就会暴露完整精度
这就是为什么:
有些 1.1 显示为 "1.1"(POI 做了四舍五入)
有些 1.1 显示为 "1.1000000000000001"(POI 没有做四舍五入)
这种行为是不确定的,依赖于 :
POI 版本
Excel 文件 的创建方式
单元格的历史操作(是否经过公式计算、复制粘贴等)
测试:
java
public class FloatingPointTest {
public static void main(String[] args) {
double[] values = {1, 1.1, 2, 2.2, 3, 3.3, 4, 5, 5.5, 6, 6.6, 7, 8, 8.8, 9, 9.9};
System.out.println("=== 浮点数精度测试 ===\n");
for (double value : values) {
String strValue = String.valueOf(value);
BigDecimal bd = BigDecimal.valueOf(value);
String preciseStr = bd.toPlainString();
boolean hasPrecisionIssue = !strValue.equals(preciseStr);
System.out.printf("值: %4.1f | String.valueOf: %20s | BigDecimal: %20s | %s%n",
value, strValue, preciseStr,
hasPrecisionIssue ? "❌ 有精度问题" : "✅ 正常");
}
// 测试累加计算
System.out.println("\n=== 累加计算精度测试 ===\n");
testAddition("1.1", "2.2");
testAddition("1.1", "3.3");
testAddition("1.1", "4.4");
testAddition("1.1", "5.5");
testAddition("1.1", "6.6");
testAddition("1.1", "7.7");
testAddition("5.5", "3.3");
testAddition("8.8", "1.1");
}
private static void testAddition(String a, String b) {
// 错误的方式:使用 Double
double sumDouble = Double.parseDouble(a) + Double.parseDouble(b);
// 正确的方式:使用 BigDecimal
BigDecimal sumBD = new BigDecimal(a).add(new BigDecimal(b));
System.out.printf("%s + %s = %s (Double) vs %s (BigDecimal)%n",
a, b, sumDouble, sumBD.setScale(2, RoundingMode.HALF_UP));
}
}
输出:
java
=== 浮点数精度测试 ===
值: 1.0 | String.valueOf: 1.0 | BigDecimal: 1.0 | ✅ 正常
值: 1.1 | String.valueOf: 1.1 | BigDecimal: 1.1 | ✅ 正常
值: 2.0 | String.valueOf: 2.0 | BigDecimal: 2.0 | ✅ 正常
值: 2.2 | String.valueOf: 2.2 | BigDecimal: 2.2 | ✅ 正常
值: 3.0 | String.valueOf: 3.0 | BigDecimal: 3.0 | ✅ 正常
值: 3.3 | String.valueOf: 3.3 | BigDecimal: 3.3 | ✅ 正常
值: 4.0 | String.valueOf: 4.0 | BigDecimal: 4.0 | ✅ 正常
值: 5.0 | String.valueOf: 5.0 | BigDecimal: 5.0 | ✅ 正常
值: 5.5 | String.valueOf: 5.5 | BigDecimal: 5.5 | ✅ 正常
值: 6.0 | String.valueOf: 6.0 | BigDecimal: 6.0 | ✅ 正常
值: 6.6 | String.valueOf: 6.6 | BigDecimal: 6.6 | ✅ 正常
值: 7.0 | String.valueOf: 7.0 | BigDecimal: 7.0 | ✅ 正常
值: 8.0 | String.valueOf: 8.0 | BigDecimal: 8.0 | ✅ 正常
值: 8.8 | String.valueOf: 8.8 | BigDecimal: 8.8 | ✅ 正常
值: 9.0 | String.valueOf: 9.0 | BigDecimal: 9.0 | ✅ 正常
值: 9.9 | String.valueOf: 9.9 | BigDecimal: 9.9 | ✅ 正常
=== 累加计算精度测试 ===
1.1 + 2.2 = 3.3000000000000003 (Double) vs 3.30 (BigDecimal)
1.1 + 3.3 = 4.4 (Double) vs 4.40 (BigDecimal)
1.1 + 4.4 = 5.5 (Double) vs 5.50 (BigDecimal)
1.1 + 5.5 = 6.6 (Double) vs 6.60 (BigDecimal)
1.1 + 6.6 = 7.699999999999999 (Double) vs 7.70 (BigDecimal)
1.1 + 7.7 = 8.8 (Double) vs 8.80 (BigDecimal)
5.5 + 3.3 = 8.8 (Double) vs 8.80 (BigDecimal)
8.8 + 1.1 = 9.9 (Double) vs 9.90 (BigDecimal)
Excel导入精度损失可能的原因:
1. Excel 文件的创建方式不同
如果客户环境的 Excel 文件是:
从其他系统导出 (如 SAP、Oracle)
通过程序生成 (如 Apache POI、EPPlus)
经过多次复制粘贴
这些操作会影响单元格内部的存储格式标记,导致 POI 采用不同的转换策略。
2. Excel 版本差异
不同版本的 Excel (2010、2013、2016、2019、365)在保存 .xlsx 文件时,对数值的序列化方式略有不同。
3. 单元格格式设置
即使看起来都是"常规"格式,内部可能有细微差别:
曾经设置为"数值"格式 ,后改回"常规"
应用过条件格式
使用过数据验证
解决方法:
java
/**
* 将单元格内容转为字符串,如果null则返回空串
*
* @param cell Excel单元格
* @return 字符串值
*/
public static String getStringValue(Cell cell) {
if (cell == null) {
return "";
}
switch (cell.getCellType()) {
case STRING:
return cell.getStringCellValue().trim();
case NUMERIC:
// ✅ 日期类型特殊处理
if (DateUtil.isCellDateFormatted(cell)) {
return cell.getDateCellValue().toString();
} else {
// ✅ 数值类型:使用 BigDecimal 精确转换
double numericValue = cell.getNumericCellValue();
// 如果是整数,不显示小数点
if (numericValue == Math.floor(numericValue) && !Double.isInfinite(numericValue)) {
return String.valueOf((long) numericValue);
} else {
// 使用 BigDecimal 避免精度问题
// stripTrailingZeros() 去除末尾的0
// toPlainString() 避免科学计数法
return BigDecimal.valueOf(numericValue)
.stripTrailingZeros()
.toPlainString();
}
}
case BOOLEAN:
return String.valueOf(cell.getBooleanCellValue());
case FORMULA:
// ✅ 公式类型:尝试获取字符串值,失败则按数值处理
try {
return cell.getStringCellValue();
} catch (Exception e) {
double formulaValue = cell.getNumericCellValue();
if (formulaValue == Math.floor(formulaValue) && !Double.isInfinite(formulaValue)) {
return String.valueOf((long) formulaValue);
} else {
return BigDecimal.valueOf(formulaValue)
.stripTrailingZeros()
.toPlainString();
}
}
default:
return "";
}
}