《编写高质量代码:改善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" >
相关文章
|
消息中间件 物联网
MQTT常见问题之mqtt 报 MqttException:客户机未连接如何解决
MQTT(Message Queuing Telemetry Transport)是一个轻量级的、基于发布/订阅模式的消息协议,广泛用于物联网(IoT)中设备间的通信。以下是MQTT使用过程中可能遇到的一些常见问题及其答案的汇总:
|
C语言
C语言之斗地主游戏
该代码实现了一个简单的斗地主游戏,包括头文件引入、宏定义、颜色枚举、卡牌类、卡牌类型类、卡牌组合类、玩家类、游戏主类以及辅助函数等,涵盖了从牌的生成、分配、玩家操作到游戏流程控制的完整逻辑。
399 8
axios全局做节流,解决多次点击导致多次请求接口
本文介绍了如何在Axios请求中实现全局节流,以防止用户快速多次点击导致重复发送相同请求的问题。
314 2
|
存储 监控 安全
|
Java 索引
Java“ArrayIndexOutOfBoundsException”解决
Java中的“ArrayIndexOutOfBoundsException”异常通常发生在尝试访问数组的无效索引时。解决方法包括:检查数组边界,确保索引值在有效范围内;使用循环时注意终止条件;对用户输入进行验证。通过这些措施可以有效避免该异常。
2625 2
|
人工智能 自然语言处理 安全
claude国内怎么用?教你两种claude国内使用方法!
Claude AI 是由 Anthropic 公司开发的一款新一代 AI 助手,旨在成为更安全、更友好、更可靠的 AI 系统。它基于 Anthropic 对 AI 安全性的深入研究,并采用 “Constitutional AI” (宪法式 AI) 的训练方法,使其行为更符合人类价值观,并减少有害输出的可能性。 🛡️
|
Kubernetes API Python
|
存储 Prometheus Cloud Native
[prometheus]基于influxdb2实现远端存储
[prometheus]基于influxdb2实现远端存储
568 2
|
安全 调度 Swift
【Swift开发专栏】Swift中的多线程与并发编程
【4月更文挑战第30天】本文探讨Swift中的多线程与并发编程,分为三个部分:基本概念、并发编程模型和最佳实践。介绍了线程、进程、并发与并行、同步与异步的区别。Swift的并发模型包括GCD、OperationQueue及新引入的结构体Task和Actor。编写高效并发代码需注意任务粒度、避免死锁、使用线程安全集合等。Swift 5.5的并发模型简化了异步编程。理解并掌握这些知识能帮助开发者编写高效、安全的并发代码。
490 1
|
存储 负载均衡 监控
Redis分区指南:如何实现高可用与扩展性
本文由技术小伙伴小米讲解Redis分区容错中的数据分区。内容涉及Hash、一致性Hash、Codis的Hash槽和RedisCluster四种方法。Hash简单但不稳定,数据迁移和分区不均衡是其主要问题;一致性Hash通过最小化数据迁移实现负载均衡,但仍有局限;Codis的Hash槽提供灵活的负载均衡和在线迁移;RedisCluster是官方高可用、可扩展的解决方案。每种方案有优缺点,需根据实际需求选择。
763 0
Redis分区指南:如何实现高可用与扩展性