本节书摘来自华章计算机《编写高质量代码:改善c程序代码的125个建议》一书中的第1章,建议3-3:,作者:马 伟 更多章节内容可以访问云栖社区“华章计算机”公众号查看。
建议3-3:使用分数来精确表达浮点数
在上面的建议3-1与建议3-2中,已经明确阐述,由于计算机的字长有限,浮点数能够精确表示的数是有限的。因此,在C语言中使用float或者double类型来存储小数是不能得到精确值的。虽然在建议3-2的最后提出使用整数的方法来解决浮点数的精确计算问题,但这种方案不具备代表性和通用性。因此,本建议将向你介绍第二种精确表示浮点数的解决方法,即用分数来表示浮点数。
对于一个浮点数,我们可以简单地把它分解成整数和纯小数两个部分来分开表示。比如浮点数据10.234,它的整数部分是10,纯小数部分是0.234。而对于纯小数部分,受计算机字长的限制,存储时会因为舍入规则而导致失去精确性。因此,这个时候就可以将纯小数部分使用分数来表示。比如,对于有限小数,我们可以这样来表示:
由此,我们可以很简单地推导出它的表示形式。对于有限小数X=0.a1a2…an(其中,a1,a2,…,an为0~9之间的数字),它的分数表示形式如下:
https://yqfile.alicdn.com/f0884c7fe939f7fcbb14aff4bd04fad29f0c29e0.png" >
上面阐述了有限小数的表示形式,但对于无限循环小数如何表示呢?
其实仍然可以使用上面的这种形式来表示。但无限循环小数比有限小数多了一个循环部分,因此我们可以用如下方式来表示:
https://yqfile.alicdn.com/c985f31bb7de7773e18487acb223a3b6a3bc905f.png" >
其中,(3)表示循环体。即,对于无限循环小数X=0.a1a2…an(b1b2…bm)(其中,a1,a2,…,an与b1,b2,…,bm都是0~9的数字,a1a2…an表示非循环部分,括号部分(b1b2…bm)表示循环部分),它的表现形式如下:
至于循环部分(b1b2…bm),我们可以这样处理,即令Y=0.b1b2…bm,那么:
将Y代入X,即:
也就是:
例如,对于小数0.3(3),根据上述方法转化为分数应为:
下面,我们通过一个示例程序演示一下如何在C语言程序中使用这种表达方式将浮点数表示为分数形式。值得注意的是,这里的精度只能达到long int类型,再大就会发生溢出。如代码清单1-20所示。
代码清单1-20 分数表达浮点数示例
#include<stdio.h>
#include<math.h>
#include<stdlib.h>
#include<string.h>
long dtol( double d );
long gcd(long a, long b);
void convertdata(char *str);
struct
{
long zhengshu;
long xiaoshu;
long xunhuan;
long fenmu;
long fenzi;
}fenshu;
union udtol
{
double d;
long l;
};
long dtol( double d )
{
union udtol to;
to.d = d + 6755399441055744.0;
return to.l;
}
long gcd(long a, long b)
{
if(!b)
{
return a;
}
else
{
return gcd(b, a % b);
}
}
void convertdata(char *str)
{
int n = 0;
int m = 0;
int len=0;
int len_1 = 0;
int len_2 = 0;
int len_3 = 0;
char *p1;
char *p2;
char *p3;
int i=0;
int j=0;
int z=0;
long gcb=0;
fenshu.zhengshu =0;
fenshu.xiaoshu =0;
fenshu.xunhuan = 0;
len = strlen(str);
len_1 = len;
p1 = strchr(str, '.');
p2 = strchr(str, '(');
p3 = strchr(str, ')');
if(p1)
{
len_1 = p1 - str;
}
if(!p2)
{
len_2 = len - len_1 - 1;
}
if(p3)
{
len_2 = p2 - p1 - 1;
len_3 = p3 - p2 - 1;
}
n = len_2;
m = len_3;
for(i = 0; i < len_1; i++)
{
fenshu.zhengshu *= 10;
fenshu.zhengshu += str[i] - '0';
}
for(j = 0; j < len_2; j++)
{
fenshu.xiaoshu *= 10;
fenshu.xiaoshu += str[len_1+1+j] - '0';
}
for(z = 0; z < len_3; z++)
{
fenshu.xunhuan *= 10;
fenshu.xunhuan += str[len_1+len_2+2+z] - '0';
}
fenshu.fenmu =dtol((pow(10.0, (double)(m)) - 1.0)
* (pow(10.0, (double)(n))));
fenshu.fenzi = dtol(fenshu.xiaoshu
* (pow(10.0, (double)(m)) - 1.0)
+ fenshu.xunhuan);
gcb = gcd(fenshu.fenzi, fenshu.fenmu);
fenshu.fenmu /= gcb;
fenshu.fenzi /= gcb;
}
int main(void)
{
for(;;)
{
char str[200] = "";
printf("请输入要转换的数(如25.444(234)):");
scanf("%s",&str);
convertdata(str);
printf("整数部分:%ld--小数部分:%ld--循环部分:%ld\n",
fenshu.zhengshu,fenshu.xiaoshu,fenshu.xunhuan);
printf("所得分数为:%ld/%ld\n",
fenshu.fenzi,fenshu.fenmu);
printf("-------------------------------------------\n");
}
return 0;
}
代码清单1-20的运行结果如图1-32所示。
在图1-32中,通过分数的形式表示浮点数,从而避免浮点数因为舍入误差而导致的不精确性问题。有兴趣的朋友可以在这个示例代码的基础继续深入研究,从而使该方案能够用于实际的开发环境中。
https://yqfile.alicdn.com/ebb3663a8c1fd4f088d9be6a9380b73c8d892001.png" >