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 = row.createCell(idx, CellType.NUMERIC);
cell.setCellValue(((BigDecimal) val).doubleValue());
cell.setCellStyle(cellStyles.getMoneyStyle());

第一行創建一個數字格式的單元格。
第二行是將系統內部的數據寫到單元格中去。
第三行是設置單元格的格式引用(多說一句,一個excel對一種單元格格式儘量使用同一個單元格格式變量,即儘量複用單元格格式,因爲存放單元格格式是需要代價的)。
我們所使用的POI3.15僅支持下面一些setValue的方式,毫無疑問,這裏只能先將BigDecimal轉換成double再存放進去。
然後,問題開始出現了,當要寫入的val值出現形如”5244524452445244.13”這樣的大數字(18,2),在系統允許範圍內,內部計算使用BigDecimal也沒問題。但是當想要導出到excel的時候,問題出現了,被寫入的數據是:
5.244524452445240E15
明顯的丟失了精度。經過簡單排查,很快發現了問題就是出現在了BigDecinal.doubleValue()這個方法上面。

BigDecimal 5244524452445244.13
Double 5.244524452445244E15

正是Double的值成了科學計數法提醒了我,雖然double類型可以具有寬闊的表值空間:-1.7*10(-308)~1.7*10(308)。但是它能維持的精度可能沒有這裏需要的高。

double結構分析:

看一下double的格式,一個double數是64位,它分爲三個區域:

1 11 52
符號位 指數位 尾數位

簡單舉一個例子,十進制數字9二進制是:1001,
十進制數字0.625的二進制是:0.101;
所以十進制數字9.625的十進制數是1001.101,也就是1.001101*2^3。double是移位存儲(這個概念不清楚請自行百度)的,所以這個數字存放進double就是:

符號位 指數位 尾數位
0 1…100 0011010…0
63 62…52 51…0

因爲移位計算後,科學計數法首位都是1,不用存,所以double 52位的尾數能表達出53位的精度。

範圍

11bit的指數位的表達區間是-2^10到2^10
所以double的表值範圍是:
2(210) ~2(210)
這個數字範圍是如此的大,已經夠接近“近似無窮大”的實際使用範圍了。估算一下:
2(210)=21024=(2256)4(1.15791077)41.79810308
故double的表達範圍是:
-1.79810308 ~1.79810308

精度:

25391015
考慮到基數必然實在1到2之間,故double的實際精度就是E15~E16.也就是說,double最多可以表達的精度是十進制的15~16位,這個意思是說double用來表達十進制數,有效位數最多可以是15位或者16位。

結論

而這裏我們的數字是”5244524452445244.13”,有效數字位爲18尾,所以觸發了末尾精度的丟失,轉換成double之後,記錄下的結果是:5.244524452445244E15,丟了兩位精度,只留下了高位的16位有效數字。

後記

當然,我們可以直接向excel中寫入BigDecimal.toString()的format後的字符串,但是這樣丟失了“數字”這一單元格類型屬性,在用戶需要進行數據分析的時候,還是需要將其轉化成數字類型,這樣還是會受到有效數位的限制,依舊會丟失精度。和項目經理溝通之後,我們還是選擇了忽略這種精度丟失問題Decimal(15,2)已經能表達萬億級別的金額,實際業務場景中,基本上不會出現需要導出這樣大的金額的情況,權衡之下,還是情願損失有效數位過長時候的精度,而保留導出的單元格是數字類型。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章