从零开始构建分数的四则运算体系——以【PAT B1034 有理数四则运算】为例
PAT 乙级练习 题解合集
题目
本题要求编写程序,计算 2 个有理数的和、差、积、商。
输入格式:
输入在一行中按照 a1/b1 a2/b2 的格式给出两个分数形式的有理数,其中分子和分母全是整型范围内的整数,负号只可能出现在分子前,分母不为 0。
输出格式:
分别在 4 行中按照 有理数1 运算符 有理数2 = 结果 的格式顺序输出 2 个有理数的和、差、积、商。注意输出的每个有理数必须是该有理数的最简形式 k a/b,其中 k 是整数部分,a/b 是最简分数部分;若为负数,则须加括号;若除法分母为 0,则输出 Inf。题目保证正确的输出中没有超过整型范围的整数。
输入样例 1:
2/3 -4/2
输出样例 1:
2/3 + (-2) = (-1 1/3)
2/3 - (-2) = 2 2/3
2/3 * (-2) = (-1 1/3)
2/3 / (-2) = (-1/3)
输入样例 2:
5/3 0/6
输出样例 2:
1 2/3 + 0 = 1 2/3
1 2/3 - 0 = 1 2/3
1 2/3 * 0 = 0
1 2/3 / 0 = Inf
思路
写在前面的废话
我写这道题的思路是定义一个结构Fraction
来表示任意分数,并以此为基础把分数的化简、四则运算、打印全部写成函数,属于过于一般化的做法(意思就是我写复杂了,光是为了做这个题完全没必要)。如果只追求这道题的 AC 的话可以把输入看成四个分离的数字来处理(没错我就是写完才发现的),如果你追求用简洁的方式把这道题做对,请参考柳神的代码。如果你对分数运算的一般化处理感兴趣的话,请继续阅读。完整的 AC 代码在最下面。
定义一个分数 Fraction
那么我们就从零开始了,首先我们得想个办法存储一个分数,结构体是个不错的选择(或者 C艹 选手愿意写成类也可以)。
虽然题目保证正确的输出中没有超过整型范围的整数,但是在运算中可能有溢出的情况出现,为保险起见,全部定义成long long
类型。
typedef long long LL;
typedef struct {
LL up, down; // 分子和分母
} Fraction;
如上所示,成员up
表示分子,down
表示分母。
化简 reduction
在分数的运算中,化简的重要性仅次于定义,因为化简使得我们体系中的分数都是标准化的。化简意味着三件事情:
- 如果分子是
0
的话,分母的大小是没有意义的,把分母变成1
方便计算; - 为了统一起见,我们希望负号只能出现在分子中。所以如果分母是负数,分子和分母都等于各自的相反数;
- 约分。约分实质上就是分子和分母同时除以他们的最大公约数。如果你不了解最大公约数的算法,可以搜索辗转相除法。
以下就是化简相关的代码,注意在求最大公约数的时候要对分子取绝对值(为什么分母不用取绝对值?因为第 2 步保证了分母非负):
// 返回 a 的绝对值
LL abs(LL a) {
return a < 0 ? -a : a;
}
// 返回 a 和 b 的最大公约数
LL gcd(LL a, LL b) {
return b ? gcd(b, a % b) : a;
}
// 对分数 a 进行约分,返回约分结果
Fraction reduction(Fraction a) {
if (a.up == 0) {
a.down = 1;
return a;
}
if (a.down < 0) {
a.up = -a.up;
a.down = -a.down;
}
LL g = gcd(abs(a.up), a.down);
a.up /= g;
a.down /= g;
return a;
}
加法 add
这里我们需要回忆一下小学的分数加法公式:
接着不要忘记对结果进行化简。代码如下:
Fraction add(Fraction a, Fraction b) {
Fraction ans;
ans.up = a.up * b.down + a.down * b.up;
ans.down = a.down * b.down;
ans = reduction(ans);
return ans;
}
减法 sub
Fraction sub(Fraction a, Fraction b) {
Fraction ans;
b.up = -b.up;
ans = add(a, b); // 加的同时已经化简过了
return ans;
}
乘法 mul
Fraction mul(Fraction a, Fraction b) {
Fraction ans;
ans.up = a.up * b.up;
ans.down = a.down * b.down;
ans = reduction(ans);
return ans;
}
除法 div
注意如果d == 0
即我们代码中b.up == 0
的话需要特殊处理,这里把结果的分母变成-1
来表示无穷。
至此,四则运算已经结束,还剩输出。
Fraction div(Fraction a, Fraction b) {
Fraction ans;
if (b.up == 0) { // 用分母出现 -1 来表示无穷
ans.down = -1;
return ans;
}
ans.up = a.up * b.down;
ans.down = a.down * b.up;
ans = reduction(ans);
return ans;
}
打印单个分数 print
- 先检查分母是不是
-1
,如果是的话代表无穷,直接输出Inf
; - 如果分子是
0
,直接输出0
; - 根据题目要求,如果是负数,两边要带圆括号,圆括号里面
-
开头; - 如果存在整数部分,输出;
- 如果整数和分数部分都存在,输出一个空格;
- 如果存在分数部分,输出。
void print(Fraction a) {
if (a.down == -1) { // 用分母出现 -1 来表示无穷
printf("Inf");
return;
}
if (a.up == 0) {
printf("0");
return;
}
if (a.up < 0)
printf("(-");
if (a.up / a.down)
printf("%lld", abs(a.up) / a.down);
if (a.up / a.down && abs(a.up) % a.down)
putchar(' ');
if (a.up % a.down)
printf("%lld/%lld", abs(a.up) % a.down, a.down);
if (a.up < 0)
putchar(')');
}
打印带过程的一行计算 printCalculation
这里参数opration
是一个函数指针,可以是以上四则运算中的任何一个函数。
typedef Fraction(*func)(Fraction, Fraction); // 定义 fun_t 为函数指针类型
void printCalculation(Fraction a, Fraction b, char ch, func opration) {
print(a);
printf(" %c ", ch);
print(b);
printf(" = ");
Fraction ans = opration(a, b);
print(ans);
putchar('\n');
}
所有函数都写完了,完整的 AC 代码在下面。
代码
#include <stdio.h>
typedef long long LL;
typedef struct {
LL up, down; // 分子和分母
} Fraction;
LL abs(LL a) { // 绝对值
return a < 0 ? -a : a;
}
LL gcd(LL a, LL b) { // 最大公约数
return b ? gcd(b, a % b) : a;
}
Fraction reduction(Fraction a) { // 约分
if (a.up == 0) {
a.down = 1;
return a;
}
if (a.down < 0) {
a.up = -a.up;
a.down = -a.down;
}
LL g = gcd(abs(a.up), a.down);
a.up /= g;
a.down /= g;
return a;
}
Fraction add(Fraction a, Fraction b) { // 加法
Fraction ans;
ans.up = a.up * b.down + a.down * b.up;
ans.down = a.down * b.down;
ans = reduction(ans);
return ans;
}
Fraction sub(Fraction a, Fraction b) { // 减法
Fraction ans;
b.up = -b.up;
ans = add(a, b);
return ans;
}
Fraction mul(Fraction a, Fraction b) { // 乘法
Fraction ans;
ans.up = a.up * b.up;
ans.down = a.down * b.down;
ans = reduction(ans);
return ans;
}
Fraction div(Fraction a, Fraction b) { // 除法
Fraction ans;
if (b.up == 0) { // 用分母出现 -1 来表示无穷
ans.down = -1;
return ans;
}
ans.up = a.up * b.down;
ans.down = a.down * b.up;
ans = reduction(ans);
return ans;
}
void print(Fraction a) { // 按照题目要求打印分数
if (a.down == -1) { // 用分母出现 -1 来表示无穷
printf("Inf");
return;
}
if (a.up == 0) {
printf("0");
return;
}
if (a.up < 0) printf("(-");
if (a.up / a.down) printf("%lld", abs(a.up) / a.down);
if (a.up / a.down && abs(a.up) % a.down) putchar(' ');
if (a.up % a.down) printf("%lld/%lld", abs(a.up) % a.down, a.down);
if (a.up < 0) putchar(')');
}
// 根据指定的操作数,运算符,运算函数输出
typedef Fraction(*func)(Fraction, Fraction); // 定义 fun_t 为函数指针类型
void printCalculation(Fraction a, Fraction b, char ch, func opration) {
print(a);
printf(" %c ", ch);
print(b);
printf(" = ");
Fraction ans = opration(a, b);
print(ans);
putchar('\n');
}
int main() {
Fraction a, b;
scanf("%lld/%lld", &a.up, &a.down);
scanf("%lld/%lld", &b.up, &b.down);
a = reduction(a);
b = reduction(b);
printCalculation(a, b, '+', add);
printCalculation(a, b, '-', sub);
printCalculation(a, b, '*', mul);
printCalculation(a, b, '/', div);
return 0;
}