快到年末了,同学所在公司办了个小比赛,要求输入年终奖和月薪,给出拆分方案。
具体要求如下:
根据现有个人所得税计税办法,个人薪酬计税有两种方式,一种为月工资(含月奖金)计税,一种为年终奖综合计税。在年终奖综合计税发放过程中,在某些区间会出现税前奖金增加,税后实际收入反而减少的情况。为了合理避税,某公司计划拆分年终奖为综合计税发放和随月工资发放两种形式,随月工资发放次数最多为2个月。
设计一个年终奖自动拆分程序,输入为计税月工资额、应发年终奖,输出为综合计税应发年终奖、第1个月随月工资发放奖金、第2个月随工资发放奖金。要求税后总收入最大,如税后收入相同,拆分发放次数约少越好。具体的税收办法和测试数据,我写在最下方,想尝试的同学可以自己动手试试。
下面给出我的解决方案:
1、拆分问题,年终奖最优拆分方案只会出现三种情况:不拆分最优、拆分入一个月最优、拆分入两个月最优。
2、独立出计算税额的功能函数
3、简化模型,对于拆分入两个月的情况最优解一定是N组解,而不是唯一的,而且这N组解中至少有两月拆入的金额相同的情况。不考虑性能的前提下,我们只关心这组解,以求简化问题。
4、定义个税相关政策的常量,进行初始化
基于以上分析,就简单多了,可以写出如下代码:
# coding=utf-8
import time
base_quota = 3500
tax_quota = [1500, 4500, 9000, 35000, 55000, 80000]
tax_rat = [0.03, 0.10, 0.20, 0.25, 0.30, 0.35, 0.45]
tax_quick = [0, 105, 555, 1005, 2755, 5505, 13505]
one_month = 0
two_month = 0
# 主线程执行函数
def run():
year_bonus = input('Please input bonus\n')
month_salary = input('Please input salary\n')
# 获取三种方案的税额(不拆分、拆入一个月、拆入两个月)
start_time = time.clock()
year_bonus_tax = get_only_bonus_tax(year_bonus, month_salary)
one_month_tax = get_one_month_bonus(year_bonus, month_salary)
two_month_tax = get_two_month_bonus(year_bonus, month_salary)
# 取最小税额并计算税后奖金
min_tax = min(year_bonus_tax, one_month_tax, two_month_tax)
bonus_remain = year_bonus - min_tax
if min_tax == year_bonus_tax:
print year_bonus, "0", "0", bonus_remain
elif min_tax == one_month_tax:
print year_bonus - one_month, one_month, "0", bonus_remain
elif min_tax == two_month_tax:
print year_bonus - (two_month * 2), two_month, two_month, bonus_remain
else:
return
end_time = time.clock()
print(u"耗时:"),
print (end_time - start_time)
run()
return
# 获取税率等级
def get_tax_num(money):
for i in range(len(tax_quota)):
if money > tax_quota[-1]:
return len(tax_quota)
elif money <= tax_quota[i]:
return i
else:
continue
# 获取税率
def get_tax_rat(money):
return rat(get_tax_num(money))
# 获取速算扣除数
def get_tax_quick(money):
return quick(get_tax_num(money))
def rat(num):
if num < len(tax_rat):
return tax_rat[num]
else:
print "function rat error"
def quick(num):
if num < len(tax_quick):
return tax_quick[num]
else:
print "function quick error"
# 获取平常月交税金额
def get_month_tax(money):
if money <= base_quota:
return 0
else:
money -= base_quota
return money * get_tax_rat(money) - get_tax_quick(money)
# 获取年奖平均月交税金额
def get_per_month_tax(money):
return money * get_tax_rat(money) - get_tax_quick(money)
# 获取不拆分年奖交税总额
def get_only_bonus_tax(year_bonus, month_salary):
if month_salary <= base_quota:
if year_bonus < base_quota - month_salary:
return 0
else:
per_month = (year_bonus - (base_quota - month_salary)) / 12.0
tax = (year_bonus - (base_quota - month_salary)) * get_tax_rat(per_month) - get_tax_quick(
per_month)
else:
per_month = year_bonus / 12.0
tax = year_bonus * get_tax_rat(per_month) - get_tax_quick(per_month)
return tax
# 获取拆分为一个月交税总额
def get_one_month_bonus(year_bonus, month_salary):
now_bonus_tax = get_only_bonus_tax(year_bonus, month_salary)
for i in range(1, int(year_bonus)):
bonus_remain = year_bonus - i
month_add_salary = month_salary + i
month_tax_add = get_month_tax(month_add_salary) - get_month_tax(month_salary)
bonus_tax = round(get_only_bonus_tax(bonus_remain, month_salary) + month_tax_add, 2)
if bonus_tax < now_bonus_tax:
now_bonus_tax = bonus_tax
global one_month
one_month = i
return now_bonus_tax
# 获取拆分为两个月交税总额
def get_two_month_bonus(year_bonus, month_salary):
now_bonus_tax = get_only_bonus_tax(year_bonus, month_salary)
for i in range(1, int(year_bonus)):
bonus_remain = year_bonus - i
month_add_salary = month_salary + (i / 2.0)
month_tax_add = (get_month_tax(month_add_salary) - get_month_tax(month_salary)) * 2.0
bonus_tax = round(get_only_bonus_tax(bonus_remain, month_salary) + month_tax_add, 2)
if bonus_tax < now_bonus_tax:
now_bonus_tax = bonus_tax
global two_month
two_month = i / 2.0
return now_bonus_tax
# 运行主函数
run()
分析优化(注意,算法中应该尽量避免加入主观认知):原始代码中,我们进行了循环穷举,然后比较计算结果。仔细征税办法的文档分析,对于年终奖拆入一个月的这种情况,年终奖不可能拆出超过50%,因为年终奖是用商数来确定交税比例的,确定比例所用的表相当于月薪减去起征点。
当然还可以用数学进一步证明最大合理循环范围,基于这样分析,我们可以把循环的最大范围缩小一半。
计算优化:刚才我们把大问题分成了三部分,又把两个月的那种情况简化为相等的两个月,而两个相等的月进行税额计算的时候会用到只拆入一个月的那种情况下的数据,于是我们可以用空间来换时间。对拆入一个月计算的数据存入内存(list中),在拆入两个月的时候只需要用的时候,用index去取就好了,这样一来,又会快很多。
同理,bonus_remain这个量也会被两部分复用,我们也放入内存中。
最后对拆入两月的情况继续缩小循环范围(年终的四分之一),我们可以得到改良代码如下:
# coding=utf-8
import time
base_quota = 3500
tax_quota = [1500, 4500, 9000, 35000, 55000, 80000]
tax_rat = [0.03, 0.10, 0.20, 0.25, 0.30, 0.35, 0.45]
tax_quick = [0, 105, 555, 1005, 2755, 5505, 13505]
one_month = 0
two_month = 0
result = [0.0]
bonus_result = [0.0]
index_range = 0
# 相当于C里的main函数
def run():
year_bonus = input('Please input bonus\n')
month_salary = input('Please input salary\n')
# 获取三种方案的税额(不拆分、拆入一个月、拆入两个月)
start_time = round(time.clock(), 2)
year_bonus_tax = get_only_bonus_tax(year_bonus, month_salary)
one_month_tax = get_one_month_bonus(year_bonus, month_salary)
two_month_tax = get_two_month_bonus(year_bonus, month_salary)
# 取最小税额并计算税后奖金
print("year_bonus_tax:", year_bonus_tax)
print("one_month_tax:", one_month_tax)
print("two_month_tax:", two_month_tax)
# min_tax = min(year_bonus_tax, one_month_tax)
min_tax = min(year_bonus_tax, one_month_tax, two_month_tax)
bonus_remain = year_bonus - min_tax
if min_tax == year_bonus_tax:
print year_bonus, "0", "0", bonus_remain
elif min_tax == one_month_tax:
print year_bonus - one_month, one_month, "0", bonus_remain
elif min_tax == two_month_tax:
print year_bonus - (two_month * 2), two_month, two_month, bonus_remain
else:
return
end_time = round(time.clock(), 2)
print(u"耗时:"),
print (end_time - start_time)
init()
return
def init():
global result, bonus_result, index_range
result = [0.0]
bonus_result = [0.0]
index_range = 0
run()
# 获取税率等级
def get_tax_num(money):
for i in range(len(tax_quota)):
if money > tax_quota[-1]:
return len(tax_quota)
elif money <= tax_quota[i]:
return i
else:
continue
# 获取税率
def get_tax_rat(money):
return rat(get_tax_num(money))
# 获取速算扣除数
def get_tax_quick(money):
return quick(get_tax_num(money))
def rat(num):
if num < len(tax_rat):
return tax_rat[num]
else:
print "function rat error"
def quick(num):
if num < len(tax_quick):
return tax_quick[num]
else:
print "function quick error"
# 获取平常月交税金额
def get_month_tax(money):
if money <= base_quota:
return 0.0
else:
money -= base_quota
return round(money * get_tax_rat(money) - get_tax_quick(money), 2)
# 获取年奖平均月交税金额
def get_per_month_tax(money):
return money * get_tax_rat(money) - get_tax_quick(money)
# 获取不拆分年奖交税总额
def get_only_bonus_tax(year_bonus, month_salary):
if month_salary <= base_quota:
if year_bonus < base_quota - month_salary:
tax = 0.0
else:
per_month = (year_bonus - (base_quota - month_salary)) / 12.0
tax = (year_bonus - (base_quota - month_salary)) * get_tax_rat(per_month) - get_tax_quick(
per_month)
else:
per_month = year_bonus / 12.0
tax = round(year_bonus * get_tax_rat(per_month) - get_tax_quick(per_month), 2)
return tax
def get_month_tax_add(month_salary, add_num):
month_add_salary = month_salary + add_num
month_tax_add = round(get_month_tax(month_add_salary) - get_month_tax(month_salary), 2)
return month_tax_add
# 获取拆分为一个月交税总额
def get_one_month_bonus(year_bonus, month_salary):
now_bonus_tax = get_only_bonus_tax(year_bonus, month_salary)
half_bonus = int((year_bonus + 1) / 2) + 1
global index_range
index_range = half_bonus
for i in range(1, half_bonus):
bonus_remain = year_bonus - i
month_tax_add = get_month_tax_add(month_salary, i)
global result, bonus_result
result.append(month_tax_add)
bonus_result.append(get_only_bonus_tax(bonus_remain, month_salary))
bonus_tax = round(bonus_result[-1] + month_tax_add, 2)
# bonus_tax = round(get_only_bonus_tax(bonus_remain, month_salary) + month_tax_add, 2)
if bonus_tax < now_bonus_tax:
now_bonus_tax = bonus_tax
global one_month
one_month = i
return now_bonus_tax
# 获取拆分为两个月交税总额
def get_two_month_bonus(year_bonus, month_salary):
now_bonus_tax = get_only_bonus_tax(year_bonus, month_salary)
quarter_bonus = int((index_range + 1) / 2)
for i in range(1, quarter_bonus):
# bonus_remain = year_bonus - (i * 2.0)
month_tax_add = result[i] * 2.0
# bonus_tax = round(get_only_bonus_tax(bonus_remain, month_salary) + month_tax_add, 2)
bonus_tax = round(bonus_result[2 * i] + month_tax_add, 2)
if bonus_tax < now_bonus_tax:
now_bonus_tax = bonus_tax
global two_month
two_month = i
return now_bonus_tax
# 运行主函数
run()
再跑一次百万年薪的最优解:5.8秒。
我暂时只想到这么多,肯定还有继续优化的余地,欢迎大家交流。
详细文档说明:
根据现有个人所得税计税办法,个人薪酬计税有两种方式,一种为月工资(含月奖金)计税,一种为年终奖综合计税。在年终奖综合计税发放过程中,在某些区间会出现税前奖金增加,税后实际收入反而减少的情况。为了合理避税,某公司计划拆分年终奖为综合计税发放和随月工资发放两种形式,随月工资发放次数最多为2个月。
请设计一个年终奖自动拆分程序,输入为计税月工资额、应发年终奖,输出为综合计税应发年终奖、第1个月随月工资发放奖金、第2个月随工资发放奖金。要求税后总收入最大,如税后收入相同,拆分发放次数约少越好。
附注:
1、计税月工资额为应发月工资额扣除社保、公积金等免税收入后的金额
2、月工资计税和年终奖综合计税方法参见附件一、附件二
附件一:月工资计税办法
应纳税额 = 月工资应纳税所得额×适用税率-速算扣除数
月工资应纳税所得额 = 计税月工资额 - 3500
注: 3500 为个人所得税起征点,即税法规定的费用扣除额。
个人所得税税率表(工资、薪金所得适用)
级数 |
月工资应纳税所得额 |
适用税率 |
速算扣除数 |
1 |
不超过1,500元的部分( X ≤ 1500) |
3% |
0 |
2 |
超过1,500元至4,500元的部分 |
10% |
105 |
3 |
超过4,500元至9,000元的部分 |
20% |
555 |
4 |
超过9,000元至35,000元的部分 |
25% |
1,005 |
5 |
超过35,000元至55,000元的部分 |
30% |
2,755 |
6 |
超过55,000元至80,000元的部分 |
35% |
5,505 |
7 |
超过80,000元的部分( X > 80,000) |
45% |
13,505 |
如某员工10月“计税月工资额”为7500元,
月工资应纳税所得额 =7500 – 3500 = 4000元
查个人所得税税率表,可得“适用税率”为10%,“速算扣除数”为105
个人所得税应纳税额 =4000*10% - 105 = 295元
月度实发工资 = 7500– 295 = 7205 元
附件二:年终奖综合计税办法
纳税人取得全年一次性奖金,单独作为一个月工资、薪金所得计算纳税,并按以下办法计税:
(一)先将雇员当月内取得的全年一次性奖金,除以12个月,按其商数确定适用税率和速算扣除数。
如果在发放年终一次性奖金的当月,雇员当月计税工资额低于税法规定的费用扣除额(个人所得税起征点3500元),应将全年一次性奖金减除“雇员当月计税工资额与费用扣除额的差额”后的余额,按上述办法确定全年一次性奖金的适用税率和速算扣除数。
(二)将雇员个人当月内取得的全年一次性奖金,按第(一)项确定的适用税率和速算扣除数计算征税,计算公式如下:
1.如果雇员当月工资薪金所得高于(或等于)税法规定的费用扣除额的,适用公式为:
应纳税额=雇员当月取得全年一次性奖金×适用税率一速算扣除数
2.如果雇员当月计税工资额低于税法规定的费用扣除额的,适用公式为:
应纳税额=(雇员当月取得全年一次性奖金一雇员当月计税工资额与费用扣除额的差额)×适用税率一速算扣除数
(三)在一个纳税年度内,对每一个纳税人,该计税办法只允许采用一次。
如某员工年终奖为50000元,月计税工资为3000元
商数 = (50000-(3500-3000)) / 12 = 4125
查个人所得税税率表,可得“适用税率”为10%,“速算扣除数”为105
年终奖个人所得税应纳税额=(50000-(3500-3000)) *10% - 105 = 4845元
税后实发年终奖 = 50000 – 4845 = 45155元
一、根据给定的年终奖金额、当月计税工资额,计算最佳拆分方法。
输入:年终奖金额、月计税工资额(用为1-n组数据,一行为一组);
输出:年终奖综合计税发放金额、年终奖随第一个月发放金额、年终奖随第二个月发放金额、年终奖税后实发总金额。
例如:输入:50000 5000
5000 2500
输出: 50000 0 0 45105
3000 10001000 4940
更多测试数据:
in: 2500 2500 out: 1000 1000 500 2500
in: 5000 2500 out: 3000 1000 1000 4940
in: 18000 3500 out: 18000 0 0 17460
in: 18001 7000 out: 18000 1 0 17460.9
in: 20001 3500 out: 17001 1500 1500 19400.97
in: 21001 2500 out: 16001 2500 2500 20460.97
in: 34134 5001 out: 18000 7499 8635 30910.2
in: 50000 4000 out: 48000 1000 1000 45245
in: 53800 7000 out: 53800 0 0 48525
in: 53800 4990 out: 53780 10 10.00 48526.4
in: 60000 60000 out: 54000 6000 0 52605
in: 80000 15000 out: 54000 23500 2500 68205
in: 87657 12500 out: 54000 26000 7657 73947.75
in: 90000 83506 out: 90000 0 0 72555
in: 108000 8000 out: 54000 30500 23500 89655
in: 108000 12500 out: 54000 26000 28000 89105
in: 130000 15000 out: 54000 43500 32500 104255
in: 400000 12500 out: 400000 0 0 301005
in: 400000 12499 out: 399998 1 1 301005.1
in: 400000 12501 out: 400000 0 0 301005
in: 420000 58500 out: 420000 0 0 316005
in: 435224 5000 out: 420000 7500 7724 328773
in: 1000000 8000 out: 660000 75500 264500 675405
in: 1000000 20000 out: 660000 63500 276500 670155
in: 1000000 7000 out: 660000 76500 263500 676105