《编写高质量代码:改善c程序代码的125个建议》——建议3-3:使用分数来精确表达浮点数

简介:

本节书摘来自华章计算机《编写高质量代码:改善c程序代码的125个建议》一书中的第1章,建议3-3:,作者:马 伟 更多章节内容可以访问云栖社区“华章计算机”公众号查看。

建议3-3:使用分数来精确表达浮点数

在上面的建议3-1与建议3-2中,已经明确阐述,由于计算机的字长有限,浮点数能够精确表示的数是有限的。因此,在C语言中使用float或者double类型来存储小数是不能得到精确值的。虽然在建议3-2的最后提出使用整数的方法来解决浮点数的精确计算问题,但这种方案不具备代表性和通用性。因此,本建议将向你介绍第二种精确表示浮点数的解决方法,即用分数来表示浮点数。
对于一个浮点数,我们可以简单地把它分解成整数和纯小数两个部分来分开表示。比如浮点数据10.234,它的整数部分是10,纯小数部分是0.234。而对于纯小数部分,受计算机字长的限制,存储时会因为舍入规则而导致失去精确性。因此,这个时候就可以将纯小数部分使用分数来表示。比如,对于有限小数,我们可以这样来表示:


f9e035b8934cafb4ef5a44a6a529598433e5d842

由此,我们可以很简单地推导出它的表示形式。对于有限小数X=0.a1a2…an(其中,a1,a2,…,an为0~9之间的数字),它的分数表示形式如下:


<a href=https://yqfile.alicdn.com/f0884c7fe939f7fcbb14aff4bd04fad29f0c29e0.png" >

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


<a href=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)表示循环部分),它的表现形式如下:


a7add4b19f60ff96e230dbea6514157d6a5d76dd

至于循环部分(b1b2…bm),我们可以这样处理,即令Y=0.b1b2…bm,那么:

5ca6055b109ef4570734b8856c24e8c9e76fcd8c

将Y代入X,即:


a089496d2253520e7eafefecb37b30a1b7c39855


593d5ff56e65d8d29b8e29a1abf361849fb98724

也就是:


83cbc7b48e0f74ccb334df147109d22918aa15c1

例如,对于小数0.3(3),根据上述方法转化为分数应为:


0f1eb749efa06392b855e7451d2a3cefaac41db4

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


<a href=https://yqfile.alicdn.com/ebb3663a8c1fd4f088d9be6a9380b73c8d892001.png" >
相关文章
|
SQL 前端开发 测试技术
测试用例的设计? 万能公式
测试用例的设计? 万能公式
79 0
测试用例的设计? 万能公式
数学_算法_语言之有趣有用的二进制运算
### 大道至简的0和1 ![image.png](https://intranetproxy.alipay.com/skylark/lark/0/2019/png/127536/1564730230613-8f4a10fe-1e32-4625-bee9-3534fc134b70.png)   据说计算机的0和1二进制编码方式灵感来自于中国古老的太极八卦中一阴一阳,长横