原创

EasyExcel 读取数值精度丢失问题与解决方案

在使用 EasyExcel 读取 Excel 时,我们经常会遇到这样一个问题:

同一个单元格,Excel 界面显示 119178409,但在公式栏里显示的原始值却是 119178408.985065

而通过 EasyExcel 读取后,拿到的值始终是 119178409,即 Excel 的“显示值”,而不是底层存储的原始值。

这在财务、对账、数据精度要求较高的场景里,往往会造成严重的数据误差。

1. 现象复现

Excel 单元格内容:

  • 界面显示:119178409
  • 公式栏显示:119178408.985065

使用 EasyExcel 默认读取方式:

EasyExcel.read(fileBytes) .registerReadListener(new ReadListener<Map<Integer, String>>() { @Override public void invoke(Map<Integer, String> data, AnalysisContext context) { System.out.println(data); } @Override public void doAfterAllAnalysed(AnalysisContext context) {} }).sheet().doRead(); 

输出结果:

{0=119178409} 

可以看到,原始的 119178408.985065 被四舍五入成了 119178409

2. 原因分析

EasyExcel 的默认行为是:

  1. 根据泛型 Map<Integer, String>,使用内置的 StringConverter
  2. StringConverter 内部依赖 Apache POI 的 DataFormatter
  3. DataFormatter 返回的就是 Excel UI 显示值,而不是单元格底层的存储值。

所以,只要你用 Map<Integer, String> 或者 Java Bean 的 String 字段接收,就一定会丢失原始精度。

3. 解决方案

方案 A:使用 Map<Integer, CellData>(推荐)

直接拿 CellData,通过 getNumberValue() 获取 Excel 的原始值:

EasyExcel.read(fileBytes) .registerReadListener(new ReadListener<Map<Integer, CellData>>() { @Override public void invoke(Map<Integer, CellData> data, AnalysisContext context) { for (Map.Entry<Integer, CellData> entry : data.entrySet()) { CellData cellData = entry.getValue(); if (cellData.getType() == CellDataTypeEnum.NUMBER) { System.out.println("原始值 = " + cellData.getNumberValue().toPlainString()); } } } @Override public void doAfterAllAnalysed(AnalysisContext context) {} }).sheet().doRead(); 

输出结果:

原始值 = 119178408.985065 

方案 B:自定义 Converter 覆盖默认行为

如果你必须用 Map<Integer, String>,可以自定义一个 RawNumberStringConverter

public class RawNumberStringConverter implements Converter<String> { @Override public Class<?> supportJavaTypeKey() { return String.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.NUMBER; } @Override public String convertToJavaData(CellData cellData, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return cellData.getNumberValue().toPlainString(); } } 

注册时一定要放在前面,覆盖默认的:

EasyExcel.read(fileBytes) .registerConverter(new RawNumberStringConverter()) .registerReadListener(new ReadListener<Map<Integer, String>>() { @Override public void invoke(Map<Integer, String> data, AnalysisContext context) { System.out.println(data); } @Override public void doAfterAllAnalysed(AnalysisContext context) {} }).sheet().doRead(); 

这样 data 中拿到的就是 119178408.985065

4. 总结

  • EasyExcel 默认返回的是 Excel 显示值,而不是原始存储值。
  • 如果需要保证数值精度:
    • ✅ 推荐用 Map<Integer, CellData>,直接取 getNumberValue()
    • 或者写自定义 Converter 覆盖默认的 StringConverter
  • 在涉及财务、统计、对账等场景时,一定要小心这个坑,否则会引入 隐蔽的精度错误

⚠️ 经验教训:EasyExcel 在默认场景下非常方便,但在高精度数值处理场景里,一定要检查它是否返回了 原始值,否则可能踩坑。

正文到此结束
Loading...