原创

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...