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+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 个字段:field1field2field3。要计算税款 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 的第一行如下:

    27519        0.14    1394.96

因此,税款 I 等于 0.14*R – 1394.96*股数=[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为50,000欧元的已婚雇员为例:

应税收入:R = 0.9 * S = 45,000

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

家庭商数:QF = 45,000 / 2 = 22,500

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

    27519        0.14    1394.96

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

Image

仍以那位有两个孩子、年薪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. 减税额计算

Image

当应纳税额低于特定门槛时,将对前文计算得出的应纳税额适用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. 脚本目录结构

Image

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

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

结果文件 results.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 行:将键 [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 数据文件(已婚、子女、工资):

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

结果文件 results.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
 
// 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中,必须直接修改代码才能调整税率区间和计算常数。然而,需要修改这些参数的人员通常无法访问算法源代码。