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+nbEnfants*0.5+(子女数-2)*0.5(若雇员未婚);
- 若已婚,则nbParts=2+nbEnfants*0.5+(nbEnfants-2)*0.5;
其中 nbEnfants 为其子女数;
- 计算应税收入 R=0.9*S,其中 S 为年薪;
- 计算家庭分摊系数 QF=R/nbParts;
- 根据以下数据(2019年)计算应纳税额I:
9964 | 0 | 0 |
27519 | 0.14 | 1394.96 |
73779 | 0.3 | 5798 |
156244 | 0.41 | 13913.69 |
0 | 0.45 | 20163.45 |
每行有 3 个字段:champ1、champ2、champ3。 要计算税款 I,需查找满足 QF<=字段1 的第一行,并取该行的数值。例如,对于一名已婚且有两个孩子的员工,年薪 S 为 50000 欧元:
应税收入:R=0.9*S=45000
份额数:nbParts=2+2*0.5=3
家庭分额:QF=45000/3=15000
满足 QF<=field1 的第一行如下:
因此,税额 I 等于 0.14*R – 1394.96*nbParts=[0,14*45000-1394,96*3]=2115。税额向下四舍五入至整欧元。
若从第一行起即满足QF<=field1,则税额为零。
若 QF 满足关系 QF<=field1 从未成立,则使用最后一行中的系数。此处:
由此得出应纳税额 I=0.45*R – 20163.45*nbParts。
4.1.2. 家庭商数上限

要判断家庭商数上限 QF 是否适用,需重新计算不含子女的应纳税额。仍以已婚、育有两名子女且年薪 S 为 50000 欧元的雇员为例:
应税收入:R=0.9*S=45000
分额数:nbParts=2(不再计入子女)
家庭分额:QF=45000/2=22500
满足 QF<=field1 的第一行如下:
因此,税额 I 等于 0.14*R – 1394.96*nbParts=[0,14*45000-1394,96*2]=3510。
与子女相关的最高收益:1551 * 2 = 3102 欧元
最低税额:3510-3102 = 408 欧元
已计算的含3个税额份额的应纳税额2115欧元高于最低税额408欧元,因此此处不适用家庭税额上限。
一般而言,应纳税额为 (税款1 + 税款2),其中:
- [impôt1]:为包含子女计算的应纳税额;
- [impôt2]:是不计入子女的应纳税额,并减去与子女相关的最高抵扣额(此处为每半份1551欧元);
4.1.3. 减免额的计算

仍以那位已婚、育有两名子女且年薪S为50000欧元的雇员为例:
上一步计算出的应纳税额(2115)低于已婚夫妇的2627欧元(单身人士为1595欧元):因此适用减免。其计算方式如下:
减免额 = 起征点(已婚夫妇=1970/单身=1196)-0.75 × 应纳税额
减免额=1970-0.75*2115=383.75,四舍五入为384欧元。
新应纳税额=2115-384=1731欧元
4.1.4. 减税额计算

