年終獎拆分避稅演算法及優化
快到年末了,同學所在公司辦了個小比賽,要求輸入年終獎和月薪,給出拆分方案。
具體要求如下:
根據現有個人所得稅計稅辦法,個人薪酬計稅有兩種方式,一種為月工資(含月獎金)計稅,一種為年終獎綜合計稅。在年終獎綜合計稅發放過程中,在某些區間會出現稅前獎金增加,稅後實際收入反而減少的情況。為了合理避稅,某公司計劃拆分年終獎為綜合計稅發放和隨月工資發放兩種形式,隨月工資發放次數最多為2個月。
設計一個年終獎自動拆分程式,輸入為計稅月工資額、應發年終獎,輸出為綜合計稅應發年終獎、第1個月隨月工資發放獎金、第2個月隨工資發放獎金。要求稅後總收入最大,如稅後收入相同,拆分發放次數約少越好。具體的稅收辦法和測試資料,我寫在最下方,想嘗試的同學可以
下面給出我的解決方案:
1、拆分問題,年終獎最優拆分方案只會出現三種情況:不拆分最優、拆分入一個月最優、拆分入兩個月最優。
2、獨立出計算稅額的功能函式
3、簡化模型,對於拆分入兩個月的情況最優解一定是N組解,而不是唯一的,而且這N組解中至少有兩月拆入的金額相同的情況。不考慮效能的前提下,我們只關心這組解,以求簡化問題。
4、定義個稅相關政策的常量,進行初始化
基於以上分析,就簡單多了,可以寫出如下程式碼:
功能上沒問題了,輸入測試資料年終獎100W,月薪8000,程式用時約15.7秒,為了不讓財務糾結,快速高興的拿到年終,我們需要改進演算法。# 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