- 编写高质量代码:改善C程序代码的125个建议
- 马伟 著
- 1365字
- 2025-03-16 17:14:06
建议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之间的数字),它的分数表示形式如下:

上面阐述了有限小数的表示形式,但对于无限循环小数如何表示呢?
其实仍然可以使用上面的这种形式来表示。但无限循环小数比有限小数多了一个循环部分,因此我们可以用如下方式来表示:

其中,(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中,通过分数的形式表示浮点数,从而避免浮点数因为舍入误差而导致的不精确性问题。有兴趣的朋友可以在这个示例代码的基础继续深入研究,从而使该方案能够用于实际的开发环境中。

图1-32 代码清单1-20的运行结果