poi框架导出excel写单元格遇到精度问题
背景:
java系统,MySql数据库,定义有些数据格式为Decimal(24,2),即最多整数22位,⼩数2位,或者Decimal(24,4),即最多整数20位,⼩数4位的数字。系统内部操作使⽤BigDecimal来记录和操作这样的数据,并⽆不妥,也不会丢失数据,但是当要将这样的数据导出的excel,问题出现了,为了便于⽤户使⽤excel对数据(可能是⾦额,数量)作分析,所以我们保证单元格写的还是数字。
以⾦额为例,虽然使⽤了formater:”#,##0.00”来格式化数字,使之显⽰为形如”52,441,314.20”这个样的字符串,但这个单元格的内部value还是”52441314.20”,还是数字。
系统中,调⽤poi框架写这样的⼀个数字的代码如下:
cell = ateCell(idx, CellType.NUMERIC);
cell.setCellValue(((BigDecimal) val).doubleValue());
cell.MoneyStyle());
第⼀⾏创建⼀个数字格式的单元格。
第⼆⾏是将系统内部的数据写到单元格中去。
第三⾏是设置单元格的格式引⽤(多说⼀句,⼀个excel对⼀种单元格格式尽量使⽤同⼀个单元格格式变量,即尽量复⽤单元格格式,因为存放单元格格式是需要代价的)。
我们所使⽤的POI3.15仅⽀持下⾯⼀些setValue的⽅式,毫⽆疑问,这⾥只能先将BigDecimal转换成double再存放进去。
然后,问题开始出现了,当要写⼊的val值出现形如”5244524452445244.13”这样的⼤数字(18,2),在系统允许范围内,内部计算使⽤BigDecimal也没问题。但是当想要导出到excel的时候,问题出现了,被写⼊的数据是:
5.244524452445240E15。
明显的丢失了精度。经过简单排查,很快发现了问题就是出现在了BigDecinal.doubleValue()这个⽅法上⾯。
BigDecimal5244524452445244.13
Double  5.244524452445244E15
正是Double的值成了科学计数法提醒了我,虽然double类型可以具有宽阔的表值空间:-1.7*10(-308)~1.7*10(308)。但是它能维持的精度可能没有这⾥需要的⾼。
double结构分析:
看⼀下double的格式,⼀个double数是64位,它分为三个区域:
bigdecimal取值范围
11152
符号位指数位尾数位
简单举⼀个例⼦,⼗进制数字9⼆进制是:1001,
⼗进制数字0.625的⼆进制是:0.101;
所以⼗进制数字9.625的⼗进制数是1001.101,也就是1.001101*2^3。double是移位存储(这个概念不清楚请⾃⾏百度)的,所以这个数字存放进double就是:
符号位指数位尾数位
<1000011010 0
<5251 0
因为移位计算后,科学计数法⾸位都是1,不⽤存,所以double 52位的尾数能表达出53位的精度。
范围
11bit的指数位的表达区间是-2^10到2^10
所以double的表值范围是:
~
这个数字范围是如此的⼤,已经够接近“近似⽆穷⼤”的实际使⽤范围了。估算⼀下:
≈ ≈
故double的表达范围是:
-~。
精度:
≈。
考虑到基数必然实在1到2之间,故double的实际精度就是E15~E16.也就是说,double最多可以表达的精度是⼗进制的15~16位,这个意思是说double⽤来表达⼗进制数,有效位数最多可以是15位或者16位。
结论
⽽这⾥我们的数字是”5244524452445244.13”,有效数字位为18尾,所以触发了末尾精度的丢失,转换成double之后,记录下的结果是:5.244524452445244E15,丢了两位精度,只留下了⾼位的16位有效数字。
后记
当然,我们可以直接向excel中写⼊String()的format后的字符串,但是这样丢失了“数字”这⼀单元格类型属性,在⽤户需要进⾏数据分析的时候,还是需要将其转化成数字类型,这样还是会受到有效数位的限制,依旧会丢失精度。和项⽬经理沟通之后,我们还是选择了忽略这种精度丢失问题Decimal(15,2)已经能表达万亿级别的⾦额,实际业务场景中,基本上不会出现需要导出这样⼤的⾦额的情况,权衡之下,还是情愿损失有效数位过长时候的精度,⽽保留导出的单元格是数字类型。

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。