2007-08-22

 

Python Cookbook 3.13 格式化十进制小数转换为货币形式

需求:

你需要做一些简单的金融计算,并希望将结果表示为欧洲货币形式.

讨论:

在新的decimal模块中,有一个修改过的moneyfmt函数,我们可以使用它来实现功能.
 import decimal
""" calculate Italian invoice taxes given a subtotal. """
def italformat(value, places=2, curr='EUR', sep='.', dp=',', pos='', neg='-',
overall=10):
""" Convert Decimal ``value'' to a money-formatted string.
places: required number of places after the decimal point
curr: optional currency symbol before the sign (may be blank)
sep: optional grouping separator (comma, period, or blank) every 3
dp: decimal point indicator (comma or period); only specify as
blank when places is zero
pos: optional sign for positive numbers: "+", space or blank
neg: optional sign for negative numbers: "-", "(", space or blank
overall: optional overall length of result, adds padding on the
left, between the currency symbol and digits
"""
q = decimal.Decimal((0, (1,), -places)) # 2 places --> '0.01'
sign, digits, exp = value.quantize(q).as_tuple( )
result = [ ]
digits = map(str, digits)
append, next = result.append, digits.pop
for i in range(places):
if digits:
append(next( ))
else:
append('0')
append(dp)
i = 0
while digits:
append(next( ))
i += 1
if i == 3 and digits:
i = 0
append(sep)
while len(result) < overall:
append(' ')
append(curr)
if sign: append(neg)
else: append(pos)
result.reverse( )
return ''.join(result)
# get the subtotal for use in calculations
def getsubtotal(subtin=None):
if subtin == None:
subtin = input("Enter the subtotal: ")
subtotal = decimal.Decimal(str(subtin))
print "\n subtotal: ", italformat(subtotal)
return subtotal
# specific Italian tax law functions
def cnpcalc(subtotal):
contrib = subtotal * decimal.Decimal('.02')
print "+ contributo integrativo 2%: ", italformat(contrib, curr='')
return contrib
def vatcalc(subtotal, cnp):
vat = (subtotal+cnp) * decimal.Decimal ('.20')
print "+ IVA 20%: ", italformat(vat, curr='')
return vat
def ritacalc(subtotal):
rit = subtotal * decimal.Decimal('.20')
print "-Ritenuta d'acconto 20%: ", italformat(rit, curr='')
return rit
def dototal(subtotal, cnp, iva=0, rit=0):
totl = (subtotal+cnp+iva)-rit
print " TOTALE: ", italformat(totl)
return totl
# overall calculations report
def invoicer(subtotal=None, context=None):
if context is None:
decimal.getcontext( ).rounding="ROUND_HALF_UP" # Euro rounding rules
else:
decimal.setcontext(context) # set to context arg
subtot = getsubtotal(subtotal)
contrib = cnpcalc(subtot)
dototal(subtot, contrib, vatcalc(subtot, contrib), ritacalc(subtot))
if _ _name_ _=='_ _main_ _':
print "Welcome to the invoice calculator"
tests = [100, 1000.00, "10000", 555.55]
print "Euro context"
for test in tests:
invoicer(test)
print "default context"
for test in tests:
invoicer(test, context= decimal.DefaultContext)
意大利的税务计算是非常复杂的,所以本节的例子也就比较长.本节的代码只适用于意大利的货品计价用户.我自己用手计算发现太累了,所以写了一个小的Python脚本来计算它.我使用decimal模块来实现这个例子,也是要说明当涉及金钱的计算的时候,千万不要使用float.
为什么说decimal最适合货币计算并不是立刻就能明白的.因为虽然计算很简单,可是对于结果的显示就不那么简单了.本节的italformat函数基于Hettinger 's moneyfmt ,那个在Python标准库中的decimal中有介绍.我进行了一些修改,为了获得自己希望的结果.一个明显的修改就是添加了overall参数,这个参数使用overall来构造一个数字,并在数字和货币符号间添加了空格.这样对输出能控制好格式.
注意我在计算小节的部分,使用了subtotal = decimal.Decimal(str(subtin)). 这样就能使用float计算我们需要的结果.
当然,如果你需要使用US $来做为货币符号,或者别的东西,改起来也是很简单的.比如,你要输出$,可以这样设置参数:
def USformat(value, places=2, curr='$', sep=',', dp='.', pos='', neg='-',
overall=10):
...
如果你可能要同时处理多种货币格式,你可以修改这个函数,让它接收一个字典为参数,或者使用别的方式来传递参数.理论上,你可以使用locale模块来处理和地区相关的问题,比如货币格式,但在实际中我很少使用locale,所以功能的具体实现就交给读者吧.
不同的国家有不同的保留小数的方式,decimal使用ROUND_HALF_EVEN 做为默认方式,然而,欧洲货币使用ROUND_HALF_UP,为了使用不同的规则,修改decimal的context,也许修改后的结果不会有什么变化,但是还是要小心这些细微的差别.
你也可以更彻底的修改context,比如自己写context,对于修改context,无论是使用getcontext来获得,还是使用setcontext(mycontexts),都会保存它的值,一直到线程中止.在产品中,一定要注意使用合适的context,尤其是保留小数的方法.因为不同的国家可能是不同的.

相关说明:

decimal.getcontext()
    Returns this thread's context.

    If this thread does not yet have a context, returns
    a new context and sets this thread's context.
    New contexts are copies of DefaultContext.

decimal.setcontext(context)
    Set this thread's context to context.

标签:


Comments: 发表评论



<< Home

This page is powered by Blogger. Isn't yours?