Python結算開發中遇到的坑
坑1:浮點數不精確性
In [1]: 0.1+0.1+0.1-0.3
Out[1]: 5.551115123125783e-17
解決辦法:
In [2]: from decimal import Decimal
In [3]: Decimal('0.1') + Decimal('0.1') + Decimal('0.1') - Decimal('0.3')
Out[3]: Decimal('0.0')
坑2:Decimal使用問題
In [5]: Decimal(0.1) + Decimal(0.1) + Decimal(0.1) - Decimal(0.3)
Out[5]: Decimal('2.775557561565156540423631668E-17')
解決辦法:
參照坑1的解決辦法,Decimal傳入值需要str類型
更多用法查看: https://docs.python.org/2/library/decimal.html
坑3:四舍五入不準確問題
In [3]: '{:.2f}'.format(Decimal(str(1001.8250)))
Out[3]: '1001.82'
In [2]: Decimal('1001.8250').quantize(Decimal('0.01'))
Out[2]: Decimal('1001.82')
In [4]: round(2.55, 1)
Out[4]: 2.5
解決辦法:
發現問題原因為在不能正確四舍五入的float數值中都是因為數據存儲末位的.5被存儲為.4999999…的形式,解決辦法是在.5上加.1的值。
def exact_round(num, exp=2):
"""
準確的四舍五入方法
:param num: 數值
:param exp: 保留精度
:return:
"""
str_num = str(num)
dec_num = Decimal(str_num)
exp_unit = Decimal('0.1') ** exp
mini_unit = Decimal('0.1') ** (exp + 1)
if dec_num % exp_unit == (5 * mini_unit):
dec_num += mini_unit
return Decimal(dec_num).quantize(exp_unit, rounding=ROUND_HALF_EVEN)
為了驗證這個方法寫了個測試腳本:
#!/usr/bin/python
-- coding: utf-8 --
"""
摘 要: exact_round.py
創 建 者: abc
創建日期: 2017-01-05
"""
author = "abc"
from decimal import Decimal, ROUND_HALF_EVEN
def exact_round(num, exp=2):
"""
準確的四舍五入方法
:param num: 數值
:param exp: 保留精度
:return:
"""
str_num = str(num)
dec_num = Decimal(str_num)
exp_unit = Decimal('0.1') exp
mini_unit = Decimal('0.1') (exp + 1)
raw_num = dec_num
if dec_num % exp_unit == (5 * mini_unit):
dec_num += mini_unit
raw_result = Decimal(raw_num).quantize(exp_unit, rounding=ROUND_HALF_EVEN)
result = Decimal(dec_num).quantize(exp_unit, rounding=ROUND_HALF_EVEN)
if result != raw_result:
print "raw:round({}, {}) = > {}; fixed: round({}, {}) => {}".format(
raw_num, exp, raw_result,
dec_num, exp, result
)
return result
if name == "main":
val = 900.0000
while val < 1001.8600:
for exp in range(0, 6):
exact_round(val, exp=exp)
val += 0.0005
</code></pre>
腳本中我們將被修正過的數據打印出來,發現被打印出來的都是四舍五入不正確的數值,經過方法處理可以保證準確的輸出。
因為我們的測試只是覆蓋了部分的數值,精度深度也只到到了6位,也不能保證說方法沒有問題。
后來詢問了在銀行做開發的朋友,他們對于數據的計算都是在數據庫的存儲過程中運算的,并對上面坑中的數值放到數據庫中做四舍五入發現確實沒有問題。
于是我將這個方法做的運算與數據庫的運算結果做對比寫了測試腳本。
#!/usr/bin/python
-- coding: utf-8 --
"""
摘 要: test_db_round.py
創 建 者: abc
創建日期: 2017-01-06
"""
author = "abc"
import os
import sys
sys.path.append(os.path.dirname(os.path.split(os.path.realpath(file))[0]))
from lib.utils import exact_round
from model import Model
def test_db_round(val, exp):
"""
test_db_round
:return:
"""
sql = "SELECT round({}, {}) as val".format(str(val), exp)
db_round = Model().raw(sql)[0]["val"]
exa_round = exact_round(val, exp)
if str(db_round) != str(exa_round):
print "db:{}; ex:{}".format(str(db_round), str(exa_round))
if name == "main":
val = 900.0000
while val < 1001.8600:
for exp in range(0, 6):
test_db_round(val, exp=exp)
val += 0.0005
</code></pre>
經過測試后發現沒有數據被打印出,證明在測試范圍內Python方法和數據庫的運算結果沒有差異。
關于浮點數不精確性的事情相信學過計算機組成原理這門課程的都明白,這里不再贅述,放個鏈接:
話說為什么要在Python中做財務相關運算呢,可能最初開發這個系統的人缺乏這方面的經驗,然后通過擴展精度保留位數來解決這個問題的,但終究在做四舍五入時可能產生1分的差異。
既然發現這個問題,本著眼里不揉沙子的態度,快速的解決方案是在Python中替換原來的四舍五入函數,長期策略是逐步將計算過程挪到數據庫通過存儲過程來實現。
來自:http://noogel.xyz/2017/03/02/1.html