最近在做一个需求,数据来源是某些银行流水;计算流水时,在转美元的时候 因为汇率极小,算的单笔交易额_USD,有些误差,所以想写一篇关于数据精确度的分享。
流水的某些字段
【产品同学对于字段的取值标准是 四舍五入,保留2位小数;这样的方式,是不能、不应该有误差,该是多少就是多少】。
但我没做好 = =
先看下要说的字段 Amount和Amount_USD,数学关系是: Amount * 汇率 = Amount_USD
再看下汇率,印尼盾兑换美元的汇率为 0.00007098【2019-11-31当天】
我要做 80+银行账号某个月的所有数据的全校验(不仅这2字段),为了快速完成工作 (偷懒) ,我把这一部分的校验写为:
我把脚本计算的结果 和后台的数据 做了正负0.01的校验;
【这样做,实际是很不负责的行为】
我的理由:保留小数点后2位,要不就是此值本身的 小数点前2位,要不就是此值的后面值(此值本身的 小数点前2位+0.01);此外 计算机来做运算就有误差【二进制 、十进制】;
但这都不应该是理由的。
那就重新来看计算的整个过程吧。
浮点数
Python中 浮点数 运算的问题: 官方文档
不幸的是,大多数的十进制小数都不能精确地表示为二进制小数。这导致在大多数情况下,你输入的十进制浮点数都只能近似地以二进制浮点数形式储存在计算机中。
看个例子:
我们十进制算的结果,反而和电脑算出来的不符; 122.10 + 122.05 = 244.14999999999998?
这样的误差要怎么来处理呢?
可以把浮点数都同时精确到小数点某个位数来比较。
round()
round() 返回浮点数x的四舍五入值。
把上面的例子 精确到小数点后2位来看,用例就跑通了。
是不是感觉可以很轻松的解决浮点数的误差了?再看一例,
看结果是 Assert失败,round() 取值好像有些问题啊,多看一点,
看上图的某些浮点数 round()四舍五入,保留2位,和预期对不上啊。 100.20 + 2.695 =102.895 四舍五入,保留2位,肯定是102.90;100.20 + 2.615 =102.815 四舍五入,保留2位,肯定是102.82。100.20 + 2.645 = 102.845 四舍五入,保留2位,肯定是102.85;
又不准确了。那要怎么来处理呢?Python有啥库 可以进行十进制数学计算?
decimal模块
Decimal 表示的结果会保留尾部的零,并根据具有两个有效位的被乘数自动推出四个有效位。 Decimal 可以模拟手工运算来避免当二进制浮点数无法精确表示十进制数时会导致的问题。
Decimal 类能够执行对于二进制浮点数来说不适用的模运算和相等性检测:
讲实在的,就应该用Decimal来计算;
def decimal_check(self, data_list):
# Log.info(decimal.getcontext())
decimal.getcontext().rounding = "ROUND_HALF_UP" # 修改舍入方式为四舍五入
# decimal.getcontext().prec = 2 # 设置精度 反而全错了
for d in data_list:
data_0 = decimal.Decimal(str(d[0]))
data_1 = decimal.Decimal(str(d[1]))
data_2 = decimal.Decimal(str(d[2]))
c_2 = (data_0 * data_1).quantize(decimal.Decimal('0.00'))
# Log.info(d)
# Log.info((data_0 * data_1))
# Log.info(c_2)
# Log.info(data_2)
assert c_2 == data_2
data_list里的元素 是(Amount,汇率,Amount_USD);
看下实际跑的结果:【当然我偷懒的校验方式也是通过的;几条用例 都是我找了些数据量在1w-10w条的】
其实原本时想再加一个 总额的字段误差,因为涉及到公司真实数据,就不能分享了;大意是这样的:某月的期初 + 当月交易净值 = 当月的期末(本币肯定没毛病),USD的取值 会有个汇率取值的小坑,此外几千万 真的一分不差都对上? 不现实的;这一部分的误差 只能产品来找财务确认后,才能通过测试的呦。