4. 应用练习 - IMPOTS
4.1. 问题
我们计划编写一个程序来计算纳税人的税款。我们假设一个简化的情况:纳税人只有工资需要申报:
- 计算员工nbParts的份额数:若未婚,则为nbEnfants/2 +1; 已婚则为 nbEnfants/2+2,其中 nbEnfants 为其子女数。
- 计算其应税收入 R=0.72*S,其中 S 为其年薪
- 计算其家庭系数 Q=R/N
根据以下数据计算其应纳税额 I
12620.0 | 0 | 0 |
13190 | 0.05 | 631 |
15640 | 0.1 | 1290.5 |
24740 | 0.15 | 2072.5 |
31810 | 0.2 | 3309.5 |
39970 | 0.25 | 4900 |
48360 | 0.3 | 6898.5 |
55790 | 0.35 | 9316.5 |
92970 | 0.4 | 12106 |
127860 | 0.45 | 16754.5 |
151250 | 0.50 | 23147.5 |
172040 | 0.55 | 30710 |
195000 | 0.60 | 39312 |
0 | 0.65 | 49062 |
每行有 3 个字段。要计算税款 I,需查找满足 QF<=字段1 的第一行。例如,若 QF=30000,则会找到该行
此时税额 I 等于 0.15*R - 2072.5*nbParts。 如果 QF 使得关系 QF<=field1 从未成立,则使用最后一行中的系数。此处为:
由此得出的税额 I=0.65*R – 49062*nbParts。
4.2. 带数组的版本(impots_01)
我们提供一个初始程序,其中:
- 计算税额所需的数据存储在三个数组中(限额、coeffR、coeffN)
- 纳税人数据(已婚、子女、工资)存储在第一个文本文件中
- 税款计算结果(已婚、子女、工资、税额)存储在第二个文本文件中
<?php
// 常量定义
$DATA = "data.txt";
$RESULTATS = "resultats.txt";
$limites = array(12620, 13190, 15640, 24740, 31810, 39970, 48360, 55790, 92970, 127860, 151250, 172040, 195000);
$coeffR = array(0, 0.05, 0.1, 0.15, 0.2, 0.25, 0.3, 0.35, 0.4, 0.45, 0.5, 0.55, 0.6, 0.65);
$coeffN = array(0, 631, 1290.5, 2072.5, 3309.5, 4900, 6898.5, 9316.5, 12106, 16754.5, 23147.5, 30710, 39312, 49062);
// 读取数据
$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);
// 计算税额
$impôt = calculImpots($marié, $enfants, $salaire, $limites, $coeffR, $coeffN);
// 将结果写入结果文件
fputs($résultats, "$marié:$enfants:$salaire:$impôt\n");
// 下一位纳税人
}
// 关闭文件
fclose($data);
fclose($résultats);
// 结束
exit;
// --------------------------------------------------------------------------
function cutNewLinechar($ligne) {
// 若存在,则删除$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 calculImpots($marié, $enfants, $salaire, $limites, $coeffR, $coeffN) {
// $marié:是,否
// $enfants:子女数量
// $salaire:年薪
// $limites, $coeffR, $coeffN:用于计算税款的数据表
// 份额数
$marié = strtolower($marié);
if ($marié == "oui")
$nbParts = $enfants / 2 + 2;
else
$nbParts=$enfants / 2 + 1;
// 若子女至少3名,则增加1/2份额
if ($enfants >= 3)
$nbParts+=0.5;
// 应税收入
$revenuImposable = 0.72 * $salaire;
// 家庭系数
$quotient = $revenuImposable / $nbParts;
// 置于限额表末尾以终止后续循环
array_push($limites, $quotient);
// 计算税额
$i = 0;
while ($quotient > $limites[$i])
$i++;
// 由于将 $quotient 放置在 $limites 表的末尾,因此前面的循环
// 不能超出数组 $limites
// 现在可以计算税款
return floor($revenuImposable * $coeffR[$i] - $nbParts * $coeffN[$i]);
}
?>
数据文件 data.txt(已婚、子女、工资):
résultats.txt文件(已婚、子女、工资、税款)的处理结果:
oui:2:200000:22504
non:2:200000:33388
oui:3:200000:16400
non:3:200000:22504
oui:5:50000:0
non:0:3000000:1354938
注释
- 第4行:包含纳税人数据(已婚、子女、工资)的文本文件名
- 第5行:包含税款计算结果(婚姻状况、子女、工资、税额)的文本文件名
- 第6-8行:用于计算税款的三个数据表
- 第11-15行:以只读方式打开纳税人数据文件
- 第18-22行:以写入模式打开结果文件
- 第25行:读取纳税人文件中各行数据(已婚、子女、工资)的循环
- 第27行:移除行结束标记
- 第29行:提取该行的组成部分(已婚、子女、工资)
- 第31行:计算税额
- 第33行:将税额存储到结果文件中
- 第37-38行:完全处理完纳税人文件后,关闭文件
- 第44行:该函数用于移除行$ligne中的行尾标记。在Windows系统中,行尾标记为字符串"\r\n";在Unix系统中,行尾标记为字符串"\n"。处理结果为去除行尾标记后的输入字符串。
- 第47-48行:substr($chaîne,$début,$taille) 是 $chaîne 的子字符串,从字符 $début 开始,且长度不超过 $taille 个字符。
- 第 63 行:strtolower($chaîne) 将其转换为小写 $chaîne
- 第 76 行:array_push($tableau,$élément) 将 $élément 添加到数组 $tableau 的末尾。 需要注意的是,数组 $limites 是作为值传递给函数 calculerImpot 的(第 56 行 ))。因此,此项元素的添加是在实际参数的副本上进行的,而该参数本身保持不变。
- 第 84 行:floor($x) 函数返回 $x 的次小整数值。
4.3. 带文本文件的版本(impots_02)
此新版本与前一版本的唯一区别在于,计算税款所需的数据($limites、$coeffR、$coeffN)现已存储在文本文件中,格式如下:
文件 impots.txt:
12620:13190:15640:24740:31810:39970:48360:55790:92970:127860:151250:172040:195000:0
0:0.05:0.1:0.15:0.2:0.25:0.3:0.35:0.4:0.45:0.5:0.55:0.6:0.65
0:631:1290.5:2072.5:3309.5:4900:6898.5:9316.5:12106:16754.5:23147.5:30710:39312:49062
新版本仅需读取 [impots.txt] 文件中的数据,并将它们放入三个表中($limites、$coeffR、$coeffN)。 这又回到了前一种情况。我们仅列出变更部分:
<?php
// 常量定义
$DATA = "data.txt";
$RESULTATS = "resultats.txt";
$IMPOTS = "impots.txt";
// 计算税款所需的数据已放入文件 $IMPOTS
// 每张表格占一行,格式为
// val1:val2:val3,...
list($erreur, $limites, $coeffR, $coeffN) = getTables($IMPOTS);
// 是否出现错误?
if ($erreur) {
print "$erreur\n";
exit;
}//if
// 读取用户数据
...
// 结束
exit;
// --------------------------------------------------------------------------
function cutNewLinechar($ligne) {
// 如果存在,则删除 $ligne 的行尾标记
...
}
// --------------------------------------------------------------------------
function calculImpots($marié, $enfants, $salaire, $limites, $coeffR, $coeffN) {
// $marié:是,否
// $enfants:子女数量
// $salaire:年薪
// $limites, $coeffR, $coeffN:用于计算税款的数据表
...
}
// --------------------------------------------------------------------------
function getTables($IMPOTS) {
// $IMPOTS: 包含表格 $limites、$coeffR、$coeffN 数据的文件名
// 文件 $IMPOTS 是否存在?
if (!file_exists($IMPOTS))
return array("Le fichier $IMPOTS n'existe pas","","","");
// 批量读取文件
$tables = file($IMPOTS);
if (!$tables)
return array("Erreur lors de l'exploitation du fichier $IMPOTS","","","");
// 创建 3 个表——假设行在语法上正确
$limites = explode(":", cutNewLineChar($tables[0]));
$coeffR = explode(":", cutNewLineChar($tables[1]));
$coeffN = explode(":", cutNewLineChar($tables[2]));
// 结束
return array("", $limites, $coeffR, $coeffN);
}
?>
注释
- 第 11 行:函数 getTables($IMPOTS) 利用文本文件 $IMPOTS 从中提取三个数组 ($limites、$coeffR、$coeffN)。生成的结果为($erreur、 $limites、$coeffR、$coeffN),其中 $erreur 是一个错误消息,若未发生错误则该文件为空。
- 第 40 行:函数 getTables($IMPOTS)。
- 第 43 行: 函数 files_exists(nom_fichier) 返回布尔值 true(若文件 nom_fichier 存在),否则返回布尔值 false。
- 第 46 行:函数 file(nom_fichier) 读取整个文本文件 nom_fichier。它返回一个数组,其中每个元素代表文本文件中的一行。由于此处文件 impots.txt 包含三行文本,因此该函数将返回一个包含三个元素的数组。
- 第50-52行:利用获取的这三行文本创建三个数组($limites、$coeffR、$coeffN)
所得结果与上一版本相同。