4. Application Exercise – Versions 1 and 2
4.1. The Problem

The table above allows for calculating the tax in the simplified case of a taxpayer who has only their salary to report. As indicated in note (1), the tax calculated in 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-income earners;
Thus, the tax calculation involves the following steps [http://impotsurlerevenu.org/comprendre-le-calcul-de-l-impot/1217-calcul-de-l-impot-2019.php]:

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, calculate the taxpayer’s number of shares:
- each parent contributes 1 share;
- the first two children each contribute 1/2 share;
- subsequent children each contribute one share:
The number of shares is therefore:
- nbParts=1+nbEnfants*0.5+(numberOfChildren-2)*0.5 if the employee is unmarried;
- nbParts = 2 + nbChildren * 0.5 + (nbChildren - 2) * 0.5 if he is married;
where nbEnfants 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 nbParts/R;
- 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:
Tax I is then equal to 0.14*R – 1394.96*nbParts=[0,14*45000-1394,96*3]=2115. The tax is rounded down to the nearest euro.
If the relationship QF <= field1 holds from the first line onward, 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:
which gives the gross tax I = 0.45*R – 20163.45*nbParts.
4.1.2. Family Quotient Cap

To determine whether the family quotient cap QF 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:
Tax I is then equal to 0.14*R – 1394.96*nbParts=[0,14*45000-1394,96*2]=3510.
Maximum child-related allowance: 1551 * 2 = 3102 euros
Minimum tax: 3510 – 3102 = 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:
- [impôt1]: is the gross tax calculated with children;
- [impôt2]: is the gross tax calculated without children and reduced by the maximum allowance (here 1,551 euros per half-share) related to children;
4.1.3. Calculation of the tax reduction

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

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 tax net
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;
- his 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 the document, tests will be conducted using the following examples. The results are those from the tax administration’s simulator:
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 Tax rate = 14% |
Couple with 2 children and an annual income of 50,000 euros | Tax = €1,385 Tax credit = €720 Reduction = 0 euros Tax rate = 14% | Tax = €1,384 Discount = 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 Reduction = €346 Tax rate = 14% | Tax = 0 euros Discount = 720 euros Deduction = 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 3 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 Discount = 0 euros Credit = 0 euros Tax rate = 14% |
Single with no children and an annual income of 100,000 euros | Tax = €22,986 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 with no children and an 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 quotient;
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 amounts in the document’s algorithm were rounded:
- up to the nearest 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

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 tables and constants;
- taxpayer data (married, children, salary) is 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):
The résultats.txt file (married, children, salary, tax, surcharge, discount, reduction, tax rate) of the results obtained:
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 tables defining the different tax brackets for the tax calculation;
- lines 26–30: Open the taxpayer data file for reading using [r]. The function [fopen] returns the Boolean value FALSE if the file could not be opened;
- lines 33–37: Open the results file for writing using [w];
- lines 40–51: loop to read the lines (spouse, children, salary) 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 an end-of-line character is encountered, it is included in the returned string. When the end of the file is reached, the function [fgets] returns the value FALSE;
- line 42: the end-of-line character is removed;
- line 44: the components (spouse, 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 [marié, enfants, salaire] are added to the previously retrieved array;
- line 49: the result is stored in the results file as a string jSON;
- lines 53–54: once the taxpayer data file has been fully processed, the files are closed;
- line 60: the function that removes the line-end character from a line $ligne. The line-end character is the string "\r\n" on Windows systems, "\n" on Unix systems. The result is the input string without its line-end character.
- Lines 63–64: substr($chaîne, $début,$taille) is the substring of $chaîne starting at the character $début and having at most $taille characters;
The function [calculImpot] is as follows:
// global constants
define("PLAFOND_QF_DEMI_PART", 1551);
// tAX CALCULATION
// --------------------------------------------------------------------------
function calculImpot(string $marié, int $enfants, float $salaire, array $limites, array $coeffR, array $coeffN): array {
// $marié : yes, no
// $enfants : number of children
// $salaire: annual salary
// $limites, $coeffR, $coeffN: tax calculation data tables
//
// tax calculation with children
$result1 = calculImpot2($marié, $enfants, $salaire, $limites, $coeffR, $coeffN);
$impot1 = $result1["impôt"];
// tax calculation without children
if ($enfants != 0) {
$result2 = calculImpot2($marié, 0, $salaire, $limites, $coeffR, $coeffN);
$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 * PLAFOND_QF_DEMI_PART;
} else {
// $PLAFOND_QF_DEMI_PART euros for the first 2 children, double for subsequent children
$impot2 = $impot2 - 2 * PLAFOND_QF_DEMI_PART - ($enfants - 2) * 2 * 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($marié, $salaire, $impot);
$impot -= $décôte;
// calculation of any tax reduction
$réduction = getRéduction($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(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 10% allowance has a minimum and a maximum
function getRevenuImposable(float $salaire): float {
…
}
// 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);
}
Comments
- line 10: the gross tax is calculated with the children. The result is in the form ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]] with:
- [‘impôt’]: the gross tax;
- [‘surcôte’]: the amount of the surcharge, if any. This applies when the 10% deduction exceeds the threshold of 12,502 euros;
- [‘taux’]: 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 resulting from having children (nbParts*coeffN) exceeds a certain threshold;
- line 15: the gross tax [impot2] due;
- lines 16–23: for the gross tax [impot2], we now factor in the children: each 1/2 share contributed 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 tax amounts have been calculated: [impot1, impot2]. The tax authority retains the higher of the two. This results in a gross tax amount of [impot];
- lines 39–40: the gross amount [impot] may be subject to a reduction;
- lines 42–43: the gross amount [impot] may be subject to a reduction;
- line 45: [impot] is now the tax net to be paid. 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é : yes, no
// $enfants : number of children
// $salaire: annual salary
// $limites, $coeffR, $coeffN: tax calculation data tables
//
// number of shares
$marié = strtolower($marié);
if ($marié === "oui") {
$nbParts = $enfants / 2 + 2;
} else {
$nbParts = $enfants / 2 + 1;
}
// 1 part per child from the 3rd
if ($enfants >= 3) {
// an additional half share for each child from the 3rd onwards
$nbParts += 0.5 * ($enfants - 2);
}
// taxable income
$revenuImposable = getRevenuImposable($salaire);
// surcharge
$surcôte = floor($revenuImposable - 0.9 * $salaire);
// for rounding problems
if ($surcôte < 0) {
$surcôte = 0;
}
// family quotient
$quotient = $revenuImposable / $nbParts;
// is set at the end of the limit table to stop the following loop
$limites[count($limites) - 1] = $quotient;
// tAX CALCULATION
$i = 0;
while ($quotient > $limites[$i]) {
$i++;
}
// because $quotient has been placed at the end of the $limites array, the previous loop
// cannot exceed the table $limites
// now we can calculate the tax
$impôt = floor($revenuImposable * $coeffR[$i] - $nbParts * $coeffN[$i]);
// 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);
}
Comments
- Here, we apply the tax calculation using the progressive tax scale;
- line 9: strtolower($chaîne) converts $chaîne to lowercase;
- lines 10–19: calculation of the taxpayer’s number of shares;
- line 21: taxable income is calculated using a function. As we have seen, it is not always 0.9*revenusAnnuels. The 10% deduction is limited to 12,502 euros;
- line 23: calculation of any surcharge if the taxable income exceeds 0.9*revenusAnnuels;
- lines 25–27: corrects the fact that, due to rounding errors, the result is sometimes [$surcôte=-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 value immediately below [$x];
- line 42: the calculated information is returned;
The function [getRevenuImposable] is as follows:
// global constants
define("ABATTEMENT_DIXPOURCENT_MAX", 12502);
define("ABATTEMENT_DIXPOURCENT_MIN", 437);
// revenuImposable=annualwage-discount
// the allowance has a minimum and a maximum
function getRevenuImposable(float $salaire): float {
// 10% salary deduction
$abattement = 0.1 * $salaire;
// this allowance may not exceed ABATTEMENT_DIXPOURCENT_MAX
if ($abattement > ABATTEMENT_DIXPOURCENT_MAX) {
$abattement = ABATTEMENT_DIXPOURCENT_MAX;
}
// the allowance cannot be less than ABATTEMENT_DIXPOURCENT_MIN
if ($abattement < ABATTEMENT_DIXPOURCENT_MIN) {
$abattement = ABATTEMENT_DIXPOURCENT_MIN;
}
// taxable income
$revenuImposable = $salaire - $abattement;
// result
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 function [getDecote] is as follows:
// global constants
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 credit;
- line 11: the tax credit formula. [plafondDécôte] 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 tax credit cannot exceed the gross tax due. This is the case, for example, if [impots] is 0 on line 11;
- lines 17–19: to avoid rounding to -1;
The function [getRéduction] is as follows:
// global constants
define("PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION", 21037);
define("PLAFOND_REVENUS_COUPLE_POUR_REDUCTION", 42074);
define("VALEUR_REDUC_DEMI_PART", 3797);
// calculates any reduction
function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
// the income ceiling to qualify for the 20% reduction
$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;
}
// taxable income
$revenuImposable = getRevenuImposable($salaire);
// reduction
$réduction = 0;
if ($revenuImposable < $plafondRevenuPourRéduction) {
// 20% discount
$réduction = 0.2 * $impots;
}
// result
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 taxpayer meets the conditions, they are eligible for a 20% tax reduction (line 15);
4.3.2. Results
The taxpayersdata.txt data file (married, children, salary):
The résultats.txt file (married, children, salary, tax, surcharge, discount, reduction, tax rate) of the results obtained:
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 simple, is complex. We will not revisit it here. Across 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. The Modifications
In the previous version, the data required for tax calculation was hard-coded as constants and arrays. This method is to be avoided. In the new version, this data is externalized into a file named jSON:

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 corresponding rate and surcharge
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 file named jSON;
- lines 21-30: if we were able to read the file jSON, 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 terminates;
- The difference from version 01 is that here the tax administration’s 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 [calculerImpot] function, where global constants or the [limites, coeffR, coeffN] arrays were previously used, the contents of the [$taxAdminData] array received as a parameter are now used (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 be sufficient to update the [taxadmindata.json] file. With the 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 code.