源铸开发

源铸:数值处理问题

2018-10-19  本文已影响0人  十月里的男艺术家

一、数值存储计算方案

高精度浮点数

采用双精度浮点数是最简单的存储方案,数据库支持,编程语言也支持。只要数据库和编程语言采用一样的浮点数标准即可。

然而,浮点数始终有精度问题,会产生很多人类大脑一般认知以外的问题。

字符串存储

字符串存储容易产生计算性能问题,而且也大小的比较排序不方便。若转化为浮点数计算,又回到前面问题。如果是自己编写加减乘除算法,复杂度更高。

采用长整数

系统设置的最大金额为100,000,000,000。

64位有符号整数,最大数((263)-1)/(107)=922,337,203,685.4775807,完全满足最大值需求。

采用整数存储和计算不会丢失精度,普通开发者可以轻松驾驭整数的计算,系统做比较排序操作也很方便。

二、采用四舍五入的情况

所谓四舍五入就是,当舍去位的数值大于等于5时,在舍去该位的同时向前位进一;当舍去位的数值小于5时,则直接舍去该位。

小数保留七位转化为整数

某些计算结果是小数,需保留小数点后七位,并乘以10的七次方转化为整数,才能保存到系统中。

对于某些小数,因为浮点数的精度问题。需要小数点后7位后,四舍五入再乘以10的7次方取整。如下代码所示

  def mul_10_7(num: BigDecimal): BigInt = {
    (num.setScale(7, RoundingMode.HALF_UP) * BigDecimal(BigInt(10).pow(7))).toBigInt()
  }

而不是该小数乘以10的7次方直接取整。如下错误代码:

  def mul_10_7(num: BigDecimal): BigInt = {
    (num * BigDecimal(BigInt(10).pow(7))).toBigInt()
  }

三、采用直接舍去的情况

页面百分比展示

页面展示百分比。当进度为99.9999999%这种情况时,采用小数点后两位四舍五入。页面展示进度时,变为100%。因此,页面展示进度改为直接舍去

(BigDecimal(inAmount) / BigDecimal(c.crowd_total) * 100).setScale(2, RoundingMode.DOWN)

而不是采取四舍五入。如下错误代码:

(BigDecimal(inAmount) / BigDecimal(c.crowd_total) * 100).setScale(2, RoundingMode.HALF_UP)

四、累积误差的处理

分红计算累积误差

如果单位分红计算时,小数点后7位四舍五入。然后,按照每个账户持有份额乘以单位分红获得分红。这样会产生累积误差。如下错误代码所示:

# 单位分红计算,小数点7位后四舍五入
val unitPrice = (feeTotal / BigDecimal(xyzCount)).setScale(7, RoundingMode.HALF_UP)

# 账户持有份额乘以单位分红
mul_10_7(div_10_7(ab.amount) * unitPrice)

正确思路是保留精度,计算乘积后再取整存储

# 单位分红计算,保留精度
val unitPrice = feeTotal / BigDecimal(xyzCount)

# 账户持有份额乘以单位分红
mul_10_7(div_10_7(ab.amount) * unitPrice)

分红计算改进方案1

上述分红计算方案,仍旧会产生问题。会出现两种错误情况,一种是分红池总金额大于实际分红;另外一种情况是分红金额小于实际分红。这两种情况都会导致系统总金额产生错误。因此,需要用如下方案改进。

假设有n个分红账户,持有该份额为X1、X2、X3...Xn-1、Xn,总份额为:

X=X1+X2+...+Xn

总分红额为total,那么前n-1位分红计算公式如下:

Ti=Xi/X*total

i代表第i位分红者,i<=n-1

最后一位分红Tn,计算公式如下:

Tn=total-(T1+T2+...+Tn-1)

这样保证分红计算没有错误产生。

分红计算进一步改进方案2

上述分红计算方案,仍旧会产生问题。会出现如下错误情况:如果份额计算时,小份额先分,大份额最后分,有可能出现大份额分不到或分少的情况。因此,需要用如下方案改进。

假设有n个分红账户,n个分红账户需要按照降序的顺序分红,计算方案仍旧如方案1所述。

五、银行家舍入

本系统并未采用这中计算方法。

银行家舍入法,其实质是一种四舍六入五取偶(又称四舍六入五留双)法。其规则是:当舍去位的数值小于5时,直接舍去该位;当舍去位的数值大于等于6时,在舍去该位的同时向前位进一;当舍去位的数值等于5时,如果前位数值为奇,则在舍去该位的同时向前位进一,如果前位数值为偶,则直接舍去该位。

上一篇下一篇

猜你喜欢

热点阅读