最近在做一個需求,數據來源是某些銀行流水;計算流水時,在轉美元的時候 因爲匯率極小,算的單筆交易額_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的取值 會有個匯率取值的小坑,此外幾千萬 真的一分不差都對上? 不現實的;這一部分的誤差 只能產品來找財務確認後,才能通過測試的呦。
交流技術 歡迎+ QQ\微信 153132336 zy
個人博客 https://blog.csdn.net/zyooooxie