C语言分数运算拓展(学习笔记)

在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;
}

这里暂时没有引入小数转分数是因为笔者遇到了精度丢失的问题,笔者会尽量思考相应的解决方法再进行补充,感谢观看!

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值