4. 练习题 – 第 1 版和第 2 版
4.1. 问题

上表可用于计算仅需申报工资收入的纳税人的简化税额。如注释 (1) 所示,按此方式计算的税额不包含以下三项机制:
- 家庭商数上限(适用于高收入人群);
- 适用于低收入群体的税收抵免和减税;
因此,税额计算涉及以下步骤 [http://impotsurlerevenu.org/comprendre-le-calcul-de-l-impot/1217-calcul-de-l-impot-2019.php]:

我们建议编写一个程序,用于计算仅需申报工资收入的纳税人的税款(简化情况):
4.1.1. 应纳税额的计算
应纳税额可按以下方式计算:
首先,我们计算纳税人的股数:
- 每位父母各贡献1份;
- 前两名子女各贡献1/2份;
- 其余子女每人各贡献1股:
因此,股份总数为:
- nbParts=1+nbChildren*0.5+(nbChildren-2)*0.5(若员工未婚);
- 若已婚,则 nbParts = 2 + nbChildren * 0.5 + (nbChildren - 2) * 0.5;
其中 nbChildren 表示子女人数;
- 我们计算应税收入 R = 0.9 * S,其中 S 为年薪;
- 家庭商 QF 的计算公式为 QF = R / nbParts;
- 我们根据以下数据(2019年)计算应纳税额 I:
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 | 20,163.45 |
每行包含 3 个字段:field1、field2、field3。要计算税款 I,我们找出满足 QF <= field1 的第一行,并取该行的数值。例如,对于一名已婚员工,有两个孩子,年薪 S 为 50,000 欧元:
应税收入:R=0.9*S=45,000
份额数:nbParts=2+2*0.5=3
家庭商:QF=45,000/3=15,000
满足 QF <= field1 的第一行如下:
因此,税款 I 等于 0.14*R – 1394.96*股数=[0.14*45000-1394.96*3]=2115。税款四舍五入至最接近的欧元。
如果第一行满足条件 QF <= field1,则税额为零。
如果 QF 的取值导致条件 QF <= field1 永远无法满足,则使用最后一行中的系数。具体如下:
由此得出总税额 I = 0.45*R – 20163.45*nbParts。
4.1.2. 家庭商上限

为确定家庭商数(QF)上限是否适用,需重新计算不包含子女的应纳税额。再次以那位有两个子女、年薪S为50,000欧元的已婚雇员为例:
应税收入:R = 0.9 * S = 45,000
份额数:nbParts=2(子女不再计入)
家庭商数:QF = 45,000 / 2 = 22,500
满足 QF <= field1 的第一行如下:
因此,税款 I 等于 0.14*R – 1394.96*股数 = [0.14*45,000 – 1394.96*2] = 3,510。
最高子女津贴:1551 × 2 = 3102 欧元
最低税额:3,510 – 3,102 = 408 欧元
按3个税率档次计算的应纳税额(已计算为2,115欧元)高于408欧元的最低税额,因此此处不适用家庭上限。
一般而言,税额总和大于 (tax1, tax2),其中:
- [税1]:包含子女计算的应纳税额;
- [税款2]:指不计入子女且扣除与子女相关的最高抵免额(此处为每半份1,551欧元)后计算出的税款总额;
4.1.3. 减税额的计算

仍以那位有两个孩子、年薪S为50,000欧元的已婚雇员为例:
上一步计算出的应纳税额(2,115)低于夫妻档的2,627欧元(单身人士为1,595欧元):因此适用减税。其计算方法如下:
减免额 = 起征点(已婚夫妇 = 1,970 / 单身 = 1,196)- 0.75 * 应纳税额
减免额 = 1,970 – 0.75 * 2,115 = 383.75,四舍五入后为384欧元。
新应纳税额 = 2,115 – 384 = 1,731 欧元
4.1.4. 减税额计算

当应纳税额低于特定门槛时,将对前文计算得出的应纳税额适用20%的减免。2019年的门槛如下:
- 单身:21,037欧元;
- 已婚夫妇:42,074欧元;(上文示例中使用的37,968欧元似乎有误);
该门槛值需增加以下数值:3,797 *(子女所贡献的半份份额数量)。
再次以那位有两个孩子、年薪S为50,000欧元的已婚雇员为例:
- 其应税收入(45,000欧元)低于门槛值(42,074 + 2 × 3,797)= 49,668欧元;
- 因此,他有权享受20%的减税:1,731 * 0.2 = 346.2欧元,四舍五入为347欧元;
- 纳税人的应纳税额为:1,731 – 347 = 1,384欧元;
4.1.5. 应纳税额计算
我们的计算到此结束:应缴净税额为1,384欧元。实际上,纳税人可能还有资格享受其他扣除,特别是向公共或公益组织捐赠的扣除。
4.1.6. 高收入人群的情况
前面的例子适用于大多数雇员。然而,高收入者的税款计算方式有所不同。
4.1.6.1. 年收入10%减免的上限
在大多数情况下,应税收入按以下公式计算:R = 0.9 × S,其中S为年薪。这被称为10%的减免。该减免设有上限。2019年:
- 不得超过12,502欧元;
- 不得低于437欧元;
让我们以一名未婚、无子女且年薪为200,000欧元的雇员为例:
- 10%的减免额为200,000欧元 > 12,502欧元。因此,减免额上限为12,502欧元;
4.1.6.2. 家庭系数上限
让我们考虑一个适用前文提及的家庭上限的情况。以一对育有三个子女、年收入为100,000欧元的夫妇为例。让我们再次回顾计算步骤:
- 10%的扣除额为10,000欧元 < 12,502欧元。因此,应税收入R为100,000 - 10,000 = 90,000欧元;
- 这对夫妇的nbParts = 2 + 0.5 * 2 + 1 = 4 份;
- 因此,他们的家庭份额为 QF = R / nbParts = 90,000 / 4 = 22,500 欧元;
- 他们带子女的税前税额 I1 为 I1 = 0.14 × 90,000 – 1,394.96 × 4 = 7,020 欧元;
- 其无子女的税款总额 I2:
- QF = 90,000 / 2 = 45,000 欧元;
- I2 = 0.3 × 90,000 – 5,798 × 2 = 15,404 欧元;
- 家庭商数上限规则规定,子女带来的减免额不得超过(1,551 × 4个半份额)= 6,204欧元。然而,此处I2 – I1 = 15,404 – 7,020 = 8,384欧元,大于6,204欧元;
- 因此,应纳税额重新计算为 I3 = I2 - 6,204 = 15,404 - 6,204 = 9,200 欧元;
这对夫妇既无法获得税收抵免,也无法享受减免,其最终应纳税额为9,200欧元。
4.1.7. 官方数据
税款计算较为复杂。在本文档中,将通过以下示例进行测试。结果来自税务机关的模拟器 [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm]:
纳税人 | 官方结果 | 本文算法得出的结果 |
一对夫妇,育有2名子女,年收入55,555欧元 | 应缴税额 = 2,815欧元 税率 = 14% | 税额 = 2,814 欧元 税率 = 14% |
一对夫妇,育有2个孩子,年收入50,000欧元 | 税额 = 1,385 欧元 税收抵免 = 720 欧元 减免 = 0 欧元 税率 = 14% | 应纳税额 = 1,384欧元 扣除额 = 384 欧元 抵免额 = 347 欧元 税率 = 14% |
一对夫妇,育有3个孩子,年收入50,000欧元 | 应纳税额 = 0 欧元 税收抵免 = 384 欧元 减免额 = 346 欧元 税率 = 14% | 应纳税额 = 0 欧元 折扣 = 720 欧元 减免 = 0 欧元 税率 = 14% |
单身,育有2名子女,年收入100,000欧元 | 应纳税额 = 19,884 欧元 税收抵免 = 0 欧元 扣除额 = 0 欧元 税率 = 41% | 应纳税额 = 19,884欧元 附加费 = 4,480 欧元 折扣 = 0 欧元 减免 = 0 欧元 税率 = 41% |
单身,育有3名子女,年收入100,000欧元 | 应纳税额 = 16,782 欧元 税收抵免 = 0 欧元 扣除额 = 0 欧元 税率 = 41% | 应纳税额 = 16,782欧元 附加费 = 7,176 欧元 折扣 = 0 欧元 减免 = 0 欧元 税率 = 41% |
一对夫妇,育有三个孩子,年收入100,000欧元 | 应纳税额 = 9,200 欧元 税收抵免 = 0 欧元 扣除额 = 0 欧元 税率 = 30% | 应纳税额 = 9,200 欧元 附加费 = 2,180 欧元 折扣 = 0 欧元 减免 = 0 欧元 税率 = 30% |
一对夫妇,育有5个孩子,年收入100,000欧元 | 应缴税款 = 4,230 欧元 税收抵免 = 0 欧元 扣除额 = 0 欧元 税率 = 14% | 应纳税额 = 4,230 欧元 扣除额 = 0 欧元 扣除额 = 0 欧元 税率 = 14% |
单身,无子女,年收入100,000欧元 | 应纳税额 = 22,986 欧元 税收抵免 = 0 欧元 扣除额 = 0 欧元 税率 = 41% | 应纳税额 = 22,986欧元 附加费 = 0 欧元 折扣 = 0 欧元 减免 = 0 欧元 税率 = 41% |
一对夫妇,育有两名子女,年收入30,000欧元 | 应纳税额 = 0 欧元 税收抵免 = 0 欧元 扣除额 = 0 欧元 税率 = 0% | 应纳税额 = 0 欧元 折扣 = 0 欧元 扣除额 = 0 欧元 税率 = 0% |
单身,无子女,年收入200,000欧元 | 应纳税额 = 64,211 欧元 税收抵免 = 0 欧元 扣除额 = 0 欧元 税率 = 45% | 应纳税额 = 64,210欧元 附加费 = 7,498 欧元 折扣 = 0 欧元 减免 = 0 欧元 税率 = 45% |
一对夫妇,育有3名子女,年收入200,000欧元 | 应纳税额 = 42,843 欧元 税收抵免 = 0 欧元 扣除额 = 0 欧元 税率 = 41% | 应纳税额 = 42,842欧元 附加费 = 17,283 欧元 减免 = 0 欧元 减免 = 0 欧元 税率 = 41% |
在上例中,“附加费”是指高收入者因以下两个因素而需额外缴纳的金额:
- 年度收入10%扣除额的上限;
- 家庭津贴的上限;
由于税务机关的模拟器未提供该数据,因此无法验证该指标。
我们可以看到,该文档的算法每次计算出的税额都是正确的,尽管存在1欧元的误差。这种误差源于四舍五入。在某些情况下,所有金额会被四舍五入至最接近的整数欧元,而在其他情况下则会四舍五入至最接近的整数欧元。由于我不熟悉官方规则,因此文档算法中的金额进行了如下四舍五入:
- 折扣和减免金额向上取整至最接近的欧元;
- 附加费和最终税额则向下舍入至最接近的欧元;
在下一节中,将进行测试以验证结果的有效性。测试将使用前一表格中的示例进行,并接受1欧元的误差范围。
4.2. 脚本目录结构

4.3. 版本 1
4.3.1. 算法
我们提出一个初始程序,其中:
- 计算税款所需的数据以数组和常量的形式硬编码在程序中;
- 纳税人数据(已婚、子女、工资)存储在第一个文本文件 [taxpayersdata.txt] 中;
- 税款计算结果(婚姻状况、子女数、工资、税额)存储在第二个文本文件 [resultats.txt] 中;
脚本 [version-01/main.php] 如下:
<?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);
}
taxpayersdata.txt 数据文件(已婚、子女、工资):
结果文件 results.txt(已婚、子女、工资、税款、附加费、折扣、减免、税率)中的结果如下:
注释
- 第4行:我们强制要求严格遵守函数参数的类型;
- 第7–16行:定义计算税款所需的所有常量;
- 第 19 行:包含纳税人数据(婚姻状况、子女、工资)的文本文件名称;
- 第 20 行:包含税额计算结果(婚姻状况、子女、工资、税额)的文本文件名称;
- 第 21–23 行:定义税额计算中不同税率档次的三个数据数组;
- 第26–30行:以读取模式[r]打开纳税人数据文件。若文件无法打开,[fopen]函数将返回布尔值FALSE;
- 第 33–37 行:以写入模式 [w] 打开结果文件;
- 第 40–51 行:循环读取纳税人数据文件中的行(配偶、子女、收入);
- 第 40 行:[fgets] 函数读取 100 个字符,并在遇到第一个换行符时停止。此处所有行长度均小于 100 个字符。若遇到换行符,该换行符将被包含在返回的字符串中。当到达文件末尾时,[fgets] 函数返回 FALSE;
- 第 42 行:移除换行符;
- 第 44 行:提取该行的组成部分(已婚、子女、工资);
- 第 46 行:计算税额。结果以关联数组的形式返回(第 76 行);
- 第 48 行:将键 [married, children, salary] 添加到之前获取的数组中;
- 第 49 行:将结果作为 JSON 字符串存储在结果文件中;
- 第 53–54 行:纳税人数据文件处理完成后,关闭相关文件;
- 第 60 行:该函数用于从字符串 $line 中移除换行符。在 Windows 系统中,换行符为字符串 "\r\n";在 Unix 系统中,换行符为 "\n"。结果是去除换行符后的原始字符串。
- 第 63–64 行:substr($string, $start, $size) 表示从字符 $start 开始,长度不超过 $size 的 $string 子字符串;
[calculImpot] 函数如下:
// 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);
}
注释
- 第 10 行:税额(含子女)的计算结果以 ["tax" => $tax, "surcharge" => $surcharge, "rate" => $coeffR[$i]] 的形式返回,其中:
- [‘tax’]:税额;
- [‘surcharge’]:附加费金额(如有)。当 10% 的扣除额超过 12,502 欧元的阈值时适用;
- [‘rate’]:纳税人的税率;
- 第 11 行:应缴税额 [impot1];
- 第13–14行:若纳税人至少有一个子女,则使用相同数据但将子女数设为0重新计算税额。此二次计算旨在确定子女带来的减免额(nbParts*coeffN)是否超过特定阈值;
- 第15行:应缴税款[impot2];
- 第16–23行:针对应缴税额[impot2],现将子女因素纳入计算:子女提供的每1/2份额可减免[PLAFOND_QF_DEMI_PART]欧元;
- 第25–26行:纳税人无子女的情况。在此情况下,无需计算[impot2],其值即等于[impot1];
- 第29–37行:已计算出两项税额[impot1, impot2]。税务机关取两者中较高者,从而得出税额[impot];
- 第39–40行:税额[tax]可能适用减免;
- 第42–43行:税额[impot]可能适用减免;
- 第 45 行:[tax] 现为应缴净税额。返回计算结果;
函数 [calculImpot2] 如下:
// --------------------------------------------------------------------------
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);
}
注释
- 在此,我们使用累进税率表进行税款计算;
- 第 9 行:strtolower($string) 将 $string 转换为小写;
- 第 10–19 行:计算纳税人的股数;
- 第 21 行:我们使用函数计算应税收入。事实上,我们已经看到它并不总是 0.9*AnnualIncome。10% 的扣除额实际上上限为 12,502 欧元;
- 第 23 行:若应税收入大于 0.9*annualIncome,则计算潜在附加费;
- 第25–27行:修正因四舍五入误差导致[$surcharge=-1]的情况;
- 第29行:计算家庭商;
- 第30–36行:利用该商数确定纳税人的税率档次;
- 第40行:确定纳税人的税率档位后,即可计算其应纳税额。floor($x)函数返回小于[$x]的最小整数;
- 第42行:返回计算结果;
[getRevenuImposable] 函数如下:
// 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);
}
注释
- 第5行:标准扣除额为年薪的10%;
- 第7–9行:扣除额不得超过最高扣除额 [ABATTEMENT_DIXPOURCENT_MAX];
- 第10–13行:扣除额不得低于最低扣除额 [ABATTEMENT_DIXPOURCENT_MIN];
- 第15行:应纳税所得额的计算;
[getDecote] 函数如下:
// 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);
}
注释
- 第6行:符合减税条件的最高应纳税额。该金额因单身人士和已婚夫妇而异;
- 第7行:纳税人是否符合减税条件;
- 第11行:税收抵免计算公式。[taxCreditCap]为税收抵免的最高限额。该最高限额在第9行计算得出。同样,其数值取决于纳税人的婚姻状况——已婚或单身;
- 第13–15行:扣除额不得超过应缴税款总额。例如,当第11行的[taxes]为0时即属此情况;
- 第17–19行:避免四舍五入为-1;
[getReduction] 函数如下:
// 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);
}
注释
- 第4–10行:要符合减税条件,应税收入(第10行)必须低于第4–8行计算出的阈值;
- 第13–16行:若满足条件,纳税人有权享受20%的减税(第15行);
4.3.2. 结果
taxpayersdata.txt 数据文件(已婚、子女、工资):
结果文件 results.txt(已婚、子女、工资、税款、附加费、折扣、减免、税率)中的结果如下:
所得结果与使用税务机关模拟器计算的结果一致。
4.3.3. 结论
即使在被视为简单的案例中,税务计算算法也相当复杂。本文将不再赘述该算法。尽管不同版本在呈现形式上有所变化,但其核心逻辑保持不变。因此,我们仅就呈现形式进行说明。
4.4. 第2版
4.4.1. 变更
在上一版本中,计算税款所需的数据被硬编码为常量和数组。应避免这种做法。在新版本中,这些数据被外部化为一个 JSON 文件:

