数学基础
早在小学的时候我就对循环小数非常感兴趣,加上初中和高中对循环小数可以说有一定基础研究,因此想到写一个将循环下小数转换为分数的程序,非常有意思,并且对初学者来说,它的输入输出格式的转换也是一大难点。
首先必须明确一点,循环小数必定可以转换为分数,原因在于循环小数总可以分解为
不循环的有限部分
+
循环的无限部分
。前者对应一个分数,后者可以写成一个收敛的等比数列的和,也必定是可以转换为一个分数的。例如0.234343434…,有限部分为0.2=1/5,无限部分为0.0343434…=0.034/(1-0.01)=34/990=17/495;0.2343434…=1/5+17/495=116/495。同理,0.879879879…=0.879/(1-0.001)=879/999。
所以任何一个循环小数都可以化为分数,并且通过上述两个例子,我们也对如何转换有了一个初步的了解。
编程思路
- 首先我们得设置一下输入的格式,有限部分和循环部分显然是要分开的,为了方便起见,我在程序里设置的是空格分开。 需要的头文件分别是**<math.h>,<stdio.h>,<stdlib.h>,<string.h>**。
- 第二,由于是纯数学运算,来不得半点近似,所有的分数运算我们都需要保留分子和分母,所以保存一个小数的变量是两个整形——分子(num)和分母(den),不妨定义一个分数(Decimal)结构体类型,里面包含整形数据的分子和分母。
- 虽然说处理的是整形变量,但是由于我们输入是浮点数,扫描会带来麻烦,所以扫描的时候我还是采用%s的格式一股气扫进去的,根据小数点来个划分。这里还用到了<stdlib.h>当中的itoa和atoi函数,这两个函数可以求出正数的长度。当然,你也可以用<math.h>当中的log10()来求取长度,这两种方法在我的程序中皆有涉及。
- 对于分数的处理,显然是要用到化简函数了,化简分数无非就是分子分母同时除以它们的最大公约数,因此需要写一个辗转相除法求最大公约数的程序。分数相加的原理也很简单,先对每个分数化简,再按照手算的方法相加,最后调用化简分数的函数对其化简即可。
-
计算有限小数部分的方法和循环小数部分的不同,这一点在第一部分里面已经提到,分别获取有限部分的位长以及循环部分的位长很关键。例如转换一个小数0.2343434…,假设输入0.2 34代表了这个循环小数,用
scanf("%s%s",str1,str2)
函数输入,那么strlen(str2)就是循环部分的位长。有限部分的位长,比循环部分的计算稍微复杂一些,你可以利用strstr函数舍掉小数点前面的部分,当然,小数点前面的部分不代表就没有用,毕竟我们输入的小数有可能大于1,因此按照如下方法处理即可:
float f1;
int integer,dec_length,cir_length;
f1=atof(str1);
integer=(int)f1;//整数部分
dec_length=strlen(strstr(str1,".")+1);//有限小数部分的长度,strstr(str1,".")+1是小数点后的一个数的指针,再取strlen即小数部分的长度
cir_length=strlen(str2);//循环小数部分长度
这几步非常关键,牵涉到我们的分母到底应该怎么写,如果分母有差错,那之后的化简都是白搭。
- 这里需要说明的是,由于我们是按照小数点分隔的,没有考虑到负号的情况,所有本程序暂时只支持输入正的小数,当然即使是负小数,也能很容易通过本程序找到答案。
代码
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <stdlib.h>
struct Decimal{
int num;
int den;
};//定义分数结构体
void simplify(int *num,int *den);//化简一个分数的分子和分母
int GCD(int a,int b);//求两个数的最大公约数
int main()
{
char str1[100],str2[100],int1[10];//存放两个分数的字符串,后续再处理
float f1;
int integer,dec1,num,den;//integer是整个小数的整数部分,dec1是有限部分的小数部分(不含小数点的),num和den分别是最终结果分数的分子和分母
struct Decimal Dec,Cir;//小数部分对应的分数结构体(有限部分+循环部分)
printf("*******小数转换为分数的实验*********\n");
printf("请输入一串小数,如有循环节,请和有限部分用空格隔开,如没有循环节,请用0代替。\n");
printf("例如0.2 34代表0.234343434......,3.8753 0代表3.8753\n");//对输入的一个简单说明
scanf("%s%s",str1,str2);//这里不能用%f扫入,否则将不知道小数长度
f1=atof(str1);//将输入的字符转换为浮点数
integer=(int)f1;//取整数部分
dec1=atoi(strstr(str1,".")+1);
itoa(integer,int1,10);//integer转为字符串,方便计算长度
Dec.num=dec1;//有限小数的小数部分(注意是整形的,指的是不含小数点的小数部分)
Dec.den=pow(10,strlen(str1)-strlen(int1)-1);
Cir.num=atoi(str2);
Cir.den=pow(10,strlen(str2))-1;
Cir.den*=pow(10,(int)(log10(dec1)+1));
if(Cir.num==0)
{
simplify(&Dec.num,&Dec.den);
num=Dec.num+integer*Dec.den;
den=Dec.den;
simplify(&num,&den);
if(integer!=0)printf("转换为带分数的结果为:%d+%d/%d\n",integer,Dec.num,Dec.den);
printf("转换为分数的结果为:%d/%d\n",num,den);
return 0;
} //如果循环小数是0,直接转换有限部分即可,否则需要将循环部分加以转换,并与有限部分的分数相加
simplify(&Dec.num,&Dec.den);
simplify(&Cir.num,&Cir.den);
den=Dec.den*Cir.den;
num=Dec.num*Cir.den+Dec.den*Cir.num;
if(integer!=0)printf("转换为带分数结果为:%d+%d/%d\n",integer,num,den);
num+=den*integer;
printf("转换为分数结果为:%d/%d\n",num,den);
return 0;
}
int GCD(int a,int b)
{
//利用辗转相除法求两个数的最大公约数
if(a==1||b==1)return 1;
int t;
if(a<b){t=a;a=b;b=t;}//保证a是大数
while(b>0)
{
t=b;//t临时存放小数
b=a%b;//小数是上一次两个数相除的余数
a=t;//大数是上一次相除的小数
}
return a;
}
void simplify(int *num,int *den)
{
int gcd=GCD(*num,*den);
*num=*num/gcd;
*den=*den/gcd;
}
那么好,我们来输入一个循环小数进行验证,如果位数比较多的话,出来的应该是个变态的答案,当然你要相信自己是对的:
下面我们用Win10自带的计算器验证一下(科学模式)
说明我们的结果没问题,大功告成。
版权声明:本文为weixin_44044411原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。