Apache POI没有返回来自Excel的大数字的正确值

Jac*_*kie 3 apache-poi

我有一个值为6228480018362050000的excel文件导出的csv看起来像这样......

Int,Bigint,String
1,6228480018362050000,Very big
Run Code Online (Sandbox Code Playgroud)

当我尝试运行以下代码时......

InputStream inp = new FileInputStream("/.../test.xlsx");
DataFormatter df = new DataFormatter(true);
df.formatCellValue(WorkbookFactory.create(inp).getSheetAt(0).getRow(1).getCell(1));
Run Code Online (Sandbox Code Playgroud)

我得到的6228480018362049500是错误的数字,因为精确度是合理的.有没有办法获得实际价值?

Axe*_*ter 9

如果我们将长数字放入Excel单元格,那么这些数字将被截断为15位有效数字.这是因为Excel不知道像大整数这样的东西.它只有浮点来存储数值.并且遵循IEEE 754规范.但根据IEEE 754规范,某些数字不能存储为浮点数.在您的示例中,6228480018362050000,即6.22848001836205E + 018,无法存储.根据IEEE 754规范,它将是6.2284800183620495E + 018或6228480018362049500.

微软的知识库提到:"Excel遵循IEEE 754关于如何存储和计算浮点数的规范.因此Excel只存储一个数字中的15位有效数字,并将第15位后的数字更改为零."

这不是全部真相.实际上至少使用Office OpenXML(*.xlsx)它会根据IEEE 754规范存储值,不仅仅是15位有效数字.用你的例子来存储<v>6.2284800183620495E+18</v>.但那是次要的.因为即使它会存储6.22848001836205E + 018,某处必须将其重新转换为浮点数,然后再次为6.2284800183620495E + 18.打开工作簿时Excel也会这样做.它转换<v>6.2284800183620495E+18</v>为浮点数,然后它只显示15位有效数字.

因此,如果您确实需要将6228480018362050000存储为Excel中的数字,那么获得与Excel相同结果的唯一方法是使用与Excel相同的方法.为此,我们可以使用BigDecimal它的round方法,它能够使用MathContext具有设置精度的方法.

例:

import org.apache.poi.ss.usermodel.*;

import java.io.*;

import java.math.BigDecimal;
import java.math.MathContext;

class ReadExcelBigNumbers {

 public static void main(String[] args) throws Exception{

  for (int i = 0; i < 10; i++) {
   String v = "6.2284800183620" + i + "E+018";
   double d = Double.parseDouble(v);
   System.out.print(v + "\t");
   System.out.print(d + "\t");
   BigDecimal bd = new BigDecimal(d);
   v = bd.round(new MathContext(15)).toPlainString();
   System.out.println(v);
  }

  InputStream inp = new FileInputStream("test.xlsx");
  Workbook wb = WorkbookFactory.create(inp);
  for (int i = 1; i < 9; i++) {
   double d = wb.getSheetAt(0).getRow(i).getCell(1).getNumericCellValue();
   BigDecimal bd = new BigDecimal(d);
   String v = bd.round(new MathContext(15)).toPlainString();
   System.out.println(v);
  }
 }
}
Run Code Online (Sandbox Code Playgroud)

第一部分打印:

6.22848001836200E+018   6.2284800183620004E18   6228480018362000000
6.22848001836201E+018   6.2284800183620096E18   6228480018362010000
6.22848001836202E+018   6.2284800183620198E18   6228480018362020000
6.22848001836203E+018   6.2284800183620301E18   6228480018362030000
6.22848001836204E+018   6.2284800183620403E18   6228480018362040000
6.22848001836205E+018   6.2284800183620495E18   6228480018362050000
6.22848001836206E+018   6.2284800183620598E18   6228480018362060000
6.22848001836207E+018   6.22848001836207E18     6228480018362070000
6.22848001836208E+018   6.2284800183620803E18   6228480018362080000
6.22848001836209E+018   6.2284800183620905E18   6228480018362090000
Run Code Online (Sandbox Code Playgroud)

在那里你可以看到有用的浮点值,符合IEEE 754规范的实际浮点值和重新格式化的BigDecimal之间的区别.如您所见,只能直接根据IEEE 754规范存储6.22848001836207E + 018.

第二部分使用以下Excel工作表执行相同操作:

在此输入图像描述

知识库文章中提到了另一种可能的解决方法:"要解决此问题,请将单元格格式化为文本,然后键入数字.单元格最多可显示1,024个字符." 如果数字不是真正的数字,而是标识符,或者其他字符串,其中数字仅表示字符,这是很好的.如果没有将它们重新转换为浮动点,那么使用这种"文本号"的计算当然是不可能的,这将再次带来问题.