在C语言的学习过程中,我们发现大多的小数(double, float)因采用二进制的存储方式,会有精度丢失的问题,若再进行更多的运算,则精度丢失更加严重。对于一个强迫症程序员来说,这种精度丢失的问题想想就会觉得难受,因而,我想到了用结构体存储分数来进行运算,这样在有理数范围内,若想要小数的表达结果,那么只有最后一步有精度丢失,话不多说,直接上代码:
定义分数结构
// 定义分数
typedef struct
{
int numerator=0; // 分子
int denominator=1; // 分母
} fraction;
在定义分数结构的时候,我们将分子初始化为0,分母初始化为1,这样就可以使分式的值为0且有意义。
辗转相除法
// 辗转相除法
int gcd(int m, int n)
{
int t;
while (t = m % n)
{
m = n;
n = t;
}
return n;
}
由于分数在运算过程中难免会产生分子分母出现公因子的情况,所以引入辗转相除函数进行约分。(gcd有很多的博主已经介绍过,这里不多赘述)。
约分
// 约分
fraction reducefraction(fraction a)
{
int divisor = gcd(a.numerator, a.denominator);
a.numerator /= divisor, a.denominator /= divisor;
if (a.numerator > 0 && a.denominator < 0)
a.numerator = -a.numerator, a.denominator = -a.denominator;
return a;
}
(divisor为因子的意思,这里取分子分母的最大公约数。)进行约分的过程中可能会出现分母为负数但分子为正数的情况,这对于数值计算来说可能没有什么影响,但是如果题目对输出格式有要求,那这样可能导致无法AC。当然,如果你不想在这里设if使每次gcd都跑一次,那就把它移到输出分数的函数里吧。
读取分数
//读取分数
void readfraction(fraction *a)
{
scanf("%d", &a->numerator);
if(getchar()!='/')
{
a->denominator = 1;
return;
}
scanf("%d", &a->denominator);
/*这里设置查错点,防止分母为0的分数生成,但是考试一般情况是不会给这样的数据点的
if(a->denominator == 0)
perror("Error!!!");
*/
*a = reducefraction(*a);
}
用这个读取分数的函数就能够读取形如1/2之类的分数了,当然你也可以只输入一个整数,这样getchar()的内容就是' '或者'\n',就进入if的管辖范围了。
输出分数
// 输出分数
void writefraction(fraction a)
{
printf("%d/%d", a.numerator, a.denominator);
}
分数加法
// 分数相加
fraction plusfraction(fraction a, fraction b, int deter)
{
fraction c;
c.numerator = a.numerator * b.denominator + a.denominator * b.numerator;
c.denominator = a.denominator * b.denominator;
if (deter == 1)
{
// 如果有连续多个分数相加,可以最后gcd,但要注意数据范围
return reducefraction(c);
}
return c;
}
(笔者还没有学会不定形参读入,如果有大佬会的话欢迎留言,万分感谢!!)这里用一个判据deter用来根据要求是否进行约分的操作,比如在for循环内就可以输入0,只有两个数相加的话就输入1,int可以换成bool(需要stdbool.h)。如果你题目时间充裕,当然可以删去这个判据,这样会使你的代码更好看点。
求分数相反数
//求分数相反数
fraction oppfraction(fraction a)
{
a.numerator = -a.numerator;
return a;
}
用相反数来定义减法。
分数乘法
// 分数相乘
fraction multifraction(fraction a, fraction b, int deter)
{
a.numerator *= b.numerator, a.denominator *= b.denominator;
if (deter == 1)
{
// 如果有连续多个分数相加,可以最后gcd,但要注意数据范围
return reducefraction(a);
}
return a;
}
求分数倒数
// 倒数,分数除法可用
fraction reciprocal(fraction a)
{
a.denominator ^= a.numerator;
a.numerator ^= a.denominator;
a.denominator ^= a.numerator;
return a;
}
用倒数的话就可以将除法变为乘法,而不需要再转门定义除法运算了。
求分数幂运算
// 分数幂
fraction powerfraction(fraction a, int power)
{
fraction b = {1, 1};
while (power > 0)
{
if (power & 1)
{
b = multifraction(b, a, 0);
}
power >>= 1;
a = multifraction(a, a, 0);
}
return b;
}
这里引入了fastpower函数的运算思想,这个也有很多博主讲过,也不多赘述。
化整数为分数
//化整为分
fraction tintofr(int num)
{
fraction a = {num, 1};
return a;
}
可以看出上面的函数都是分数之间的运算,那么如果一开始输入的是其他的类型的数据,那么就需要一个转化类型的函数,这个是整数转分数。
总览
// 定义分数
typedef struct
{
int numerator; // 分子
int denominator; // 分母
} fraction;
// 辗转相除法
int gcd(int m, int n)
{
int t;
while (t = m % n)
{
m = n;
n = t;
}
return n;
}
// 约分
fraction reducefraction(fraction a)
{
int divisor = gcd(a.numerator, a.denominator);
a.numerator /= divisor, a.denominator /= divisor;
if (a.numerator > 0 && a.denominator < 0)
a.numerator = -a.numerator, a.denominator = -a.denominator;
return a;
}
//读取分数
void readfraction(fraction *a)
{
scanf("%d", &a->numerator);
if(getchar()!='/')
{
a->denominator = 1;
return;
}
scanf("%d", &a->denominator);
/*这里设置查错点,防止分母为0的分数生成,但是考试一般情况是不会给这样的数据点的
if(a->denominator == 0)
perror("Error!!!");
*/
*a = reducefraction(*a);
}
// 输出分数
void writefraction(fraction a)
{
printf("%d/%d", a.numerator, a.denominator);
}
// 分数相加
fraction plusfraction(fraction a, fraction b, int deter)
{
fraction c;
c.numerator = a.numerator * b.denominator + a.denominator * b.numerator;
c.denominator = a.denominator * b.denominator;
if (deter == 1)
{
// 如果有连续多个分数相加,可以最后gcd,但要注意数据范围
return reducefraction(c);
}
return c;
}
//相反数,分数减法可用
fraction oppfraction(fraction a)
{
a.numerator = -a.numerator;
return a;
}
// 分数相乘
fraction multifraction(fraction a, fraction b, int deter)
{
a.numerator *= b.numerator;
a.denominator *= b.denominator;
if (deter == 1)
{
// 如果有连续多个分数相加,可以最后gcd,但要注意数据范围
return reducefraction(a);
}
return a;
}
// 倒数,分数除法可用
fraction reciprocal(fraction a)
{
a.denominator ^= a.numerator;
a.numerator ^= a.denominator;
a.denominator ^= a.numerator;
return a;
}
// 分数幂
fraction powerfraction(fraction a, int power)
{
fraction b = {1, 1};
while (power > 0)
{
if (power & 1)
{
b = multifraction(b, a, 0);
}
power >>= 1;
a = multifraction(a, a, 0);
}
return b;
}
// 化整为分
fraction tintofr(int num)
{
fraction a = {num, 1};
return a;
}
这里暂时没有引入小数转分数是因为笔者遇到了精度丢失的问题,笔者会尽量思考相应的解决方法再进行补充,感谢观看!