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

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

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:
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:
which gives the gross tax I = 0.45*R – 20163.45*nbParts.
4.1.2. Family Quotient Cap

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

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

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):
The results.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 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):
The results.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 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:

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.