Skip to content

4. Practice Exercise – Versions 1 and 2

4.1. The Problem

Image

The table above allows you to calculate the tax in the simplified case of a taxpayer who has only their salary to report. As indicated in note (1), the tax calculated this way is the tax before three mechanisms:

  • the family quotient cap, which applies to high incomes;
  • the tax credit and tax reduction that apply to low incomes;

Thus, the tax calculation involves the following steps [http://impotsurlerevenu.org/comprendre-le-calcul-de-l-impot/1217-calcul-de-l-impot-2019.php]:

Image

We propose to write a program to calculate a taxpayer’s tax in the simplified case of a taxpayer who has only their salary to report:

4.1.1. Calculation of Gross Tax

The gross tax can be calculated as follows:

First, we calculate the taxpayer’s number of shares:

    • each parent contributes 1 share;
    • The first two children each bring 1/2 portion;
    • The remaining children each bring one share:

The number of shares is therefore:

  • nbParts=1+nbChildren*0.5+(nbChildren-2)*0.5 if the employee is unmarried;
  • nbParts = 2 + nbChildren * 0.5 + (nbChildren - 2) * 0.5 if they are married;

where nbChildren is the number of children;

  • we calculate the taxable income R = 0.9 * S, where S is the annual salary;
  • the family quotient QF is calculated as QF = R / nbParts;
  • we calculate the gross tax I based on the following data (2019):
9964
0
0
27,519
0.14
1,394.96
73,779
0.3
5,798
156,244
0.41
13,913.69
0
0.45
20163.45

Each row has 3 fields: field1, field2, field3. To calculate tax I, we find the first row where QF <= field1 and take the values from that row. For example, for a married employee with two children and an annual salary S of 50,000 euros:

Taxable income: R=0.9*S=45,000

Number of shares: nbParts=2+2*0.5=3

Family quotient: QF=45,000/3=15,000

The first row where QF <= field1 is as follows:

    27,519        0.14    1394.96

Tax I is then equal to 0.14*R – 1394.96*numberOfShares=[0.14*45000-1394.96*3]=2115. The tax is rounded down to the nearest euro.

If the condition QF <= field1 holds in the first row, then the tax is zero.

If QF is such that the condition QF <= field1 is never satisfied, then the coefficients from the last line are used. Here:

    0            0.45        20,163.45

which gives the gross tax I = 0.45*R – 20163.45*nbParts.

4.1.2. Family Quotient Cap

Image

To determine whether the family quotient (QF) cap applies, we recalculate the gross tax without the children. Again, for the married employee with two children and an annual salary S of 50,000 euros:

Taxable income: R = 0.9 * S = 45,000

Number of shares: nbParts=2 (children are no longer counted)

Family quotient: QF = 45,000 / 2 = 22,500

The first line where QF <= field1 is as follows:

    27519        0.14    1,394.96

Tax I is then equal to 0.14*R – 1394.96*number of shares = [0.14*45,000 – 1394.96*2] = 3,510.

Maximum child-related allowance: 1551 * 2 = 3102 euros

Minimum tax: 3,510 – 3,102 = 408 euros

The gross tax with 3 tax brackets, already calculated at 2,115 euros, is higher than the minimum tax of 408 euros, so the family cap does not apply here.

Generally speaking, the gross tax is greater than (tax1, tax2) where:

  • [tax1]: is the gross tax calculated including children;
  • [tax2]: is the gross tax calculated without children and reduced by the maximum credit (here 1,551 euros per half-share) related to children;

4.1.3. Calculation of the tax reduction

Image

Still for the married employee with two children and an annual salary S of 50,000 euros:

The gross tax (2,115) from the previous step is less than 2,627 euros for a couple (1,595 euros for a single person): the tax reduction therefore applies. It is calculated as follows:

discount = threshold (couple = 1,970 / single = 1,196) - 0.75 * gross tax

discount = 1,970 – 0.75 * 2,115 = 383.75, rounded to 384 euros.

New gross tax = 2,115 – 384 = 1,731 euros

4.1.4. Calculation of the tax reduction

Image

Below a certain threshold, a 20% reduction is applied to the gross tax resulting from the previous calculations. In 2019, the thresholds are as follows:

  • Single: 21,037 euros;
  • couple: 42,074 euros; (the figure 37,968 used in the example above appears to be incorrect);

This threshold is increased by the value: 3,797 * (number of half-shares contributed by the children).

Again, for the married employee with two children and an annual salary S of 50,000 euros:

  • his taxable income (45,000 euros) is below the threshold (42,074 + 2 × 3,797) = 49,668 euros;
  • he is therefore entitled to a 20% reduction in his tax: 1,731 * 0.2 = 346.2 euros, rounded to 347 euros;
  • the taxpayer’s gross tax becomes: 1,731 – 347 = 1,384 euros;

4.1.5. Calculation of Net Tax

Our calculation ends here: the net tax due will be 1,384 euros. In reality, the taxpayer may be eligible for other deductions, particularly for donations to public or general interest organizations.

4.1.6. High-Income Cases

Our previous example applies to the majority of employees. However, the tax calculation differs for high-income earners.

4.1.6.1. Cap on the 10% reduction on annual income

In most cases, taxable income is calculated using the formula: R = 0.9 × S, where S is the annual salary. This is known as the 10% reduction. This reduction is capped. In 2019:

  • it cannot exceed 12,502 euros;
  • it cannot be less than €437;

Let’s consider the case of an unmarried employee with no children and an annual salary of 200,000 euros:

  • the 10% reduction is 200,000 euros > 12,502 euros. It is therefore capped at 12,502 euros;

4.1.6.2. Family Quotient Cap

Let’s consider a case where the family cap mentioned in the linked paragraph applies. Let’s take the case of a couple with three children and an annual income of 100,000 euros. Let’s go through the calculation steps again:

  • The 10% deduction is 10,000 euros < 12,502 euros. The taxable income R is therefore 100,000 - 10,000 = 90,000 euros;
  • The couple has nbParts = 2 + 0.5 * 2 + 1 = 4 shares;
  • their family quotient is therefore QF = R / nbParts = 90,000 / 4 = 22,500 euros;
  • their gross tax I1 with children is I1 = 0.14 × 90,000 – 1,394.96 × 4 = 7,020 euros;
  • their gross tax I2 without children:
    • QF = 90,000 / 2 = 45,000 euros;
    • I2 = 0.3 × 90,000 – 5,798 × 2 = 15,404 euros;
    • the family quotient cap rule states that the benefit provided by children cannot exceed (1,551 × 4 half-shares) = 6,204 euros. However, here, it is I2 – I1 = 15,404 – 7,020 = 8,384 euros, which is greater than 6,204 euros;
    • the gross tax is therefore recalculated as I3 = I2 - 6,204 = 15,404 - 6,204 = 9,200 euros;

This couple will receive neither a tax credit nor a reduction, and their final tax will be 9,200 euros.

4.1.7. Official figures

Tax calculation is complex. Throughout this document, tests will be conducted using the following examples. The results are from the tax authority’s simulator [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm]:

Taxpayer
Official results
Results from the document’s algorithm
Couple with 2 children and an annual income of 55,555 euros
Tax = 2,815 euros
Tax rate = 14%
Tax = 2,814 euros
Tax rate = 14%
Couple with 2 children and an annual income of 50,000 euros
Tax = 1,385 euros
Tax credit = €720
Reduction = 0 euros
Tax rate = 14%
Tax = €1,384
Deduction = 384 euros
Credit=347 euros
Tax rate = 14%
Couple with 3 children and an annual income of 50,000 euros
Tax = 0 euros
Tax credit = 384 euros
Reduction = 346 euros
Tax rate = 14%
Tax = 0 euros
Discount = 720 euros
Reduction = 0 euros
Tax rate = 14%
Single with 2 children and an annual income of 100,000 euros
Tax = 19,884 euros
Tax credit = 0 euros
Deduction = 0 euros
Tax rate = 41%
Tax = €19,884
Surcharge = 4,480 euros
Discount = 0 euros
Reduction = 0 euros
Tax rate = 41%
Single with 3 children and an annual income of 100,000 euros
Tax = €16,782
Tax credit = 0 euros
Deduction = 0 euros
Tax rate = 41%
Tax = €16,782
Surcharge = 7,176 euros
Discount = 0 euros
Reduction = 0 euros
Tax rate = 41%
Couple with three children and an annual income of 100,000 euros
Tax = €9,200
Tax credit = 0 euros
Deduction = 0 euros
Tax rate = 30%
Tax = €9,200
Surcharge = 2,180 euros
Discount = 0 euros
Reduction = 0 euros
Tax rate = 30%
Couple with 5 children and an annual income of 100,000 euros
Tax = €4,230
Tax credit = 0 euros
Deduction = 0 euros
Tax rate = 14%
Tax = €4,230
Deduction = 0 euros
Deduction=0 euros
Tax rate = 14%
Single, no children, and annual income of 100,000 euros
Tax = 22,986 euros
Tax credit = 0 euros
Deduction = 0 euros
Tax rate = 41%
Tax = €22,986
Surcharge = 0 euros
Discount = 0 euros
Reduction = 0 euros
Tax rate = 41%
Couple with 2 children and an annual income of 30,000 euros
Tax = 0 euros
Tax credit = 0 euros
Deduction = 0 euros
Tax rate = 0%
Tax = 0 euros
Discount=0 euros
Deduction=0 euros
Tax rate = 0%
Single, no children, and annual income of 200,000 euros
Tax = 64,211 euros
Tax credit = 0 euros
Deduction = 0 euros
Tax rate = 45%
Tax = €64,210
Surcharge = 7,498 euros
Discount = 0 euros
Reduction = 0 euros
Tax rate = 45%
Couple with 3 children and an annual income of 200,000 euros
Tax = €42,843
Tax credit = 0 euros
Deduction = 0 euros
Tax rate = 41%
Tax = €42,842
Surcharge = 17,283 euros
Discount = 0 euros
Reduction = 0 euros
Tax rate = 41%

In the example above, the “surcharge” refers to the additional amount paid by high-income earners due to two factors:

  • the cap on the 10% deduction from annual income;
  • the cap on the family allowance;

This indicator could not be verified because the tax authority’s simulator does not provide it.

We can see that the document’s algorithm calculates the correct tax amount every time, though with a margin of error of 1 euro. This margin of error stems from rounding. All monetary amounts are rounded up to the nearest euro in some cases and down to the nearest euro in others. Since I was not familiar with the official rules, the monetary amounts in the document’s algorithm were rounded:

  • up to the next euro for discounts and reductions;
  • down to the nearest euro for surcharges and the final tax;

In the following section, tests will be conducted to verify the validity of the results. They will be performed using the examples from the previous table with an accepted margin of error of 1 euro.

4.2. The script directory structure

Image

4.3. Version 1

4.3.1. The Algorithm

We present an initial program in which:

  • the data needed to calculate the tax is hard-coded in the program as arrays and constants;
  • taxpayer data (married, children, salary) is stored in a first text file [taxpayersdata.txt];
  • the results of the tax calculation (married, children, salary, tax) are stored in a second text file [resultats.txt];

The script [version-01/main.php] is as follows:


<?php

// strict types for function parameters
declare(strict_types=1);

// global constants
define("HALF-RATE_INCOME_LIMIT", 1551);
define("INCOME_LIMIT_SINGLE_PERSON_FOR_REDUCTION", 21037);
define("INCOME_LIMIT_FOR_COUPLES_FOR_REDUCTION", 42074);
define("REDUCTION_VALUE_HALF_PORTION", 3797);
define("SINGLE_PERSON_DISCOUNT_LIMIT", 1196);
define("COUPLE_DEDUCTION_LIMIT", 1970);
define("COUPLE_INCOME_THRESHOLD_FOR_DEDUCTION", 2627);
define("SINGLE_TAX_CAP_FOR_DEDUCTION", 1595);
define("MAXIMUM_10_PERCENT_DEDUCTION", 12502);
define("MINIMUM_10_PERCENT_DEDUCTION", 437);

// definition of local constants
$DATA = "taxpayersdata.txt";
$RESULTS = "results.txt";
$limits = array(9964, 27519, 73779, 156244, 0);
$coeffR = array(0, 0.14, 0.3, 0.41, 0.45);
$coeffN = array(0, 1394.96, 5798, 13913.69, 20163.45);

// read data
$data = fopen($DATA, "r");
if (!$data) {
  print "Unable to open the data file [$DATA] for reading\n";
  exit;
}

// Open the results file
$results = fopen($RESULTS, "w");
if (!$results) {
  print "Unable to create the results file [$RESULTS]\n";
  exit;
}

// process the current line of the data file
while ($line = fgets($data, 100)) {
  // Remove any line-end characters
  $line = cutNewLineChar($line);
  // retrieve the 3 fields married:children:salary that make up $line
  list($married, $children, $salary) = explode(",", $line);
  // calculate the tax
  $result = calculateTax($married, (int) $children, (float) $salary, $limits, $coeffR, $coeffN);
  // write the result to the results file
  $result = ["married" => $married, "children" => $children, "salary" => $salary] + $result;
  fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
  // next data
}
// close the files
fclose($data);
fclose($results);

// end
exit;

// --------------------------------------------------------------------------
function cutNewLinechar(string $line): string {
  // remove the end-of-line character from $line if it exists
  $L = strlen($line);  // line length
  while (substr($line, $L - 1, 1) === "\n" or substr($line, $L - 1, 1) === "\r") {
    $line = substr($line, 0, $L - 1);
    $L--;
  }
  // end
  return($line);
}

// tax calculation
// --------------------------------------------------------------------------
function calculateTax(string $married, int $children, float $salary, array $thresholds, array $coeffR, array $coeffN): array {

  // result
  return ["tax" => floor($tax), "surcharge" => $surcharge, "discount" => $discount, "reduction" => $reduction, "rate" => $rate];
}

// --------------------------------------------------------------------------
function calculateTax2(string $married, int $children, float $salary, array $limits, array $coeffR, array $coeffN): array {

  // result
  return ["tax" => $tax, "surcharge" => $surcharge, "rate" => $coeffR[$i]];
}

// taxableIncome = annualSalary - deduction
// the deduction has a minimum and a maximum
function getTaxableIncome(float $salary): float {

  // result
  return floor($taxableIncome);
}

// calculates a possible tax deduction
function getDiscount(string $married, float $salary, float $taxes): float {

  // result
  return ceil($discount);
}

// calculates a possible reduction
function getDiscount(string $married, float $salary, int $children, float $taxes): float {
  /
  // result
  return ceil($reduction);
}

The taxpayersdata.txt data file (married, children, salary):

yes,2,55555
yes,2,50000
yes,3,50000
no,2,100000
no,3,100000
yes,3,100000
yes,5,100000
no,0,100000
yes,2,30000
no,0,200000
yes,3,200000

The results.txt file (married, children, salary, tax, surcharge, discount, reduction, tax rate) of the results obtained:

{"married":"yes","children":"2","salary":"55555","tax":2814,"surcharge":0,"discount":0,"reduction":0,"rate":0.14}
{"married":"yes","children":"2","salary":"50000","tax":1384,"surcharge":0,"discount":384,"reduction":347,"rate":0.14}
{"married":"yes","children":"3","salary":"50000","tax":0,"surcharge":0,"discount":720,"reduction":0,"rate":0.14}
{"married":"no","children":"2","salary":"100000","tax":19884,"surcharge":4480,"discount":0,"reduction":0,"rate":0.41}
{"married":"no","children":"3","salary":"100000","tax":16782,"surcharge":7176,"discount":0,"reduction":0,"rate":0.41}
{"married":"yes","children":"3","salary":"100000","tax":9200,"surcharge":2180,"discount":0,"reduction":0,"rate":0.3}
{"married":"yes","children":"5","salary":"100000","tax":4230,"surcharge":0,"discount":0,"reduction":0,"rate":0.14}
{"married":"no","children":"0","salary":"100000","tax":22986,"surcharge":0,"discount":0,"reduction":0,"rate":0.41}
{"married":"yes","children":"2","salary":"30000","tax":0,"surcharge":0,"discount":0,"reduction":0,"rate":0}
{"married":"no","children":"0","salary":"200000","tax":64210,"surcharge":7498,"discount":0,"reduction":0,"rate":0.45}
{"married":"yes","children":"3","salary":"200000","tax":42842,"surcharge":17283,"discount":0,"reduction":0,"rate":0.41}

Comments

  • line 4: we enforce strict adherence to the type of function parameters;
  • lines 7–16: definition of all constants necessary for calculating the tax;
  • line 19: the name of the text file containing taxpayer data (married, children, salary);
  • line 20: the name of the text file containing the results (married, children, salary, tax) of the tax calculation;
  • lines 21–23: the three data arrays defining the different tax brackets for the tax calculation;
  • lines 26–30: open the taxpayer data file for reading [r]. The [fopen] function returns the Boolean value FALSE if the file could not be opened;
  • lines 33–37: Open the results file in write mode [w];
  • lines 40–51: loop to read the lines (spouse, children, income) from the taxpayer data file;
  • line 40: the [fgets] function reads 100 characters and stops at the first line break encountered. Here, all lines are less than 100 characters long. If a line break is encountered, it is included in the returned string. When the end of the file is reached, the [fgets] function returns FALSE;
  • line 42: the end-of-line character is removed;
  • line 44: the components (married, children, salary) of the line are retrieved;
  • line 46: the tax is calculated. The result is returned as an associative array (line 76);
  • line 48: the keys [married, children, salary] are added to the previously retrieved array;
  • line 49: the result is stored in the results file as a JSON string;
  • lines 53–54: once the taxpayer data file has been fully processed, the files are closed;
  • line 60: the function that removes the line break from a line $line. The line break is the string "\r\n" on Windows systems, "\n" on Unix systems. The result is the input string without its line break.
  • lines 63-64: substr($string, $start, $size) is the substring of $string starting at character $start and containing at most $size characters;

The [calculImpot] function is as follows:


// global constants
define("PLAFOND_QF_DEMI_PART", 1551);

// tax calculation
// --------------------------------------------------------------------------
function taxCalculation(string $married, int $children, float $salary, array $limits, array $coeffR, array $coeffN): array {
  // $marié: yes, no
  // $children : number of children
  // $salary: annual salary
  // $limits, $coeffR, $coeffN: data arrays used to calculate the tax
  //
  // Calculate tax with children
  $result1 = calculateTax2($married, $children, $salary, $limits, $coeffR, $coeffN);
  $tax1 = $result1["tax"];
  // Calculate tax without children
  if ($children != 0) {
    $result2 = calculateTax2($married, 0, $salary, $limits, $coeffR, $coeffN);
    $tax2 = $result2["tax"];
    // apply the family quotient cap
    if ($children < 3) {
      // $QF_CAP_HALF_PART euros for the first 2 children
      $tax2 = $tax2 - $children * HALF_FAMILY_QUOTA_CAP;
    } else {
      // $PLAFOND_QF_DEMI_PART euros for the first 2 children, double that for subsequent children
      $tax2 = $tax2 - 2 * HALF-RATE_CAP - ($children - 2) * 2 * HALF-RATE_CAP;
    }
  } else {
    $tax2 = $tax1;
    $result2 = $result1;
  }
  // We take the higher tax amount, along with its corresponding rate and surcharge
  if ($tax1 > $tax2) {
    $tax = $tax1;
    $rate = $result1["rate"];
    $surcharge = $result1["surcharge"];
  } else {
    $surcharge = $tax2 - $tax1 + $result2["surcharge"];
    $tax = $tax2;
    $rate = $result2["rate"];
  }
  // calculate any tax credit
  $discount = getDiscount($spouse, $salary, $tax);
  $tax -= $discount;
  // calculate any tax credit
  $reduction = getReduction($spouse, $salary, $children, $tax);
  $tax -= $reduction;
  // result
  return ["tax" => floor($tax), "surcharge" => $surcharge, "discount" => $discount, "reduction" => $reduction, "rate" => $rate];
}

// --------------------------------------------------------------------------
function calculateTax2(string $married, int $children, float $salary, array $limits, array $coeffR, array $coeffN): array {

  // result
  return ["tax" => $tax, "surcharge" => $surcharge, "rate" => $coeffR[$i]];
}

// taxableIncome = annualSalary - deduction
// the 10% deduction has a minimum and a maximum
function getTaxableIncome(float $salary): float {

}

// calculates a possible tax deduction
function getDiscount(string $married, float $salary, float $taxes): float {

  // result
  return ceil($discount);
}

// calculates a possible reduction
function getDiscount(string $married, float $salary, int $children, float $taxes): float {

  // result
  return ceil($discount);
}

Comments

  • Line 10: The gross tax is calculated including children. The result is returned in the form ["tax" => $tax, "surcharge" => $surcharge, "rate" => $coeffR[$i]] where:
    • [‘tax’]: the gross tax;
    • [‘surcharge’]: the amount of the surcharge, if any. This applies when the 10% deduction exceeds the threshold of 12,502 euros;
    • [‘rate’]: the taxpayer’s tax rate;
  • line 11: the gross tax [impot1] due;
  • lines 13–14: if the taxpayer has at least one child, the tax calculation is redone using the same data but with 0 children. This second calculation is necessary to determine whether the reduction provided by the children (nbParts*coeffN) exceeds a certain threshold;
  • line 15: the gross tax [impot2] due;
  • lines 16–23: for the gross tax [impot2], children are now factored in: each 1/2 share provided by the children allows for a reduction of [PLAFOND_QF_DEMI_PART] euros;
  • lines 25–26: case where the taxpayer has no children. In this case, calculating [impot2] is unnecessary. It is equal to [impot1];
  • lines 29–37: two gross taxes have been calculated [impot1, impot2]. The tax authority retains the higher of the two. This yields a gross tax [impot];
  • lines 39–40: the gross amount [tax] may be subject to a reduction;
  • lines 42–43: the gross amount [impot] may be subject to a reduction;
  • line 45: [tax] is now the net tax due. The results are returned;

The function [calculImpot2] is as follows:


// --------------------------------------------------------------------------
function calculateTax2(string $married, int $children, float $salary, array $limits, array $coeffR, array $coeffN): array {
  // $marié: yes, no
  // $children: number of children
  // $salary: annual salary
  // $limits, $coeffR, $coeffN: data arrays used to calculate the tax
  //
  // number of shares
  $married = strtolower($married);
  if ($married === "yes") {
    $nbParts = $children / 2 + 2;
  } else {
    $numberOfShares = $children / 2 + 1;
  }
  // 1 share per child starting with the 3rd
  if ($children >= 3) {
    // half a portion more for each child starting with the third
    $nbParts += 0.5 * ($children - 2);
  }
  // taxable income
  $taxableIncome = getTaxableIncome($salary);
  // surcharge
  $surcharge = floor($taxableIncome - 0.9 * $salary);
  // for rounding issues
  if ($surcharge < 0) {
    $surcharge = 0;
  }
  // family quotient
  $quotient = $taxableIncome / $numberOfShares;
  // is added to the end of the limits array to stop the following loop
  $limits[count($limits) - 1] = $quotient;
  // calculate the tax
  $i = 0;
  while ($quotient > $limits[$i]) {
    $i++;
  }
  // Since we placed $quotient at the end of the $limites array, the previous loop
  // cannot go beyond the bounds of the $limits array
  // now we can calculate the tax
  $tax = floor($taxableIncome * $coeffR[$i] - $numberOfShares * $coeffN[$i]);
  // result
  return ["tax" => $tax, "surcharge" => $surcharge, "rate" => $coeffR[$i]];
}

// taxableIncome = annualSalary - deduction
// the deduction has a minimum and a maximum
function getTaxableIncome(float $salary): float {
  
  // result
  return floor($taxableIncome);
}

Comments

  • Here, we apply the tax calculation using the progressive tax scale;
  • line 9: strtolower($string) converts $string to lowercase;
  • lines 10–19: calculation of the taxpayer’s number of shares;
  • line 21: we calculate taxable income using a function. Indeed, we have seen that it is not always 0.9*AnnualIncome. The 10% deduction is in fact capped at 12,502 euros;
  • line 23: calculates the potential surcharge if the taxable income is greater than 0.9*annualIncome;
  • lines 25–27: corrects the fact that, due to rounding errors, we sometimes have [$surcharge=-1];
  • line 29: the family quotient;
  • lines 30–36: this quotient is used to determine the taxpayer’s tax bracket;
  • line 40: once the taxpayer’s tax bracket has been determined, their gross tax can be calculated. The floor($x) function returns the integer immediately less than [$x];
  • line 42: the calculated information is returned;

The [getRevenuImposable] function is as follows:


// global constants
define("MAX_10_PERCENT_DEDUCTION", 12502);
define("ABATTEMENT_DIXPOURCENT_MIN", 437);

// taxableIncome = annualSalary - deduction
// the deduction has a minimum and a maximum
function getTaxableIncome(float $salary): float {
  // 10% deduction from salary
  $deduction = 0.1 * $salary;
  // this deduction cannot exceed MAX_10_PERCENT_DEDUCTION
  if ($deduction > TENPERCENT_MAX_DEDUCTION) {
    $deduction = MAX_10PERCENT_DEDUCTION;
  }
  // the deduction cannot be less than ABATTEMENT_DIXPOURCENT_MIN
  if ($discount < DISCOUNT_TENPERCENT_MIN) {
    $discount = MINIMUM_TEN_PERCENT_DISCOUNT;
  }
  // taxable income
  $taxableIncome = $salary - $deduction;
  // result
  return floor($taxableIncome);
}

Comments

  • line 5: the standard deduction is 10% of the annual salary;
  • lines 7–9: the deduction cannot exceed the maximum deduction [ABATTEMENT_DIXPOURCENT_MAX];
  • lines 10–13: the deduction cannot be less than the minimum deduction [ABATTEMENT_DIXPOURCENT_MIN];
  • line 15: calculation of taxable income;

The [getDecote] function is as follows:


// global constants
define("SINGLE_DEDUCTION_CAP", 1196);
define("COUPLE_DEDUCTION_LIMIT", 1970);
define("COUPLE_TAX_THRESHOLD_FOR_DEDUCTION", 2627);
define("SINGLE_TAX_THRESHOLD_FOR_DISCOUNT", 1595);

// calculates a possible tax deduction
function getDiscount(string $married, float $salary, float $taxes): float {
  // initially, a zero discount
  $discount = 0;
  // maximum tax amount to qualify for the deduction
  $taxThresholdForDeduction = $married === "yes" ? TAX_THRESHOLD_FOR_COUPLES_FOR_DEDUCTION : TAX_THRESHOLD_FOR_SINGLES_FOR_DEDUCTION;
  if ($taxes < $taxThresholdForDeduction) {
    // maximum discount amount
    $discountLimit = $married === "yes" ? COUPLE_DISCOUNT_LIMIT : SINGLE_DISCOUNT_LIMIT;
    // theoretical deduction
    $discount = $discount_limit - 0.75 * $taxes;
    // the deduction cannot exceed the tax amount
    if ($discount > $taxes) {
      $discount = $taxes;
    }
    // no discount <0
    if ($discount < 0) {
      $discount = 0;
    }
  }
  // result
  return ceil($discount);
}

Comments

  • line 6: maximum gross tax amount to qualify for a tax reduction. This amount differs for single individuals and couples;
  • line 7: whether the taxpayer is eligible for the tax reduction;
  • line 11: the tax credit formula. [taxCreditCap] is the maximum tax credit amount. This maximum amount is calculated on line 9. Again, it depends on the taxpayer’s status—married or single;
  • lines 13–15: the deduction cannot exceed the gross tax due. This is the case, for example, if [taxes] is 0 on line 11;
  • lines 17–19: to avoid rounding to -1;

The [getReduction] function is as follows:


// global constants
define("INCOME_THRESHOLD_FOR_SINGLE_PERSON_REDUCTION", 21037);
define("COUPLE_INCOME_LIMIT_FOR_REDUCTION", 42074);
define("REDUCTION_VALUE_FOR_HALF_SHARE", 3797);

// calculates a possible reduction
function getReduction(string $married, float $salary, int $children, float $taxes): float {
  // the income ceiling to qualify for the 20% reduction
  $incomeThresholdForReduction = $married === "yes" ? COUPLE_INCOME_THRESHOLD_FOR_REDUCTION : SINGLE_INCOME_THRESHOLD_FOR_REDUCTION;
  $IncomeThresholdForReduction += $children * HALF_SHARE_REDUCTION_VALUE;
  if ($children > 2) {
    $IncomeLimitForReduction += ($children - 2) * HALF_SHARE_REDUCTION_VALUE;
  }
  // taxable income
  $taxableIncome = getTaxableIncome($salary);
  // deduction
  $reduction = 0;
  if ($taxableIncome < $IncomeThresholdForDeduction) {
    // 20% reduction
    $reduction = 0.2 * $taxes;
  }
  // result
  return ceil($reduction);
}

Comments

  • lines 4–10: to be eligible for a tax reduction, the taxable income (line 10) must be less than a threshold calculated in lines 4–8;
  • lines 13–16: if the conditions are met, the taxpayer is entitled to a 20% tax reduction (line 15);

4.3.2. Results

The taxpayersdata.txt data file (married, children, salary):

yes,2,55555
yes,2,50000
yes,3,50000
no,2,100000
no,3,100000
yes,3,100000
yes,5,100000
no,0,100000
yes,2,30000
no,0,200000
yes,3,200000

The results.txt file (married, children, salary, tax, surcharge, discount, reduction, tax rate) of the results obtained:

{"married":"yes","children":"2","salary":"55555","tax":2814,"surcharge":0,"discount":0,"reduction":0,"rate":0.14}
{"married":"yes","children":"2","salary":"50000","tax":1384,"surcharge":0,"discount":384,"reduction":347,"rate":0.14}
{"married":"yes","children":"3","salary":"50000","tax":0,"surcharge":0,"discount":720,"reduction":0,"rate":0.14}
{"married":"no","children":"2","salary":"100000","tax":19884,"surcharge":4480,"discount":0,"reduction":0,"rate":0.41}
{"married":"no","children":"3","salary":"100000","tax":16782,"surcharge":7176,"discount":0,"reduction":0,"rate":0.41}
{"married":"yes","children":"3","salary":"100000","tax":9200,"surcharge":2180,"discount":0,"reduction":0,"rate":0.3}
{"married":"yes","children":"5","salary":"100000","tax":4230,"surcharge":0,"discount":0,"reduction":0,"rate":0.14}
{"married":"no","children":"0","salary":"100000","tax":22986,"surcharge":0,"discount":0,"reduction":0,"rate":0.41}
{"married":"yes","children":"2","salary":"30000","tax":0,"surcharge":0,"discount":0,"reduction":0,"rate":0}
{"married":"no","children":"0","salary":"200000","tax":64210,"surcharge":7498,"discount":0,"reduction":0,"rate":0.45}
{"married":"yes","children":"3","salary":"200000","tax":42842,"surcharge":17283,"discount":0,"reduction":0,"rate":0.41}

The results obtained are consistent with those obtained using the tax authority’s simulator.

4.3.3. Conclusion

The tax calculation algorithm, even in cases considered straightforward, is complex. We will not revisit it here. Across different versions, its core will remain the same despite some changes in presentation. We will therefore comment only on the latter.

4.4. Version 2

4.4.1. Changes

In the previous version, the data required to calculate the tax was hard-coded as constants and arrays. This method should be avoided. In the new version, this data is externalized into a JSON file:

Image

The contents of the [taxadmindata.json] file are as follows:


{
    "limits": [9964, 27519, 73779, 156244, 0],
    "coeffR": [0, 0.14, 0.3, 0.41, 0.45],
    "coeffN": [0, 1394.96, 5798, 13913.69, 20163.45],
    "HALF-RATE_INCOME_LIMIT": 1551,
    "INCOME_THRESHOLD_SINGLE_PERSON_FOR_REDUCTION": 21037,
    "INCOME_LIMIT_FOR_COUPLES_FOR_REDUCTION": 42074,
    "REDUCTION_VALUE_HALF_PORTION": 3797,
    "SINGLE_DISCOUNT_LIMIT": 1196,
    "COUPLE_DEDUCTION_LIMIT": 1970,
    "COUPLE_TAX_THRESHOLD_FOR_DEDUCTION": 2627,
    "SINGLE_TAX_LIMIT_FOR_DEDUCTION": 1595,
    "MAXIMUM_10_PERCENT_DEDUCTION": 12502,
    "MINIMUM_10_PERCENT_DEDUCTION": 437
}

The new version [version-02/main.php] is as follows:


<?php

// Strict adherence to the declared types of function parameters
declare (strict_types=1);

// definition of constants
$TAXPAYERSDATA = "taxpayersdata.txt";
$RESULTS = "results.txt";
$TAXADMINDATA = "taxadmindata.json";

// retrieve the contents of the tax data file
$fileContents = \file_get_contents($TAXADMINDATA);
$error = FALSE;
// error?
if (!$fileContents) {
  // log the error
  $error = TRUE;
  $message = "The data file [$TAXADMINDATA] does not exist";
}

if (!$error) {
  // Retrieve the JSON data from the configuration file into an associative array
  $taxAdminData = \json_decode($fileContents, true);
  // Error?
  if (!$taxAdminData) {
    // log the error
    $error = TRUE;
    $message = "The JSON data file [$TAXADMINDATA] could not be processed correctly";
  }
}

// error?
if ($error) {
  print "$message\n";
  exit;
}

// Open the results file
$results = fopen($RESULTS, "w");
if (!$results) {
  print "Unable to create the results file [$RESULTS]\n";
  // exit
  exit;
}

// open taxpayer data file
$taxpayersdata = fopen($TAXPAYERSDATA, "r");
if (!$taxpayersdata) {
  print "Unable to open the taxpayer file [$TAXPAYERSDATA]\n";
  // exit
  exit;
}

// process the current line of the data file
while ($line = fgets($taxpayersdata, 100)) {
  // remove any end-of-line characters
  $line = cutNewLineChar($line);
  // retrieve the 3 fields married:children:salary that make up $line
  list($married, $children, $salary) = explode(",", $line);
  // calculate the tax
  $result = calculateTax($taxAdminData, $married, (int) $children, (int) $salary);
  // write the result to the results file
  $result = ["married" => $married, "children" => $children, "salary" => $salary] + $result;
  fputs($results, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
  // next data
}
// close the files
fclose($taxpayersdata);
fclose($results);

// end
exit;

// --------------------------------------------------------------------------
function cutNewLinechar(string $line) {

  // end
  return($line);
}

// tax calculation
// --------------------------------------------------------------------------
function calculateTax(array $taxAdminData, string $married, int $children, float $salary) {
  // $married: yes, no
  // $children: number of children
  // $salary: annual salary
  // $taxAdminData: data from the tax authorities
  //
  // Calculate tax with children
  $result1 = calculateTax2($taxAdminData, $married, $children, $salary);
  $tax1 = $result1["tax"];
  // Calculate tax without children
  if ($children != 0) {
    $result2 = calculateTax2($taxAdminData, $married, 0, $salary);
    $tax2 = $result2["tax"];
    // apply the family quotient cap
    if ($children < 3) {
      // $PLAFOND_QF_DEMI_PART euros for the first 2 children
      $tax2 = $tax2 - $children * $taxAdminData["HALF-SHARE_FAMILY_QUOTIENT_CAP"];
    } else {
      // $PLAFOND_QF_DEMI_PART euros for the first 2 children, double that for subsequent children
      $tax2 = $tax2 - 2 * $taxAdminData["PLAFOND_QF_DEMI_PART"] - ($children - 2) * 2 * $taxAdminData["PLAFOND_QF_DEMI_PART"];
    }
  } else {
    $tax2 = $tax1;
    $result2 = $result1;
  }
  // we take the higher tax amount with the corresponding rate and surcharge
  if ($tax1 > $tax2) {
    $tax = $tax1;
    $rate = $result1["rate"];
    $surcharge = $result1["surcharge"];
  } else {
    $surcharge = $tax2 - $tax1 + $result2["surcharge"];
    $tax = $tax2;
    $rate = $result2["rate"];
  }
  // Calculate any tax credit
  $discount = getDiscount($taxAdminData, $married, $salary, $tax);
  $tax -= $discount;
  // Calculate any tax reduction
  $reduction = getReduction($taxAdminData, $married, $salary, $children, $tax);
  $tax -= $reduction;
  // result
  return ["tax" => floor($tax), "surcharge" => $surcharge, "discount" => $discount, "reduction" => $reduction, "rate" => $rate];
}

// --------------------------------------------------------------------------
function calculateTax2(array $taxAdminData, string $married, int $children, float $salary) {
  // $marié: yes, no

  // result
  return ["tax" => $tax, "surcharge" => $surcharge, "rate" => $coeffR[$i]];
}

// taxableIncome = annualSalary - deduction
// the deduction has a minimum and a maximum
function getTaxableIncome(array $taxAdminData, float $salary): float {

  // result
  return floor($taxableIncome);
}

// calculates any tax deduction
function getDiscount(array $taxAdminData, string $married, float $salary, float $taxes): float {

  // result
  return ceil($discount);
}

// calculates a possible reduction
function getDiscount(array $taxAdminData, string $married, float $salary, int $children, float $taxes): float {

  // result
  return ceil($reduction);
}

Comments

  • lines 11–19: we attempt to read the contents of the JSON file named [TAXADMINDATA];
  • lines 21–30: if the JSON file was successfully read, its contents are decoded into the associative array [$taxAdminData];
  • lines 32–36: if an error occurred in either of the two previous operations, an error message is written to the console and the program stops;
  • The difference from version 01 is that here the tax administration data (arrays and constants) are stored in the associative array [$taxAdminData], whereas in version 01, they were stored in global arrays and constants. It is the global nature of these constants that distinguishes the two versions:
    • in version 01, the constants were known in all functions of [main.php];
    • to achieve the same result in version 02, the associative array [$taxAdminData] must be passed as a parameter to all functions (lines 83, 129, 138, 145, 152);
  • each function in version 02 must use the contents of the array [$taxAdminData];
  • Lines 83–126: In the [calculateTax] function, where global constants or the [limits, coeffR, coeffN] arrays were previously used, we now use the contents of the [$taxAdminData] array passed as a parameter (lines 99, 102);
  • All other functions are rewritten in the same way;

The results obtained are the same as those obtained in the previous version.

4.4.2. Conclusion

Version 02 is much more flexible than version 01. In 2020, the tax calculation algorithm will likely be the same as in 2019. Only the tax brackets and calculation constants will have changed. It will then suffice to update the [taxadmindata.json] file. With version 01, you have to go into the code to modify the tax brackets and calculation constants. However, it is likely that the people who need to change the values of the tax brackets and calculation constants do not have access to the algorithm’s code.