Skip to content

4. 应用练习——第1版和第2版

4.1. 问题

Image

上表用于计算纳税人仅申报工资收入的简化情况下的税额。如注释(1)所示,由此计算出的税额是未考虑以下三项机制的税额:

  • 针对高收入群体的家庭商数上限;
  • 针对低收入群体的税额减免和减税;

因此,税额计算包括以下步骤 [http://impotsurlerevenu.org/comprendre-le-calcul-de-l-impot/1217-calcul-de-l-impot-2019.php]

Image

我们拟编写一个程序,用于计算纳税人的税额,具体针对仅需申报工资收入的简化情况:

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 个字段:champ1champ2champ3。 要计算税款 I,需查找满足 QF<=字段1 的第一行,并取该行的数值。例如,对于一名已婚且有两个孩子的员工,年薪 S 为 50000 欧元:

应税收入:R=0.9*S=45000

份额数:nbParts=2+2*0.5=3

家庭分额:QF=45000/3=15000

满足 QF<=field1 的第一行如下:

    27519        0.14    1394.96

因此,税额 I 等于 0.14*R – 1394.96*nbParts=[0,14*45000-1394,96*3]=2115。税额向下四舍五入至整欧元。

若从第一行起即满足QF<=field1,则税额为零。

若 QF 满足关系 QF<=field1 从未成立,则使用最后一行中的系数。此处:

    0            0.45        20163.45

由此得出应纳税额 I=0.45*R – 20163.45*nbParts

4.1.2. 家庭商数上限

Image

要判断家庭商数上限 QF 是否适用,需重新计算不含子女的应纳税额。仍以已婚、育有两名子女且年薪 S 为 50000 欧元的雇员为例:

应税收入:R=0.9*S=45000

分额数:nbParts=2(不再计入子女)

家庭分额:QF=45000/2=22500

满足 QF<=field1 的第一行如下:

    27519        0.14    1394.96

因此,税额 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. 减免额的计算

Image

仍以那位已婚、育有两名子女且年薪S为50000欧元的雇员为例:

上一步计算出的应纳税额(2115)低于已婚夫妇的2627欧元(单身人士为1595欧元):因此适用减免。其计算方式如下:

减免额 = 起征点(已婚夫妇=1970/单身=1196)-0.75 × 应纳税额

减免额=1970-0.75*2115=383.75,四舍五入为384欧元。

新应纳税额=2115-384=1731欧元

4.1.4. 减税额计算

Image

当收入低于一定门槛时,对前文计算出的应纳税额给予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. 脚本目录结构

Image

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(已婚、子女、工资):

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

所得结果的文件 résultats.txt(已婚、子女、工资、税款、附加费、减免、减税、税率):

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

注释

  • 第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(已婚、子女、工资):

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

根据所得结果生成的文件 résultats.txt(已婚、子女、工资、税款、附加费、减免、减税、税率):

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

所得结果与税务部门模拟器计算结果一致。

4.3.3. 结论

即使在看似简单的情况下,税款计算算法也相当复杂。我们不再赘述。随着版本的更新,尽管界面有所调整,其核心逻辑将保持不变。因此,我们仅对界面调整部分进行说明。

4.4. 第2版

4.4.1. 修改内容

在上一版本中,计算税款所需的数据以常量和数组的形式硬编码在程序中。这种做法应予禁止。在新版本中,这些数据被移至外部文件 jSON 中:

Image

文件 [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 中,则需要进入代码修改税率区间和计算常数。但需要修改税率区间和计算常数值的人员,很可能无法访问算法代码。