4. Application Exercise – [Tax Calculation]
![]() |
4.1. The problem
We aim to write a program to calculate a taxpayer’s tax. We consider the simplified case of a taxpayer who has only their salary to report:
- we calculate the number of tax brackets for the employee nbParts = nbChildren / 2 + 1 if they are unmarried, nbChildren / 2 + 2 if they are married, where nbChildren is the number of children;
- we calculate their taxable income R = 0.72 * S, where S is their annual salary;
- we calculate their family coefficient Q = R/N;
- we calculate their tax I based on the following data.
12,620.0 0 0
13,190 0.05 631
15,640 0.1 1,290.5
24,740 0.15 2,072.5
31,810 0.2 3,309.5
39,970 0.25 4,900
48,360 0.3 6,898.5
55,790 0.35 9,316.5
92,970 0.4 12,106
127,860 0.45 16,754.5
151,250 0.50 23,147.5
172,040 0.55 30,710
195,000 0.60 39,312
0 0.65 49,062
Each row has 3 fields. To calculate tax I, find the first row where QF <= field1. For example, if QF = 30,000, the row found will be:
Tax I is then equal to 0.15*R - 2072.5*nbParts. If QF is such that the condition QF<=field1 is never met, then the coefficients from the last row are used. Here:
which gives tax I = 0.65*R - 49062*nbParts.
4.2. Version with lists
# -*- coding=utf-8 -*-
import math, sys
def cutNewLineChar(line):
# Remove the end-of-line character from line if it exists
l = len(line);
while(line[l-1]=="\n" or line[l-1]=="\r"):
l -= 1
return(line[0:l]);
# --------------------------------------------------------------------------
def calculateTaxes(spouse, children, salary, thresholds, coeffR, coeffN):
# married: yes, no
# children: number of children
# salary: annual salary
# number of tax brackets
married = married.lower()
if(married=="yes"):
nbShares = float(children) / 2 + 2
else:
nbParts = float(children) / 2 + 1
# add 1/2 portion if there are at least 3 children
if children >= 3:
nbParts+=0.5
# taxable income
taxableIncome=0.72*salary
# family quotient
familyQuotient = taxableIncome / nbParts
# is placed at the end of the limits array to stop the following loop
limits[len(limits)-1]=quota
# tax calculation
i=0
while(quotient > limits[i]):
i = i + 1
# Since quotient is placed at the end of the limits array, the previous loop
# cannot go beyond the limits array
# now we can calculate the tax
return math.floor(taxableIncome * coeffR[i] - numShares * coeffN[i])
# ------------------------------------------------ main
# definition of constants
DATA="data.txt"
RESULTS="results.txt"
limits=[12620,13190,15640,24740,31810,39970,48360,55790,92970,127860,151250,172040,195000,0]
coeffR=[0,0.05,0.1,0.15,0.2,0.25,0.3,0.35,0.4,0.45,0.5,0.55,0.6,0.65]
coeffN=[0,631,1290.5,2072.5,3309.5,4900,6898.5,9316.5,12106,16754.5,23147.5,30710,39312,49062]
# reading the data
try:
data = open(DATA, "r")
except:
print "Unable to open the data file [DATA] for reading"
sys.exit()
# Open the results file
try:
results = open(RESULTS, "w")
except:
print "Unable to create the results file [RESULTS]"
sys.exit()
# process the current line of the data file
line = data.readline()
while(line != ''):
# remove any line break characters
line = cutNewLineChar(line)
# retrieve the 3 fields married:children:salary that make up the line
(spouse, children, salary) = line.split(",")
children = int(children)
salary = int(salary)
# calculate the tax
tax = calculateTaxes(spouse, children, salary, limits, coeffR, coeffN)
# print the result
results.write("{0}:{1}:{2}:{3}\n".format(spouse, children, salary, tax))
# read a new line
line = data.readline()
# close the files
data.close()
results.close()
The data file data.txt:
The results.txt file containing the results:
yes:2:200000:22504.0
no:2:200000:33388.0
yes:3:200000:16400.0
no:3:200000:22504.0
yes:5:50000:0.0
no:0:3000000:1354938.0
4.3. Version with text files
In the previous example, the data needed to calculate the tax was found in three lists. From now on, it will be retrieved from a text file:
# -*- coding=utf-8 -*-
import math, sys
# --------------------------------------------------------------------------
def getTables(IMPOTS):
# IMPOTS: the name of the file containing the data for the limit tables, coeffR, and coeffN
# Does the IMPOTS file exist?
try:
data = open(IMPOTS, "r")
except:
return ("The IMPOTS file does not exist", 0, 0, 0)
# Create the 3 lists - assuming the lines are syntactically correct
# -- line 1
line = data.readline()
if line == '':
return ("The first line of the {0} file is missing".format(IMPOTS), 0, 0, 0)
limits = cutNewLineChar(line).split(":")
for i in range(len(limits)):
limits[i] = int(limits[i])
# -- line 2
line = data.readline()
if line == '':
return ("The second line of the file {0} is missing".format(IMPOTS), 0, 0, 0)
coeffR = cutNewLineChar(line).split(":")
for i in range(len(coeffR)):
coeffR[i] = float(coeffR[i])
# -- line 3
line = data.readline()
if line == '':
return ("The third line of the file {0} is missing".format(IMPOTS), 0, 0, 0)
coeffN = cutNewLineChar(line).split(":")
for i in range(len(coeffN)):
coeffN[i] = float(coeffN[i])
# end
return ("", limits, coeffR, coeffN)
# --------------------------------------------------------------------------
def cutNewLineChar(line):
# Remove the end-of-line character from line if it exists
l = len(line);
while(line[l-1] == "\n" or line[l-1] == "\r"):
l -= 1
return(line[0:l]);
# --------------------------------------------------------------------------
def calculateTaxes(married, children, salary, limits, coeffR, coeffN):
# married: yes, no
# children: number of children
# salary: annual salary
# number of shares
married = married.lower()
if(married=="yes"):
nbParts = float(children) / 2 + 2
else:
nbParts = float(children) / 2 + 1
# add 1/2 portion if there are at least 3 children
if children >= 3:
nbParts+=0.5
# taxable income
taxableIncome=0.72*salary
# family quotient
familyQuotient = taxableIncome / nbParts
# is placed at the end of the limits array to stop the following loop
limits[length(limits)-1] = quotient
# Calculate the tax
i=0
while(quotient > limits[i]):
i = i + 1
# Since quotient is placed at the end of the limits array, the previous loop
# cannot go beyond the limits array
# now we can calculate the tax
return math.floor(taxableIncome * coeffR[i] - numShares * coeffN[i])
# ------------------------------------------------ main
# definition of constants
DATA="data.txt"
RESULTS="results.txt"
TAXES="taxes.txt"
# The data needed to calculate the tax has been placed in the TAXES file
# with one line per table in the form
# val1:val2:val3,...
(error,limits,coeffR,coeffN)=getTables(TAXES)
# Was there an error?
if(error):
print "{0}\n".format(error)
sys.exit()
# reading data
try:
data = open(DATA, "r")
except:
print "Unable to open the data file [DATA] for reading"
sys.exit()
# Open the results file
try:
results = open(RESULTS, "w")
except:
print "Unable to create the results file [RESULTS]"
sys.exit()
# process the current line of the data file
line = data.readline()
while(line != ''):
# remove any line break characters
line = cutNewLineChar(line)
# retrieve the 3 fields married:children:salary that make up the line
(spouse, children, salary) = line.split(",")
children = int(children)
salary = int(salary)
# calculate the tax
tax = calculateTaxes(spouse, children, salary, limits, coeffR, coeffN)
# print the result
results.write("{0}:{1}:{2}:{3}\n".format(spouse, children, salary, tax))
# read a new line
line = data.readline()
# close the files
data.close()
results.close()
Same as before.
The getImpots method above (lines 6–36) returns a tuple (error, limits, coeffR, coeffN), where error is an error message that may be empty. You may want to handle these error cases using exceptions. In that case:
- if an error occurs, the getImpots method throws an exception;
- otherwise, it returns the tuple (limits, coeffR, coeffN).
The code for the getImpots method then becomes the following:
def getTables(IMPOTS):
# IMPOTS: the name of the file containing the data for the limits, coeffR, and coeffN tables
# Does the IMPOTS file exist? If not, an IOError exception is raised
data = open(IMPOTS, "r")
# creation of the 3 lists - we assume that the lines are syntactically correct
# -- line 1
line = data.readline()
if line == '':
raise RuntimeError("The first line of the file {0} is missing".format(IMPOTS))
limits = cutNewLineChar(line).split(":")
for i in range(len(limits)):
limits[i] = int(limits[i])
# -- line 2
line = data.readline()
if line == '':
raise RuntimeError("The second line of the file {0} is missing".format(IMPOTS))
coeffR = cutNewLineChar(line).split(":")
for i in range(len(coeffR)):
coeffR[i] = float(coeffR[i])
# -- line 3
line = data.readline()
if line == '':
raise RuntimeError("The third line of the file {0} is missing".format(IMPOTS))
coeffN = cutNewLineChar(line).split(":")
for i in range(len(coeffN)):
coeffN[i] = float(coeffN[i])
# end
return (limits, coeffR, coeffN)
- line 4: we do not check if opening the file fails. If it fails, an IOError exception is raised. We let this propagate to the calling program;
- lines 9–10: We raise an exception to indicate that the expected first line is missing. We use a predefined RuntimeError exception. We can also create our own exception classes. We’ll do that a little later;
- lines 16–17 and 23–24: We do the same thing for lines 2 and 3.
The main program code then becomes the following:
# ------------------------------------------------ main
# definition of constants
DATA="data.txt"
RESULTS="results.txt"
TAXES="taxes.txt"
# The data needed to calculate the tax has been placed in the TAXES file
# with one line per table in the form
# val1:val2:val3,...
error=False
try:
(limits, coeffR, coeffN) = getTables(IMPOTS)
except IOError, message:
error = True
except RuntimeError, message:
error = True
# Was there an error?
if(error):
print "The following error occurred: {0}\n".format(message)
sys.exit()
# reading data
...
- lines 12-17: we call the getImpots method and handle the two exceptions it may raise: IOError and RuntimeError. In this handling, we simply note that an error occurred;
- lines 20–22: we display the error message of the intercepted exception and terminate the program.