当收入低于一定门槛时,对前文计算出的应纳税额给予20%的减免。2019年的门槛如下:
- 单身:21037欧元;
- 已婚夫妇:42074欧元;(上例中使用的37968这一数字似乎有误);
该门槛值需增加以下数值:3797 *(子女带来的半份数)。
仍以那位已婚、育有两名子女且年薪S为50000欧元的雇员为例:
- 其应税收入(45000欧元)低于门槛(42074+2*3797)=49668欧元;
- 因此他有权享受20%的税额减免:1731 * 0.2 = 346.2欧元(四舍五入为347欧元);
- 纳税人的应纳税额为:1731-347=1384欧元;
4.1.5. 净税额计算
我们的计算到此为止:应缴净税额为1384欧元。实际上,纳税人还可以享受其他减免,特别是针对向公益或公共利益组织捐赠的减免。
4.1.6. 高收入人群的情况
前面的例子适用于大多数工薪阶层。然而,高收入群体的税款计算方式有所不同。
4.1.6.1. 年度收入10%减免的上限
在大多数情况下,应税收入按以下公式计算:R=0.9*S,其中S为年薪。这被称为10%的减免。该减免设有上限。2019年:
- 减免额不得超过12502欧元;
- 不得低于437欧元;
以一名未婚且无子女、年薪为200000欧元的雇员为例:
- 10%的减免额为200,000欧元 > 12,502欧元。因此,减免额被调整为12,502欧元;
4.1.6.2. 家庭商数上限
假设出现上文所述的家庭上限情况。以一对育有三个子女、年收入为100,000欧元的夫妇为例。重新梳理计算步骤:
- 10%的免税额为10000欧元 < 12502欧元。因此应税收入R为100000-10000=90000欧元;
- 该夫妇拥有nbParts=2+0.5×2+1=4个家庭份额;
- 因此其家庭分额为 QF = R / nbParts = 90000 / 4 = 22500 欧元;
- 其带子女的应纳税额I1为I1=0.14×90000-1394.96×4=7020欧元;
- 其无子女的应纳税额I2:
- QF=90000/2=45000欧元;
- I2=0.3*90000-5798*2=15404欧元;
- 家庭分额上限规则规定,子女带来的增益不得超过(1551×4个半份额)=6204欧元。但此处该数值为I2-I1=15404-7020=8384欧元,因此高于6204欧元;
- 因此,应纳税额重新计算为 I3=I2-6204=15404-6204=9200欧元;
这对夫妇既没有免税额,也没有减免,最终应缴税额为9200欧元。
4.1.7. 官方数据
税款计算较为复杂。在本文中,我们将通过以下示例进行测试。结果来自税务部门的模拟器 [https://www3.impots.gouv.fr/simulateur/calcul_impot/2019/simplifie/index.htm]:
纳税人 | 官方结果 | 本文算法计算结果 |
一对夫妇,育有2名子女,年收入55555欧元 | 税额=2815欧元 税率=14% | 应纳税额=2814欧元 税率=14% |
一对夫妇,育有2个孩子,年收入50000欧元 | 税额=1385欧元 减免额=720欧元 减免=0欧元 税率=14% | 应纳税额=1384欧元 减免额=384欧元 减免额=347欧元 税率=14% |
一对夫妇,育有3个孩子,年收入50000欧元 | 税额=0欧元 减免额=384欧元 减免额=346欧元 税率=14% | 应纳税额=0欧元 减免额=720欧元 减免额=0欧元 税率=14% |
单身,有2个孩子,年收入100000欧元 | 应纳税额=19884欧元 减免额=0欧元 减免额=0欧元 税率=41% | 应纳税额=19884欧元 附加税=4480欧元 减免额=0欧元 减免额=0欧元 税率=41% |
单身,育有3名子女,年收入100000欧元 | 应纳税额=16782欧元 减免额=0欧元 减免额=0欧元 税率=41% | 应纳税额=16782欧元 附加税=7176欧元 减免额=0欧元 减免额=0欧元 税率=41% |
一对夫妇,育有3个孩子,年收入100000欧元 | 应纳税额=9200欧元 减免额=0欧元 减免额=0欧元 税率=30% | 应纳税额=9200欧元 附加税=2180欧元 减免额=0欧元 减免额=0欧元 税率=30% |
一对夫妇,育有5个孩子,年收入100000欧元 | 应纳税额=4230欧元 减免额=0欧元 减免额=0欧元 税率=14% | 应纳税额=4230欧元 减免=0欧元 减免=0欧元 税率=14% |
单身无子女,年收入100000欧元 | 应纳税额=22986欧元 减免额=0欧元 减免额=0欧元 税率=41% | 应纳税额=22986欧元 附加费=0欧元 减免=0欧元 减免=0欧元 税率=41% |
一对夫妇,育有2个孩子,年收入30000欧元 | 应纳税额=0欧元 减免额=0欧元 减免额=0欧元 税率=0% | 应纳税额=0欧元 折扣=0欧元 减免=0欧元 税率=0% |
单身无子女,年收入200000欧元 | 应纳税额=64211欧元 减免额=0欧元 减免额=0欧元 税率=45% | 应纳税额=64210欧元 附加税=7498欧元 减免=0欧元 减免额=0欧元 税率=45% |
一对夫妇,育有3个孩子,年收入200000欧元 | 应纳税额=42843欧元 减免额=0欧元 减免额=0欧元 税率=41% | 应纳税额=42,842欧元 附加税=17283欧元 减免额=0欧元 减免额=0欧元 税率=41% |
上文所称的附加税,是指高收入者因以下两种情况而需额外缴纳的税款:
- 年度收入10%免税额的上限;
- 家庭分摊额的上限;
该指标无法验证,因为税务部门的模拟器未提供该数据。
可以看出,该文档中的算法每次计算出的税额均准确无误,但存在1欧元的误差范围。该误差源于四舍五入。所有金额有时四舍五入至更高的一欧元,有时则四舍五入至更低的一欧元。 由于我并不了解官方规则,因此文档中算法的金额已按以下方式进行四舍五入:
- 折扣和减免金额向上取整至整欧元;
- 附加费和最终税款向下取整;
接下来将进行测试以验证结果的有效性。测试将基于前表中的示例进行,允许的误差范围为1欧元。
4.2. 脚本目录结构

4.3. 版本 1
4.3.1. 算法
我们在此展示一个初始程序,其中:
- 计算税款所需的数据以数组和常量的形式硬编码在代码中;
- 纳税人数据(婚姻状况、子女、工资)存储在第一个文本文件 [taxpayersdata.txt] 中;
- 税款计算结果(婚姻状况、子女、工资、税额)存储在第二个文本文件 [resultats.txt] 中;
脚本 [version-01/main.php] 如下:
<?php
// 函数参数的严格类型
declare(strict_types=1);
// 全局常量
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);
// 局部常量的定义
$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);
// 读取数据
$data = fopen($DATA, "r");
if (!$data) {
print "Impossible d'ouvrir en lecture le fichier des données [$DATA]\n";
exit;
}
// 打开结果文件
$résultats = fopen($RESULTATS, "w");
if (!$résultats) {
print "Impossible de créer le fichier des résultats [$RESULTATS]\n";
exit;
}
// 处理数据文件的当前行
while ($ligne = fgets($data, 100)) {
// 移除可能存在的换行符
$ligne = cutNewLineChar($ligne);
// 提取构成 $ligne 的 3 个字段:已婚:子女:工资
list($marié, $enfants, $salaire) = explode(",", $ligne);
// 计算税额
$result = calculImpot($marié, (int) $enfants, (float) $salaire, $limites, $coeffR, $coeffN);
// 将结果写入结果文件
$result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
fputs($résultats, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
// 下一条数据
}
// 关闭文件
fclose($data);
fclose($résultats);
// 结束
exit;
// --------------------------------------------------------------------------
function cutNewLinechar(string $ligne): string {
// 如果存在,则删除 $ligne 中的行尾标记
$L = strlen($ligne); // 行长度
while (substr($ligne, $L - 1, 1) === "\n" or substr($ligne, $L - 1, 1) === "\r") {
$ligne = substr($ligne, 0, $L - 1);
$L--;
}
// 结束
return($ligne);
}
// 计算税款
// --------------------------------------------------------------------------
function calculImpot(string $marié, int $enfants, float $salaire, array $limites, array $coeffR, array $coeffN): array {
…
// 结果
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 {
…
// 结果
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=年薪-免税额
// 免税额有最低和最高限额
function getRevenuImposable(float $salaire): float {
…
// 结果
return floor($revenuImposable);
}
// 计算可能的折减
function getDecote(string $marié, float $salaire, float $impots): float {
…
// 结果
return ceil($décôte);
}
// 计算可能的减额
function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
/…
// 结果
return ceil($réduction);
}
数据文件 taxpayersdata.txt(已婚、子女、工资):
所得结果的文件 résultats.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行:向先前获取的数组中添加键[marié, enfants, salaire];
- 第49行:将结果以字符串形式(jSON)存储在结果文件中;
- 第53-54行:在完全处理完纳税人数据文件后,关闭相关文件;
- 第 60 行:该函数用于移除行 $ligne 中的换行符。在 Windows 系统中,换行符为字符串 "\r\n";在 Unix 系统中,换行符为 "\n"。处理结果为去除换行符后的原始字符串。
- 第63-64行:substr($chaîne,$début,$taille) 是 $chaîne 的子字符串,从字符 $début 开始,且长度不超过 $taille 个字符;
函数 [calculImpot] 定义如下:
// 全局常量
define("PLAFOND_QF_DEMI_PART", 1551);
// 计算税额
// --------------------------------------------------------------------------
function calculImpot(string $marié, int $enfants, float $salaire, array $limites, array $coeffR, array $coeffN): array {
// $marié:是,否
// $enfants:子女人数
// $salaire:年薪
// $limites, $coeffR, $coeffN:用于计算税款的数据表
//
// 含子女的税款计算
$result1 = calculImpot2($marié, $enfants, $salaire, $limites, $coeffR, $coeffN);
$impot1 = $result1["impôt"];
// 不含子女的税款计算
if ($enfants != 0) {
$result2 = calculImpot2($marié, 0, $salaire, $limites, $coeffR, $coeffN);
$impot2 = $result2["impôt"];
// 家庭商数上限的适用
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART 欧元(前两名子女)
$impot2 = $impot2 - $enfants * PLAFOND_QF_DEMI_PART;
} else {
// $PLAFOND_QF_DEMI_PART 欧元适用于前两名子女,后续子女金额加倍
$impot2 = $impot2 - 2 * PLAFOND_QF_DEMI_PART - ($enfants - 2) * 2 * PLAFOND_QF_DEMI_PART;
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// 取税率最高的那项,并适用相应的税率和附加费
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"];
}
// 计算可能的减免额
$décôte = getDecote($marié, $salaire, $impot);
$impot -= $décôte;
// 计算可能的减税额
$réduction = getRéduction($marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// 结果
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 {
…
// 结果
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=年薪-免税额
// 10%的免税额有最低和最高限额
function getRevenuImposable(float $salaire): float {
…
}
// 计算可能的折扣
function getDecote(string $marié, float $salaire, float $impots): float {
…
// 结果
return ceil($décôte);
}
// 计算可能的减额
function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
…
// 结果
return ceil($réduction);
}
注释
- 第 10 行:税前税额是包含子女的计算结果。 结果以 ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]] 的形式呈现,其中:
- [‘impôt’]:应纳税额;
- [‘surcôte’]:如有附加费,则为该金额。当10%的免税额超过12502欧元的门槛时,将产生附加费;
- [‘taux’]:纳税人的税率;
- 第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行:税额[impot]可能适用减免;
- 第42-43行:税额[impot]可能适用减免;
- 第45行:[impot]即为应缴净税额。返回计算结果;
函数 [calculImpot2] 如下:
// --------------------------------------------------------------------------
function calculImpot2(string $marié, int $enfants, float $salaire, array $limites, array $coeffR, array $coeffN): array {
// $marié:是,否
// $enfants:子女人数
// $salaire:年薪
// $limites, $coeffR, $coeffN:用于计算税款的数据表
//
// 份额数
$marié = strtolower($marié);
if ($marié === "oui") {
$nbParts = $enfants / 2 + 2;
} else {
$nbParts = $enfants / 2 + 1;
}
// 从第三个孩子起,每个孩子1个份额
if ($enfants >= 3) {
// 第3个及以后的每个孩子额外增加半份
$nbParts += 0.5 * ($enfants - 2);
}
// 应税收入
$revenuImposable = getRevenuImposable($salaire);
// 附加费
$surcôte = floor($revenuImposable - 0.9 * $salaire);
// 用于四舍五入问题
if ($surcôte < 0) {
$surcôte = 0;
}
// 家庭分摊额
$quotient = $revenuImposable / $nbParts;
// 被放置在限额表的末尾,以终止后续循环
$limites[count($limites) - 1] = $quotient;
// 税额计算
$i = 0;
while ($quotient > $limites[$i]) {
$i++;
}
// 由于将 $quotient 放置在 $limites 表的末尾,因此终止了前面的循环
// 不能超出数组 $limites
// 现在可以计算税款
$impôt = floor($revenuImposable * $coeffR[$i] - $nbParts * $coeffN[$i]);
// 结果
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=年薪-免税额
// 免税额有最低和最高限额
function getRevenuImposable(float $salaire): float {
// 结果
return floor($revenuImposable);
}
注释
- 此处应用累进税率表进行税额计算;
- 第9行:strtolower($chaîne) 将$chaîne转换为小写;
- 第10-19行:计算纳税人的份额数;
- 第21行:使用函数计算应税收入。实际上,我们已看到该值并非总是0.9*revenusAnnuels。10%的免税额上限为12502欧元;
- 第23行:若应税收入高于0.9*revenusAnnuels,则计算可能产生的附加税;
- 第25-27行:修正了因四舍五入误差导致有时出现[$surcôte=-1]的情况;
- 第29行:家庭系数;
- 第30-36行:利用该系数确定纳税人的税率档次;
- 第40行:确定纳税人的税率档位后,即可计算其应纳税额。floor($x)函数返回[$x]的次小整数值;
- 第42行:输出计算结果;
函数 [getRevenuImposable] 如下:
// 全局常量
define("ABATTEMENT_DIXPOURCENT_MAX", 12502);
define("ABATTEMENT_DIXPOURCENT_MIN", 437);
// revenuImposable=年薪-扣除额
// 扣除额有最低值和最高值
function getRevenuImposable(float $salaire): float {
// 工资的10%作为扣除额
$abattement = 0.1 * $salaire;
// 该扣除额不得超过 ABATTEMENT_DIXPOURCENT_MAX
if ($abattement > ABATTEMENT_DIXPOURCENT_MAX) {
$abattement = ABATTEMENT_DIXPOURCENT_MAX;
}
// 扣除额不得低于 ABATTEMENT_DIXPOURCENT_MIN
if ($abattement < ABATTEMENT_DIXPOURCENT_MIN) {
$abattement = ABATTEMENT_DIXPOURCENT_MIN;
}
// 应税收入
$revenuImposable = $salaire - $abattement;
// 结果
return floor($revenuImposable);
}
备注
- 第5行:标准扣除额为年薪的10%;
- 第7-9行:扣除额不得超过最高扣除额[ABATTEMENT_DIXPOURCENT_MAX];
- 第10-13行:扣除额不得低于最低扣除额[ABATTEMENT_DIXPOURCENT_MIN];
- 第15行:计算应税收入;
函数 [getDecote] 如下:
// 全局常量
define("PLAFOND_DECOTE_CELIBATAIRE", 1196);
define("PLAFOND_DECOTE_COUPLE", 1970);
define("PLAFOND_IMPOT_COUPLE_POUR_DECOTE", 2627);
define("PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE", 1595);
// 计算可能的折价
function getDecote(string $marié, float $salaire, float $impots): float {
// 初始折价为零
$décôte = 0;
// 享受减免的最高应纳税额
$plafondImpôtPourDécôte = $marié === "oui" ? PLAFOND_IMPOT_COUPLE_POUR_DECOTE : PLAFOND_IMPOT_CELIBATAIRE_POUR_DECOTE;
if ($impots < $plafondImpôtPourDécôte) {
// 折扣上限
$plafondDécôte = $marié === "oui" ? PLAFOND_DECOTE_COUPLE : PLAFOND_DECOTE_CELIBATAIRE;
// 理论折扣
$décôte = $plafondDécôte - 0.75 * $impots;
// 折扣额不得超过应纳税额
if ($décôte > $impots) {
$décôte = $impots;
}
// 无折减 <0
if ($décôte < 0) {
$décôte = 0;
}
}
// 结果
return ceil($décôte);
}
说明
- 第6行:享有减免资格的应纳税额上限。该金额对单身人士和已婚夫妇有所不同;
- 第7行:纳税人是否符合减免条件;
- 第11行:减免额的计算公式。[plafondDécôte]为减免额上限。该上限金额在第9行计算得出。此处同样取决于纳税人的婚姻状况(已婚或单身);
- 第13-15行:减免额不得超过应缴税款总额。例如,当第11行中的[impots]等于0时即属此情况;
- 第17-19行:为避免四舍五入至-1;
函数 [getRéduction] 如下:
// 全局常量
define("PLAFOND_REVENUS_CELIBATAIRE_POUR_REDUCTION", 21037);
define("PLAFOND_REVENUS_COUPLE_POUR_REDUCTION", 42074);
define("VALEUR_REDUC_DEMI_PART", 3797);
// 计算可能的减免
function getRéduction(string $marié, float $salaire, int $enfants, float $impots): float {
// 享有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;
}
// 应税收入
$revenuImposable = getRevenuImposable($salaire);
// 减免
$réduction = 0;
if ($revenuImposable < $plafondRevenuPourRéduction) {
// 8折
$réduction = 0.2 * $impots;
}
// 结果
return ceil($réduction);
}
说明
- 第4-10行:要获得减税资格,应税收入(第10行)必须低于第4-8行计算出的上限;
- 第13-16行:若符合条件,纳税人可享受20%的减税(第15行);
4.3.2. 结果
数据文件 taxpayersdata.txt(已婚、子女、工资):
根据所得结果生成的文件 résultats.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
// 严格遵守函数参数的声明类型
declare (strict_types=1);
// 常量定义
$TAXPAYERSDATA = "taxpayersdata.txt";
$RESULTATS = "resultats.txt";
$TAXADMINDATA = "taxadmindata.json";
// 读取税务数据文件的内容
$fileContents = \file_get_contents($TAXADMINDATA);
$erreur = FALSE;
// 错误?
if (!$fileContents) {
// 记录错误
$erreur = TRUE;
$message = "Le fichier des données [$TAXADMINDATA] n'existe pas";
}
if (!$erreur) {
// 从配置文件中将代码 jSON 读取到关联数组中
$taxAdminData = \json_decode($fileContents, true);
// 错误?
if (!$taxAdminData) {
// 记录错误
$erreur = TRUE;
$message = "Le fichier de données jSON [$TAXADMINDATA] n'a pu être exploité correctement";
}
}
// 错误?
if ($erreur) {
print "$message\n";
exit;
}
// 打开结果文件
$résultats = fopen($RESULTATS, "w");
if (!$résultats) {
print "Impossible de créer le fichier des résultats [$RESULTATS]\n";
// 输出
exit;
}
// 打开纳税人数据文件
$taxpayersdata = fopen($TAXPAYERSDATA, "r");
if (!$taxpayersdata) {
print "Impossible d'ouvrir le fichier des contribuables [$TAXPAYERSDATA]\n";
// 输出
exit;
}
// 处理数据文件的当前行
while ($ligne = fgets($taxpayersdata, 100)) {
// 移除可能存在的换行符
$ligne = cutNewLineChar($ligne);
// 提取构成 $ligne 的 3 个字段:已婚:子女:工资
list($marié, $enfants, $salaire) = explode(",", $ligne);
// 计算税额
$result = calculImpot($taxAdminData, $marié, (int) $enfants, (int) $salaire);
// 将结果写入结果文件
$result = ["marié" => $marié, "enfants" => $enfants, "salaire" => $salaire] + $result;
fputs($résultats, \json_encode($result, JSON_UNESCAPED_UNICODE) . "\n");
// 下一条数据
}
// 关闭文件
fclose($taxpayersdata);
fclose($résultats);
// 结束
exit;
// --------------------------------------------------------------------------
function cutNewLinechar(string $ligne) {
…
// 结束
return($ligne);
}
// 计算税款
// --------------------------------------------------------------------------
function calculImpot(array $taxAdminData, string $marié, int $enfants, float $salaire) {
// $marié:是,否
// $enfants:子女人数
// $salaire:年薪
// $taxAdminData:税务部门数据
//
// 含子女的税额计算
$result1 = calculImpot2($taxAdminData, $marié, $enfants, $salaire);
$impot1 = $result1["impôt"];
// 无子女的税款计算
if ($enfants != 0) {
$result2 = calculImpot2($taxAdminData, $marié, 0, $salaire);
$impot2 = $result2["impôt"];
// 家庭商数上限的适用
if ($enfants < 3) {
// $PLAFOND_QF_DEMI_PART 欧元(前两名子女)
$impot2 = $impot2 - $enfants * $taxAdminData["PLAFOND_QF_DEMI_PART"];
} else {
// $PLAFOND_QF_DEMI_PART 欧元适用于前两名子女,后续子女金额加倍
$impot2 = $impot2 - 2 * $taxAdminData["PLAFOND_QF_DEMI_PART"] - ($enfants - 2) * 2 * $taxAdminData["PLAFOND_QF_DEMI_PART"];
}
} else {
$impot2 = $impot1;
$result2 = $result1;
}
// 取税率最高的那项,并按其对应的税率和附加费计算
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"];
}
// 计算可能的减免额
$décôte = getDecote($taxAdminData, $marié, $salaire, $impot);
$impot -= $décôte;
// 计算可能的减税额
$réduction = getRéduction($taxAdminData, $marié, $salaire, $enfants, $impot);
$impot -= $réduction;
// 结果
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é:是,否
…
// 结果
return ["impôt" => $impôt, "surcôte" => $surcôte, "taux" => $coeffR[$i]];
}
// revenuImposable=年薪-免税额
// 免税额有最低和最高限额
function getRevenuImposable(array $taxAdminData, float $salaire): float {
…
// 结果
return floor($revenuImposable);
}
// 计算可能的折减
function getDecote(array $taxAdminData, string $marié, float $salaire, float $impots): float {
…
// 结果
return ceil($décôte);
}
// 计算可能的减免
function getRéduction(array $taxAdminData, string $marié, float $salaire, int $enfants, float $impots): float {
…
// 结果
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行: 在函数 [calculerImpot] 中,原先使用全局常量或数组 [limites, coeffR, coeffN] 的位置,现改用作为参数接收的数组 [$taxAdminData] 的内容(第 99、102 行);
- 所有其他函数均按此方式重写;
所得结果与上一版本完全一致。
4.4.2. 结论
02版比01版灵活得多。2020年的税额计算算法很可能与2019年相同,仅税率区间和计算常量会发生变化。 届时只需更新文件 [taxadmindata.json] 即可。而在版本 01 中,则需要进入代码修改税率区间和计算常数。但需要修改税率区间和计算常数值的人员,很可能无法访问算法代码。