[taxadmindata.json] 文件的内容如下:
{
"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
}
新版本 [version-02/main.php] 如下:
<?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);
}
注释
- 第 11–19 行:我们尝试读取名为 [TAXADMINDATA] 的 JSON 文件的内容;
- 第 21–30 行:如果成功读取了 JSON 文件,其内容将被解码为关联数组 [$taxAdminData];
- 第 32–36 行:如果前两项操作中的任何一项发生错误,则向控制台输出错误信息并终止程序;
- 与版本 01 的区别在于,此处税务管理数据(数组和常量)存储在关联数组 [$taxAdminData] 中,而在版本 01 中,它们存储在全局数组和常量中。正是这些常量的全局性质区分了这两个版本:
- 在版本 01 中,[main.php] 中的所有函数都能访问这些常量;
- 而在版本 02 中,为实现相同效果,必须将关联数组 [$taxAdminData] 作为参数传递给所有函数(第 83、129、138、145、152 行);
- 版本 02 中的每个函数都必须使用数组 [$taxAdminData] 的内容;
- 第 83–126 行:在 [calculateTax] 函数中,此前使用全局常量或 [limits, coeffR, coeffN] 数组的地方,现在改用作为参数传递的 [$taxAdminData] 数组的内容(第 99、102 行);
- 所有其他函数均按此方式重写;
所得结果与上一版本完全一致。
4.4.2. 结论
版本 02 比版本 01 灵活得多。2020 年的税费计算算法很可能与 2019 年相同。 届时仅需更新[taxadmindata.json]文件即可。而在版本01中,必须直接修改代码才能调整税率区间和计算常数。然而,需要修改这些参数的人员通常无法访问算法源代码。