1. 场景描述
跨界一词不知道什么时候成为了热词。电影演员学说相声,跨界表演展现多才多艺的才能;县长直播带货,推荐地方土特产,展现即兴表演才能;退役核潜艇艇长担任银行电脑处处长,这个跨界可是有点大。然而,某银行招聘支行柜台员工却火了,吸引上百人报名应聘,其中不乏本科毕业生、硕士研究生,在专业分布上居然还有学习爆炸专业、核物理专业和火箭推进专业的硕士毕业生也来应聘银行柜员,这波操作更是把专业跨界推到极致,这种突兀的专业跨界背后反映出就业形势严峻性。不可否认,随着金融业的快速发展,银行从业人员的高收入和高福利也是吸引大学生毕业生就业的非常重要的原因。在许多人的记忆中,80年代高中生就可以进银行当柜员, 90年代要求大专生以上,2000年之后要求本科生起步,现在很多银行要求211和985的本科毕业生,硕士研究生优先,银行招聘门槛也是水涨船高。 由于银行电子化普及和应用,从前银行柜员大多数工作内容都固化到银行柜台计算机交易系统中,换句话说现在银行柜台员工的工作都实现了标准化和流程化,因而降低了柜台员工综合技能要求。但是要求柜员有高度的责任感、制度和流程的执行力以及良好的沟通能力。我个人认为对硕士、甚至博士生来说,长期从事银行柜员工作有些屈才,大材小用。更为可惜的是4年以上大学专业知识的学习,不能做到人尽其才,也是人才资源的巨大浪费。我们下面来一睹银行柜员日终扎帐场景中的具体内容和流程。 银行网点每日对外营业结束后,柜员就要开始进行日终账务核算处理,这个过程也叫做扎帐。现金清分则是日终扎帐中非常重要的环节和步骤,柜员首先要对钱箱中的现金进行清点,统计每一种货币面值的现金数量,据此计算出柜员钱箱中的现金总额;然后与银行计算机系统中柜员现金尾箱记账金额进行核对,要求做到账实相符。也就是说钱箱现金合计金额要与计算机系统中的记账金额相等。如果两者金额不相等,说明业务办理中出现了差错,需要查找差错原因,直到账实相符,银行方能关门,表示当日账务处理准确无误。 下面我们就来模拟银行柜员的现金清分场景,统计柜员钱箱中各种面值现金数量,计算出现金钱箱的现金总额。这好像听起来业务需求并不复杂,但是事实或许恰恰相反,让我们一起进入下一节,详细了解编程思路。
2. 编程思路
2.1 货币表示
这是我们需要首先解决的问题。计算机中的浮点数是表示近似值,本质上讲凡是浮点数参与的运算处理均有可能造成误差。因此它不适合于金融、会计等专业领域的货币运算处理,因为解决这些问题要求计算结果精准无误。下面的代码展示了浮点数运算产生的误差:
C:\>python
Python 3.8.0 (tags/v3.8.0:fa919fd, Oct 14 2019, 19:37:50) [MSC v.1916 64 bit (AMD64)] on win32
Type "help", "copyright", "credits" or "license" for more information.
>>> x = 0.1
>>> y = 0.2
>>>
>>> print(x+y)
0.30000000000000004
>>>
>>> print(x-y)
-0.1
>>>
>>> print(x*y)
0.020000000000000004
>>>
>>> print(x/y)
0.5
>>>
上面的例子我们可以看到print(x+y)的表达式计算结果和打印内容产生的误差,同样print(x*y)表达式中的乘法运算也出现了误差,这与我们期待的运算结果不同,出现了偏差,尽管这个误差很小。这是不是只有Python语言才出现了这个问题呢?答案是否定的,同样地我们在C/C++语言环境下进行了类似的测试,实验表明上述问题同样存在。就其根本的原因,还是在于浮点数在计算机中的表示,它永远只能表示一个近似值。它不适合金融和会计等领域要求精准计算的应用场景。为了解决本案中的具体问题,一种取而代之的解决方案是使用整数来表示货币,整数在计算机中的运算将产生精准的计算结果,而不会产生任何计算误差。
2.2 币值分类
这里我们仅仅模拟银行网点本币(人民币)现金清分场景。按照本币的币值面额进行分类:100元、50元、20元、10元、5元、2元、1元、5角、2角、1角、5分、2分、和1分。 我们可以使用Python中的字典,定义货币及含义。 currency = {10000: '100元', 5000: '50元', 2000: '20元', 1000: '10元', 500: '5元', 100: '1元', 50: '5角', 10: '1角', 5: '5分', 2: '2分', 1: '1分'} 货币单位以分来表示。
2.3 计算总额
我们要以整数来表示货币,最小单位是分。因此一百元零五分应表示为整数10005分,而不是浮点数100.05元的表示方法,这一点容易引发错误的数据表示,的确如此,一不小心就出错了。 $total = \sum^n_{i=1}a_ib_i$ 其中 $total$ 表示现金总额,$a_i$ 表示某种币值,$b_i$ 表示某种币值对应的数量。
2.4 显示结果
正确显示钱箱现金总额是我们程序设计的一个关键点。我们假设钱箱中现金总额是10005分,为了符合人们习惯叫法,需要把货币单位由分转换成元。因此我们通过以下方式,可以方便将金额为10005分可以转成: 元:10005 // 100 分:10005 % 100 可以使用如下格式显示符号: total = 10005 print(f'{total//100}.{total % 100:0>2}元') 第二个占位符中0>2确保了正确显示角分05,而不是显示为5;它的含义是显示2位数字,如果是1位数字,则数字前边用0填充。 在交互式Python解释器中,执行以下命令,观察其显示结果:
>>> total = 10005
>>> total // 100
100
>>> total % 100
5
>>> print(f'{total//100}.{total % 100}元') # ①
100.5元
>>> print(f'{total//100}.{total % 100:0>2}元') # ②
100.05元
>>>
语句①执行结果与我们预期结果不符,这个问题的解决方法如同语句②所示,非常的巧妙。 语句②正确地打印和显示结果100.05元。
3. 程序代码
"""
teller_cash.py : 银行柜员现金清分模拟
"""
from random import choice
# 货币币值,以分为单位
currency = {10000: '100元', 5000: '50元', 2000: '20元', 1000: '10元', 500: '5元',
100: '1元', 50: '5角', 10: '1角', 5: '5分', 2: '2分', 1: '1分'}
def main():
# 创建柜员钱箱
cash_box = teller_box(100)
# 按币值清点数量
cash = cash_type(cash_box)
# 打印清分结果
print_detail(cash)
print_total(cash)
def teller_box(n=10):
"""
功能:创建柜员钱箱
返回:以列表形式表示的钱箱
"""
money_box = []
for n in range(n):
money_box += [choice(list(currency.keys()))] # ①
money_box.sort(reverse=True) # ②
return money_box
def cash_type(money_box):
"""
功能:按币值分类清点现金和数量
参数:money_box 钱箱
"""
cash = {}
for bill in money_box: # ③
if bill in cash:
cash[bill] += 1
else:
cash[bill] = 1
return cash
def print_detail(cash):
"""
功能:打印货币面值和数量
参数:cash 现金统计分类字典
"""
print('币值\t数量')
for bill, numbers in cash.items(): # ④
print(f'{currency[bill].ljust(6)}\t{numbers}')
def print_total(cash):
"""
功能:统计钱箱现金合计
参数:cash 现金统计分类字典
"""
total = 0
for bill, numbers in cash.items(): # ⑤
total += bill * numbers
print(f'合计:\t¥{total//100}.{total % 100:0>2}元') # ⑥
if __name__ == '__main__':
main()
主要函数说明如下: 函数teller_box(n=10):这个函数用于创建柜员钱箱,参数n表示钱箱中货币数量。 函数cash_type(money_box):按照币值不同进行分类统计每一种币值的数量,参数money_box是以列表形式表示的钱箱。 函数print_detail(cash):打印币值和数量的明细内容,cash是以字典形式表示的币值和数量。 函数print_total(cash):计算和打印现金合计,cash是以字典形式表示的币值和数量。 重要语句说明如下: 语句①随机产生一种币值,并添加到柜员钱箱。 语句②按照币值大小对钱箱列表降序排序。 语句③循环统计各种币值的数量。 语句④循环打印显示币值和数量。 语句⑤循环统计所有现金的合计金额。 语句⑥格式化打印现金合计金额。
4. 执行效果
D:\cases\银行柜员现金清分>python teller_cash.py
币值 数量
100元 10
50元 11
20元 10
10元 7
5元 8
1元 11
5角 9
1角 10
5分 5
2分 10
1分 9
合计: ¥1877.04元
D:\cases\银行柜员现金清分>
上面的程序很好地模拟了银行柜员现金清分的过程。或许当你执行这段程序代码的时候,屏幕上的显示内容可能与以上不同,这是由于我们在创建钱箱时采用了随机生成算法所导致的正确结果,毋庸置疑程序执行的正确性。
5. 程序优化
目前这一版程序完全达到了设计要求。作为项目扩展内容,我们还可以从以下两个方面进一步进行优化和完善:
5.1 本币和外币
现有的程序版本,仅支持本币,不支持外币。
5.2 纸币和硬币
目前我国人民币发行的实际情况,1元及以下面值货币有纸币和硬币两种形态,根据银行柜员清分场景要求,纸币和硬币需要分别处理,即便是币值相同。例如:15张1角的纸币,15个1角的硬币是需要单独清分的。 以上两种问题,留待有兴趣的读者自行完成。