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:

    27519        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        20163.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    1394.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("PLAFOND_QF_DEMI_PART", 1551);
define("PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION", 21037);
define("PLAFOND_REVENUS_COUPLE_POUR_REDUCTION", 42074);
define("VALEUR_REDUC_DEMI_PART", 3797);
define("PLAFOND_DECOTE_CELIBATAIRE", 1196);
define("PLAFOND_DECOTE_COUPLE", 1970);
define("PLAFOND_IMPOT_COUPLE_POUR_DECOTE", 2627);
define("PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE", 1595);
define("ABATTEMENT_DIXPOURCENT_MAX", 12502);
define("ABATTEMENT_DIXPOURCENT_MIN", 437);
 
// definition of local constants
$DATA = "taxpayersdata.txt";
$RESULTATS = "resultats.txt";
$limites = 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);
 
// reading data
$data = fopen($DATA, "r");
if (!$data) {
  print "Impossible d'ouvrir en lecture le fichier des données [$DATA]\n";
  exit;
}
 
// open results file
$résultats = fopen($RESULTATS, "w");
if (!$résultats) {
  print "Impossible de créer le fichier des résultats [$RESULTATS]\n";
  exit;
}
 
// use the current line of the data file
while ($ligne = fgets($data, 100)) {
  // remove any end-of-line marker
  $ligne = cutNewLineChar($ligne);
  // we retrieve the 3 fields married:children:salary which form $ligne
  list($marié, $enfants, $salaire) = explode(",", $ligne);
  // tax calculation
  $result = calculImpot($marié, (int) $enfants, (float) $salaire, $limites, $coeffR, $coeffN);
  // enter the result in the results file
  $result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
  fputs($résultats, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
  // following data
}
// close files
fclose($data);
fclose($résultats);
 
// end
exit;
 
// --------------------------------------------------------------------------
function cutNewLinechar(string $ligne): string {
  // delete the end-of-line mark from $ligne if it exists
  $L = strlen($ligne);  // line length
  while (substr($ligne, $L - 1, 1) === "\n" or substr($ligne, $L - 1, 1) === "\r") {
    $ligne = substr($ligne, 0, $L - 1);
    $L--;
  }
  // end
  return($ligne);
}
 
// tAX CALCULATION
// --------------------------------------------------------------------------
function calculImpot(string $marié, int $enfants, float $salaire, array $limites, array $coeffR, array $coeffN): array {

  // result
  return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
}
 
// --------------------------------------------------------------------------
function calculImpot2(string $marié, int $enfants, float $salaire, array $limites, array $coeffR, array $coeffN): array {

  // result
  return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
 
// revenuImposable=annualwage-discount
// the allowance has a minimum and a maximum
function getRevenuImposable(float $salaire): float {

  // result
  return floor($revenuImposable);
}
 
// calculates any discount
function getDecote(string $marié, float $salaire, float $impots): float {

  // result
  return ceil($décôte);
}
 
// calculates any reduction
function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
  /
  // result
  return ceil($réduction);
}

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

oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3,100000
oui,3,100000
oui,5,100000
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

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

{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"oui","enfants":"2","salaire":"50000","impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}
{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}
{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}
{"marié":"oui","enfants":"3","salaire":"200000","impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":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:


// constantes globales
define("PLAFOND_QF_DEMI_PART", 1551);

// calcul de l'impôt
// --------------------------------------------------------------------------
function calculImpot(string $marié, int $enfants, float $salaire, array $limites, array $coeffR, array $coeffN): array {
  // $marié : oui, non
  // $enfants : nombre d'enfants
  // $salaire : salaire annuel
  // $limites, $coeffR, $coeffN : les tableaux des données permettant le calcul de l'impôt
  //
  // calcul de l'impôt avec enfants
  $result1 = calculImpot2($marié, $enfants, $salaire, $limites, $coeffR, $coeffN);
  $impot1 = $result1["impôt"];
  // calcul de l'impôt sans les enfants
  if ($enfants != 0) {
    $result2 = calculImpot2($marié, 0, $salaire, $limites, $coeffR, $coeffN);
    $impot2 = $result2["impôt"];
    // application du plafonnement du quotient familial
    if ($enfants < 3) {
      // $PLAFOND_QF_DEMI_PART euros pour les 2 premiers enfants
      $impot2 = $impot2 - $enfants * PLAFOND_QF_DEMI_PART;
    } else {
      // $PLAFOND_QF_DEMI_PART euros pour les 2 premiers enfants, le double pour les suivants
      $impot2 = $impot2 - 2 * PLAFOND_QF_DEMI_PART - ($enfants - 2) * 2 * PLAFOND_QF_DEMI_PART;
    }
  } else {
    $impot2 = $impot1;
    $result2 = $result1;
  }
  // on prend l'impôt le plus fort avec le taux et la surcôte qui vont avec
  if ($impot1 > $impot2) {
    $impot = $impot1;
    $taux = $result1["taux"];
    $surcôte = $result1["surcôte"];
  } else {
    $surcôte = $impot2 - $impot1 + $result2["surcôte"];
    $impot = $impot2;
    $taux = $result2["taux"];
  }
  // calcul d'une éventuelle décôte
  $décôte = getDecote($marié, $salaire, $impot);
  $impot -= $décôte;
  // calcul d'une éventuelle réduction d'impôts
  $réduction = getRéduction($marié, $salaire, $enfants, $impot);
  $impot -= $réduction;
  // résultat
  return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
}
 
// --------------------------------------------------------------------------
function calculImpot2(string $marié, int $enfants, float $salaire, array $limites, array $coeffR, array $coeffN): array {

  // résultat
  return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
 
// revenuImposable=salaireAnnuel-abattement
// l'abattement de 10 % a un min et un max
function getRevenuImposable(float $salaire): float {

}
 
// calcule une décôte éventuelle
function getDecote(string $marié, float $salaire, float $impots): float {

  // résultat
  return ceil($décôte);
}
 
// calcule une réduction éventuelle
function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {

  // résultat
  return ceil($réduction);
}

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 calculImpot2(string $marié, int $enfants, float $salaire, array $limites, array $coeffR, array $coeffN): array {
  // $marié : oui, non
  // $enfants : nombre d'enfants
  // $salaire : salaire annuel
  // $limites, $coeffR, $coeffN : les tableaux des données permettant le calcul de l'impôt
  //
  // nombre de parts
  $marié = strtolower($marié);
  if ($marié === "oui") {
    $nbParts = $enfants / 2 + 2;
  } else {
    $nbParts = $enfants / 2 + 1;
  }
  // 1 part par enfant à partir du 3e
  if ($enfants >= 3) {
    // une demi-part de + pour chaque enfant à partir du 3e
    $nbParts += 0.5 * ($enfants - 2);
  }
  // revenu imposable
  $revenuImposable = getRevenuImposable($salaire);
  // surcôte
  $surcôte = floor($revenuImposable - 0.9 * $salaire);
  // pour des pbs d'arrondi
  if ($surcôte < 0) {
    $surcôte = 0;
  }
  // quotient familial
  $quotient = $revenuImposable / $nbParts;
  // est mis à la fin du tableau limites pour arrêter la boucle qui suit
  $limites[count($limites) - 1] = $quotient;
  // calcul de l'impôt
  $i = 0;
  while ($quotient > $limites[$i]) {
    $i++;
  }
  // du fait qu'on a placé $quotient à la fin du tableau $limites, la boucle précédente
  // ne peut déborder du tableau $limites
  // maintenant on peut calculer l'impôt
  $impôt = floor($revenuImposable * $coeffR[$i] - $nbParts * $coeffN[$i]);
  // résultat
  return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
 
// revenuImposable=salaireAnnuel-abattement
// l'abattement a un min et un max
function getRevenuImposable(float $salaire): float {
  
  // résultat
  return floor($revenuImposable);
}
 

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:


// constantes globales
define("ABATTEMENT_DIXPOURCENT_MAX", 12502);
define("ABATTEMENT_DIXPOURCENT_MIN", 437);

// revenuImposable=salaireAnnuel-abattement
// l'abattement a un min et un max
function getRevenuImposable(float $salaire): float {
  // abattement de 10% du salaire
  $abattement = 0.1 * $salaire;
  // cet abattement ne peut dépasser ABATTEMENT_DIXPOURCENT_MAX
  if ($abattement > ABATTEMENT_DIXPOURCENT_MAX) {
    $abattement = ABATTEMENT_DIXPOURCENT_MAX;
  }
  // l'abattement ne peut être inférieur à ABATTEMENT_DIXPOURCENT_MIN
  if ($abattement < ABATTEMENT_DIXPOURCENT_MIN) {
    $abattement = ABATTEMENT_DIXPOURCENT_MIN;
  }
  // revenu imposable
  $revenuImposable = $salaire - $abattement;
  // résultat
  return floor($revenuImposable);
}

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:


// constantes globales
define("PLAFOND_DECOTE_CELIBATAIRE", 1196);
define("PLAFOND_DECOTE_COUPLE", 1970);
define("PLAFOND_IMPOT_COUPLE_POUR_DECOTE", 2627);
define("PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE", 1595);

// calcule une décôte éventuelle
function getDecote(string $marié, float $salaire, float $impots): float {
  // au départ, une décôt nulle
  $décôte = 0;
  // montant maximal d'tax to get the discount
  $plafondImpôtPourDécôte = $marié === "oui" ? PLAFOND_IMPOT_COUPLE_POUR_DECOTE : PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE;
  if ($impots < $plafondImpôtPourDécôte) {
    // montant maximal de la décôte
    $plafondDécôte = $marié === "oui" ? PLAFOND_DECOTE_COUPLE : PLAFOND_DECOTE_CELIBATAIRE;
    // décôte théorique
    $décôte = $plafondDécôte - 0.75 * $impots;
    // la décôte ne peut dépasser le montant de l'tax
    if ($décôte > $impots) {
      $décôte = $impots;
    }
    // pas de décôte <0
    if ($décôte < 0) {
      $décôte = 0;
    }
  }
  // résultat
  return ceil($décôte);
}

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:


// constantes globales
define("PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION", 21037);
define("PLAFOND_REVENUS_COUPLE_POUR_REDUCTION", 42074);
define("VALEUR_REDUC_DEMI_PART", 3797);

// calcule une réduction éventuelle
function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
  // le plafond des revenus pour avoir droit à la réduction de 20%
  $plafondRevenuPourRéduction = $marié === "oui" ? PLAFOND_REVENUS_COUPLE_POUR_REDUCTION : PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION;
  $plafondRevenuPourRéduction += $enfants * VALEUR_REDUC_DEMI_PART;
  if ($enfants > 2) {
    $plafondRevenuPourRéduction += ($enfants - 2) * VALEUR_REDUC_DEMI_PART;
  }
  // revenu imposable
  $revenuImposable = getRevenuImposable($salaire);
  // réduction
  $réduction = 0;
  if ($revenuImposable < $plafondRevenuPourRéduction) {
    // réduction de 20%
    $réduction = 0.2 * $impots;
  }
  // résultat
  return ceil($réduction);
}

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):

oui,2,55555
oui,2,50000
oui,3,50000
non,2,100000
non,3,100000
oui,3,100000
oui,5,100000
non,0,100000
oui,2,30000
non,0,200000
oui,3,200000

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

{"marié":"oui","enfants":"2","salaire":"55555","impôt":2814,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"oui","enfants":"2","salaire":"50000","impôt":1384,"surcôte":0,"décôte":384,"réduction":347,"taux":0.14}
{"marié":"oui","enfants":"3","salaire":"50000","impôt":0,"surcôte":0,"décôte":720,"réduction":0,"taux":0.14}
{"marié":"non","enfants":"2","salaire":"100000","impôt":19884,"surcôte":4480,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"non","enfants":"3","salaire":"100000","impôt":16782,"surcôte":7176,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":"3","salaire":"100000","impôt":9200,"surcôte":2180,"décôte":0,"réduction":0,"taux":0.3}
{"marié":"oui","enfants":"5","salaire":"100000","impôt":4230,"surcôte":0,"décôte":0,"réduction":0,"taux":0.14}
{"marié":"non","enfants":"0","salaire":"100000","impôt":22986,"surcôte":0,"décôte":0,"réduction":0,"taux":0.41}
{"marié":"oui","enfants":"2","salaire":"30000","impôt":0,"surcôte":0,"décôte":0,"réduction":0,"taux":0}
{"marié":"non","enfants":"0","salaire":"200000","impôt":64210,"surcôte":7498,"décôte":0,"réduction":0,"taux":0.45}
{"marié":"oui","enfants":"3","salaire":"200000","impôt":42842,"surcôte":17283,"décôte":0,"réduction":0,"taux":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:


{
    "limites": [9964, 27519, 73779, 156244, 0],
    "coeffR": [0, 0.14, 0.3, 0.41, 0.45],
    "coeffN": [0, 1394.96, 5798, 13913.69, 20163.45],
    "PLAFOND_QF_DEMI_PART": 1551,
    "PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION": 21037,
    "PLAFOND_REVENUS_COUPLE_POUR_REDUCTION": 42074,
    "VALEUR_REDUC_DEMI_PART": 3797,
    "PLAFOND_DECOTE_CELIBATAIRE": 1196,
    "PLAFOND_DECOTE_COUPLE": 1970,
    "PLAFOND_IMPOT_COUPLE_POUR_DECOTE": 2627,
    "PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE": 1595,
    "ABATTEMENT_DIXPOURCENT_MAX": 12502,
    "ABATTEMENT_DIXPOURCENT_MIN": 437
}

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


<?php
 
// strict adherence to declared function parameter types
declare (strict_types=1);
 
// definition of constants
$TAXPAYERSDATA = "taxpayersdata.txt";
$RESULTATS = "resultats.txt";
$TAXADMINDATA = "taxadmindata.json";
 
// retrieve the contents of the tax data file
$fileContents = \file_get_contents($TAXADMINDATA);
$erreur = FALSE;
// mistake?
if (!$fileContents) {
  // we note the error
  $erreur = TRUE;
  $message = "Le fichier des données [$TAXADMINDATA] n'existe pas";
}
 
if (!$erreur) {
  // retrieve the jSON code from the configuration file in an associative array
  $taxAdminData = \json_decode($fileContents, true);
  // mistake?
  if (!$taxAdminData) {
    // we note the error
    $erreur = TRUE;
    $message = "Le fichier de données jSON [$TAXADMINDATA] n'a pu être exploité correctement";
  }
}
 
// mistake?
if ($erreur) {
  print "$message\n";
  exit;
}
 
// open results file
$résultats = fopen($RESULTATS, "w");
if (!$résultats) {
  print "Impossible de créer le fichier des résultats [$RESULTATS]\n";
  // output
  exit;
}
 
// opening taxpayer data file
$taxpayersdata = fopen($TAXPAYERSDATA, "r");
if (!$taxpayersdata) {
  print "Impossible d'ouvrir le fichier des contribuables [$TAXPAYERSDATA]\n";
  // output
  exit;
}
 
// use the current line of the data file
while ($ligne = fgets($taxpayersdata, 100)) {
  // remove any end-of-line marker
  $ligne = cutNewLineChar($ligne);
  // we retrieve the 3 fields married:children:salary which form $ligne
  list($marié, $enfants, $salaire) = explode(",", $ligne);
  // tax calculation
  $result = calculImpot($taxAdminData, $marié, (int) $enfants, (int) $salaire);
  // enter the result in the results file
  $result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
  fputs($résultats, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
  // following data
}
// close files
fclose($taxpayersdata);
fclose($résultats);
 
// end
exit;
 
// --------------------------------------------------------------------------
function cutNewLinechar(string $ligne) {

  // end
  return($ligne);
}
 
// tAX CALCULATION
// --------------------------------------------------------------------------
function calculImpot(array $taxAdminData, string $marié, int $enfants, float $salaire) {
  // $marié : yes, no
  // $enfants : number of children
  // $salaire: annual salary
  // $taxAdminData: tax administration data
  //
  // tax calculation with children
  $result1 = calculImpot2($taxAdminData, $marié, $enfants, $salaire);
  $impot1 = $result1["impôt"];
  // tax calculation without children
  if ($enfants != 0) {
    $result2 = calculImpot2($taxAdminData, $marié, 0, $salaire);
    $impot2 = $result2["impôt"];
    // application of the family allowance ceiling
    if ($enfants < 3) {
      // $PLAFOND_QF_DEMI_PART euros for the first 2 children
      $impot2 = $impot2 - $enfants * $taxAdminData["PLAFOND_QF_DEMI_PART"];
    } else {
      // $PLAFOND_QF_DEMI_PART euros for the first 2 children, double for subsequent children
      $impot2 = $impot2 - 2 * $taxAdminData["PLAFOND_QF_DEMI_PART"] - ($enfants - 2) * 2 * $taxAdminData["PLAFOND_QF_DEMI_PART"];
    }
  } else {
    $impot2 = $impot1;
    $result2 = $result1;
  }
  // we take the highest tax with the rate and surcharge that go with it
  if ($impot1 > $impot2) {
    $impot = $impot1;
    $taux = $result1["taux"];
    $surcôte = $result1["surcôte"];
  } else {
    $surcôte = $impot2 - $impot1 + $result2["surcôte"];
    $impot = $impot2;
    $taux = $result2["taux"];
  }
  // calculation of any discount
  $décôte = getDecote($taxAdminData, $marié, $salaire, $impot);
  $impot -= $décôte;
  // calculation of any tax reduction
  $réduction = getRéduction($taxAdminData, $marié, $salaire, $enfants, $impot);
  $impot -= $réduction;
  // result
  return ["impôt" => floor($impot), "surcôte" => $surcôte, "décôte" => $décôte, "réduction" => $réduction, "taux" => $taux];
}
 
// --------------------------------------------------------------------------
function calculImpot2(array $taxAdminData, string $marié, int $enfants, float $salaire) {
  // $marié : yes, no

  // result
  return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
 
// revenuImposable=annualwage-discount
// the allowance has a minimum and a maximum
function getRevenuImposable(array $taxAdminData, float $salaire): float {

  // result
  return floor($revenuImposable);
}
 
// calculates any discount
function getDecote(array $taxAdminData, string $marié, float $salaire, float $impots): float {

  // result
  return ceil($décôte);
}
 
// calculates any reduction
function getRéduction(array $taxAdminData, string $marié, float $salaire, int $enfants, float $impots): float {

  // result
  return ceil($réduction);
}

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.