一、數(shù)值存儲(chǔ)計(jì)算方案
高精度浮點(diǎn)數(shù)
采用雙精度浮點(diǎn)數(shù)是最簡(jiǎn)單的存儲(chǔ)方案,數(shù)據(jù)庫(kù)支持,編程語(yǔ)言也支持。只要數(shù)據(jù)庫(kù)和編程語(yǔ)言采用一樣的浮點(diǎn)數(shù)標(biāo)準(zhǔn)即可。
然而,浮點(diǎn)數(shù)始終有精度問(wèn)題,會(huì)產(chǎn)生很多人類大腦一般認(rèn)知以外的問(wèn)題。
字符串存儲(chǔ)
字符串存儲(chǔ)容易產(chǎn)生計(jì)算性能問(wèn)題,而且也大小的比較排序不方便。若轉(zhuǎn)化為浮點(diǎn)數(shù)計(jì)算,又回到前面問(wèn)題。如果是自己編寫加減乘除算法,復(fù)雜度更高。
采用長(zhǎng)整數(shù)
系統(tǒng)設(shè)置的最大金額為100,000,000,000。
64位有符號(hào)整數(shù),最大數(shù)((263)-1)/(107)=922,337,203,685.4775807,完全滿足最大值需求。
采用整數(shù)存儲(chǔ)和計(jì)算不會(huì)丟失精度,普通開發(fā)者可以輕松駕馭整數(shù)的計(jì)算,系統(tǒng)做比較排序操作也很方便。
二、采用四舍五入的情況
所謂四舍五入就是,當(dāng)舍去位的數(shù)值大于等于5時(shí),在舍去該位的同時(shí)向前位進(jìn)一;當(dāng)舍去位的數(shù)值小于5時(shí),則直接舍去該位。
小數(shù)保留七位轉(zhuǎn)化為整數(shù)
某些計(jì)算結(jié)果是小數(shù),需保留小數(shù)點(diǎn)后七位,并乘以10的七次方轉(zhuǎn)化為整數(shù),才能保存到系統(tǒng)中。
對(duì)于某些小數(shù),因?yàn)楦↑c(diǎn)數(shù)的精度問(wèn)題。需要小數(shù)點(diǎn)后7位后,四舍五入再乘以10的7次方取整。如下代碼所示
def mul_10_7(num: BigDecimal): BigInt = {
(num.setScale(7, RoundingMode.HALF_UP) * BigDecimal(BigInt(10).pow(7))).toBigInt()
}
而不是該小數(shù)乘以10的7次方直接取整。如下錯(cuò)誤代碼:
def mul_10_7(num: BigDecimal): BigInt = {
(num * BigDecimal(BigInt(10).pow(7))).toBigInt()
}
三、采用直接舍去的情況
頁(yè)面百分比展示
頁(yè)面展示百分比。當(dāng)進(jìn)度為99.9999999%這種情況時(shí),采用小數(shù)點(diǎn)后兩位四舍五入。頁(yè)面展示進(jìn)度時(shí),變?yōu)?00%。因此,頁(yè)面展示進(jìn)度改為直接舍去
(BigDecimal(inAmount) / BigDecimal(c.crowd_total) * 100).setScale(2, RoundingMode.DOWN)
而不是采取四舍五入。如下錯(cuò)誤代碼:
(BigDecimal(inAmount) / BigDecimal(c.crowd_total) * 100).setScale(2, RoundingMode.HALF_UP)
四、累積誤差的處理
分紅計(jì)算累積誤差
如果單位分紅計(jì)算時(shí),小數(shù)點(diǎn)后7位四舍五入。然后,按照每個(gè)賬戶持有份額乘以單位分紅獲得分紅。這樣會(huì)產(chǎn)生累積誤差。如下錯(cuò)誤代碼所示:
# 單位分紅計(jì)算,小數(shù)點(diǎn)7位后四舍五入
val unitPrice = (feeTotal / BigDecimal(xyzCount)).setScale(7, RoundingMode.HALF_UP)
# 賬戶持有份額乘以單位分紅
mul_10_7(div_10_7(ab.amount) * unitPrice)
正確思路是保留精度,計(jì)算乘積后再取整存儲(chǔ)
# 單位分紅計(jì)算,保留精度
val unitPrice = feeTotal / BigDecimal(xyzCount)
# 賬戶持有份額乘以單位分紅
mul_10_7(div_10_7(ab.amount) * unitPrice)
分紅計(jì)算改進(jìn)方案1
上述分紅計(jì)算方案,仍舊會(huì)產(chǎn)生問(wèn)題。會(huì)出現(xiàn)兩種錯(cuò)誤情況,一種是分紅池總金額大于實(shí)際分紅;另外一種情況是分紅金額小于實(shí)際分紅。這兩種情況都會(huì)導(dǎo)致系統(tǒng)總金額產(chǎn)生錯(cuò)誤。因此,需要用如下方案改進(jìn)。
假設(shè)有n個(gè)分紅賬戶,持有該份額為X1、X2、X3...Xn-1、Xn,總份額為:
X=X1+X2+...+Xn
總分紅額為total,那么前n-1位分紅計(jì)算公式如下:
Ti=Xi/X*total
i代表第i位分紅者,i<=n-1
最后一位分紅Tn,計(jì)算公式如下:
Tn=total-(T1+T2+...+Tn-1)
這樣保證分紅計(jì)算沒(méi)有錯(cuò)誤產(chǎn)生。
分紅計(jì)算進(jìn)一步改進(jìn)方案2
上述分紅計(jì)算方案,仍舊會(huì)產(chǎn)生問(wèn)題。會(huì)出現(xiàn)如下錯(cuò)誤情況:如果份額計(jì)算時(shí),小份額先分,大份額最后分,有可能出現(xiàn)大份額分不到或分少的情況。因此,需要用如下方案改進(jìn)。
假設(shè)有n個(gè)分紅賬戶,n個(gè)分紅賬戶需要按照降序的順序分紅,計(jì)算方案仍舊如方案1所述。
五、銀行家舍入
本系統(tǒng)并未采用這中計(jì)算方法。
銀行家舍入法,其實(shí)質(zhì)是一種四舍六入五取偶(又稱四舍六入五留雙)法。其規(guī)則是:當(dāng)舍去位的數(shù)值小于5時(shí),直接舍去該位;當(dāng)舍去位的數(shù)值大于等于6時(shí),在舍去該位的同時(shí)向前位進(jìn)一;當(dāng)舍去位的數(shù)值等于5時(shí),如果前位數(shù)值為奇,則在舍去該位的同時(shí)向前位進(jìn)一,如果前位數(shù)值為偶,則直接舍去該位。