Skip to main content

Section 7.12 Case Study: Taxes

Subsection 7.12.1 The problem

We would like to calculate the taxes someone owes (using a VERY simplified and version of the US tax system). In this system, the following chart shows how much tax someone owes based on their income and filing status (single or married):
  • Income ≀ $20,000 β†’ Tax = 10% of the income
  • Income between $20,001 and $50,000 β†’ Tax = $2,000 + 15% of the amount exceeding $20,000
  • Income between $50,001 and $100,000 β†’ Tax = $6,500 + 20% of the amount exceeding $50,000
  • Income > $100,000 β†’ Tax = $16,500 + 25% of the amount exceeding $100,000
Figure 7.12.1. Tax Calculation if Married
  • Income ≀ $10,000 β†’ Tax = 10% of the income.
  • Income between $10,001 and $30,000 β†’ Tax = $1,000 + 15% of the amount exceeding $10,000.
  • Income between $30,001 and $70,000 β†’ Tax = $4,000 + 20% of the amount exceeding $30,000.
  • Income > $70,000 β†’ Tax = $12,000 + 25% of the amount exceeding $70,000.
Figure 7.12.2. Tax Calculation if Single
We are going to focus on writing a function that does this task. We can write tests for that function that verify all the different categories of filer (status/income) work as expected. Once that function is working, we could easily place it into a program that asked the user for their income and status.
For now, we will just write the function and the tests. We will not worry about the user interface. The function we will write will look like this:
double calculateTax(double income, bool isMarried)

Subsection 7.12.2 Understanding the Problem And Tests

Before we start writing code, we need to understand the problem. A good way to make sure we do is to solve it by hand. In this case, there is not one solution - each combination of status and income will have a different solution. We can start by solving a few of the cases by hand:
  • If we are single and make $5,000, we should pay 10%. That is \(0.10 \cdot 5000 = 500\)
  • If we are single and make $50,000 we should pay $4,000 plus 20% of the amount over $30,000. That is \(4000 + .20 \cdot (50000-30000) = 8000\)
  • If we are married and make $120,000 we should pay $16,500 plus 25% of the amount over $100,000. That is \(16500 + .25 \cdot (120000-100000) = 21500\)
Those are not a complete set of test cases, but the are good enough to get us started. Turned into tests they look like:
// The tests
TEST_CASE("calculateTax") {
  CHECK(calculateTax(5000, false) == doctest::Approx(500));
  CHECK(calculateTax(50000, false) == doctest::Approx(8000));
  CHECK(calculateTax(120000, true) == doctest::Approx(21500));
}

Subsection 7.12.3 First decision

Anytime that we have a complex problem, It is a good idea to solve it in steps. For math problems this means doing individual parts of the calculation so we can check those results before combining the results. For a problem involving complex selection it means only worrying about one test at a time. Here we need to decide whether to worry about the status or income first. The status seems simpler (there are only two categories) and more determinative (single and married filers use completely different tables), so we will start there.
We won’t worry about the function returning the correct value yet, we will just print out if each test case represents a filing that is single or married. Try running this version. The output is a little hard to read, but you should be able to find ***SINGLE*** in two spots and then one ***MARRIED***.

Subsection 7.12.4 Single Filers

Now that we have the status figured out, we can start worrying about the income. We will start with the single filers.

Activity 7.12.1. Single Filers.

Now that we have the status figured out, we can start worrying about the income. We will start with the single filers. Complete tasks A and B above.
(a)
Complete the code below to pass the first test. Just change the part that shows ???. The other two tests are commented out so they do not run.
(b)
Fix the line with ??? to pass the second test. The third test is still commented out.
Once part B is finished, take a moment to look at the structure of the conditions for the income. Note that:
  • Because else if only runs if the previous tests in the chain have failed, we do not need to state something like income > 30000 && income <= 70000. The only way we reach the 70000 test is if the income is not <= 30000.
  • The final else does not need a condition.
Listing 7.12.3.
if (income <= ???) {
    tax = income * 0.10;
} else if (income <= 30000) {
    tax = 1000 + (income - 10000) * 0.15;
} else if (income <= 70000) {
    tax = ???;
} else {
  tax = 12000 + (income - 70000) * 0.25;
}

Insight 7.12.1.

Avoid stating things that were eliminated by previous checks. Doing so is a form of β€œrepeating yourself”. Remember DRY
In this case, writing out each test in long form can make it easy to by accident include a boundary (like $50,000) in two conditions or none at all.

Subsection 7.12.5 Married Filers

Now let’s finish the program.

Checkpoint 7.12.1.

Complete the Married section. Paste your Single code from above into this problem, then fix the two ??? areas of the program. Here is a link if you need to look back at the Married Tax Table.
There are some new tests. All the tests are hidden, but if you fail them, you will see what they are.

Subsection 7.12.6 Debrief

Of course, there are other ways we could write the code. But they would almost all involve more repeating ourselves. One such alternate would be to replace the nested ifs with complex conditions:
Listing 7.12.4.
...
if (isMarried && income <= 20000) {...
else if (isMarried && income <= 50000) {...
else if (isMarried && income <= 100000) {...
else if (isMarried) {...
else if (!isMarried income <= 10000) {...
...
Although that structure could produce the right values, the repetition is unnecessary and just serves to make the code harder to read or update.
It is also worth mentioning that we have a lot of β€œmagic numbers” like 50000 in this program that probably should be constants. In a real tax program, those special values would likely be stored in a configuration file and read into variables so the tax brackets could be changed without rebuilding the entire program.
You have attempted of activities on this page.