代码改变世界

仅使用处理单个数字的I/O例程,编写一个过程以输出任意实数(可以是负的)

  星星之火✨🔥  阅读(3130)  评论(4编辑  收藏  举报

题目取自:《数据结构与算法分析:C语言描述_原书第二版》——Mark Allen Weiss    

练习1.3 如题。

补充说明:假设仅有的I/O例程只处理单个数字并将其输出到终端,我们将这个例程命名为PrintDigit;例如"PrintDigit(4)"

     将输出一个"4"到终端。

思路:根据先简后繁的原则,程序各版本完成的功能依次为:处理正整数—>处理所有整数—>处理double—>double舍入。

版本一:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// 正整数版(更大的范围可以使用long long int)
#include<stdio.h>
 
void PrintOut(int number);
void PrintDigit(int number);
 
int main(void)
{
    int n = 123;
     
    PrintOut(n);
 
    return 0;
}
 
void PrintOut(int number)
{
    int value = number / 10;
     
    if(value != 0) // 考虑用while会出现什么情况,如果数字不是个位数,那么程序死循环输出首位数字。
        PrintOut(value);
     
    PrintDigit(number % 10);
}
 
void PrintDigit(int number) // 对处理单个数字的I/O例程进行模拟
{
    printf("%d", number);
}

 版本二:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 强化版:所有整数
#include<stdio.h>
 
void PrintOut(int number);
void PrintDigit(int number);
void PreDispose(int number); // 对传入的参数做一些预处理工作,然后调用PrintOut函数
int main(void)
{
    int n = -45689;
     
    PreDispose(n);
 
    return 0;
}
 
void PreDispose(int number)
{
    if(number < 0)
    {
        putchar('-');
        number = -number;
    }
     
    PrintOut(number);
}
 
void PrintOut(int number)
{  
    int value = number / 10;
     
    if(value != 0)
        PrintOut(value);
     
    PrintDigit(number % 10);
}
 
void PrintDigit(int number)
{
    printf("%d", number);
}

讲述版本三之前先来看一下double类型在内存中的存储情况,在code::blocks中定义如下变量:

设置断点,调试,a、b、c初始化之前的值如图一,赋值后的值如图二

        图一                    图二

不难看出,double类型在内存中存储是有误差的。比如我们定义的c = 9.1,内存中实际值为9.099999999...6。其实这个值也是四舍五入得来的,那么如何看到它在内存中的全貌呢,请看版本三:

版本三:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
// 强化版二:double类型
#include<stdio.h>
#include<math.h>
 
void PrintOut(int number);
void PrintDigit(int digit);
void PreDispose(double number);
 
int main(void)
{
    double n = 9.1;
     
    PreDispose(n);
 
    return 0;
}
 
void PreDispose(double number)
{
    double ip;
     
    // 函数modf把传入的第一个参数分为整数和小数两部分,整数部分保存在第二个参数中
    // 两部分的正负号均匀x相同,该函数返回小数部分
    double fraction = modf(number, &ip);
    //  一个更加简便的分离小数位与整数位的方法如下:
    // double ip, fraction;    
    // fraction = number - (int)number;   
    // ip = (int)number;   
 
    if(ip < 0)
    {
        putchar('-');
        ip = -ip;
        fraction = -fraction;  
    }
         
    PrintOut(ip);
    putchar('.');
     
    // 对小数部分逐位输出,理论上可以输出到小数点后任意多的数位,就这几行代码还耗了不少脑细胞呢Orz
    int N = 70; // 希望输出到小数点多少位,就设定N为多少(想不到更好的解释了:-)
    while(N--)
    {
        fraction *= 10;
        PrintOut((int)fraction%10); // 因为传入的参数是单个数字,所以这里也可以直接调用PrintDigit函数
        fraction = fraction - (int)fraction; // 防止fraction数据过大,导致整型溢出
    }
}
         
void PrintOut(int number)
{  
    int value = number / 10;
     
    if(value != 0)
        PrintOut(value);
     
    PrintDigit(number % 10);
}
 
void PrintDigit(int digit)
{
    printf("%d", digit);
}
//输出结果:9.0999999999999996447286321199499070644378662109375000000000000000000000

对b=1.1而言,输出结果为:1.1000000000000000888178419700125232338905334472656250000000000000000000,对比图二不难得出结论:code::blocks中所示的数值就是原double值四舍五入并且精确到小数点后16位得到的。

但是,存在一个问题,比如拿c=9.1来说事,我们令版本三中程序中的变量N的值为1,则输出结果为9.0。所以这个程序的一个问题就是:没有考虑舍入误差。那么如何处理舍入误差呢,还好有Weiss 提供的core->)。

版本四:终极进化版

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
//下面是我根据作者提供的核心代码补全后的版本,考虑了舍入误差(四舍五入)
#include<stdio.h>
 
int IntPart(double N); // 得到N的整数部分
double DecPart(double N); // 得到N的小数部分
void PrintReal(double N, int DecPlaces); // 该函数打印double值,其中第二个参数为精确到小数点后的位数
void PrintFractionPart(double FractionPart, int DecPlaces); // 打印小数部分
double RoundUp( double N, int DecPlaces ); // 实现四舍五入的函数
void PrintOut(int number);
void PrintDigit(int number);
 
int main(void)
{
    double value = -9.1;
     
    PrintReal(value, 1);
     
    return 0;
}
 
double RoundUp( double N, int DecPlaces ) // 窃以为该函数为整个程序的画龙点睛之笔。
{
    int i;
    double AmountToAdd = 0.5;
     
    for( i = 0; i < DecPlaces; i++ )
        AmountToAdd /= 10;
    return N + AmountToAdd;
}
 
 
void PrintReal(double N, int DecPlaces)
{
    int IntegerPart;
    double FractionPart;
     
    if( N < 0 )
    {
        putchar('-');
        N = -N;
    }
     
    N = RoundUp(N, DecPlaces);
    IntegerPart = IntPart( N );
    FractionPart = DecPart( N );
     
    PrintOut(IntegerPart); // 假设错误检查已经完成,即输入是常规的文本
    if(DecPlaces > 0)
        putchar('.');
    PrintFractionPart(FractionPart, DecPlaces);
}
void PrintFractionPart(double FractionPart, int DecPlaces) // 程序三中输出小数部分的实现思路与之类似,不过其提供了对外接口——函数外部可以设定要输出的小数位数
{
    int i, Adigit;
     
    for( i = 0; i < DecPlaces; i++ )
    {
        FractionPart *= 10;
        Adigit = IntPart(FractionPart);
        PrintDigit(Adigit);
         
        FractionPart = DecPart(FractionPart);
    }
}
 
int IntPart(double N)
{
    return (int)N;
}
 
double DecPart(double N)
{
    return N - IntPart(N); 
}
 
void PrintOut(int number)
{  
    int value = number / 10;
     
    if(value != 0)
        PrintOut(value);
     
    PrintDigit(number % 10 );
}
 
void PrintDigit(int digit)
{
    printf("%d", digit);
}
//输出结果:9.1

可以看到,该程序不仅解决了版本三中遗留的小数点舍入问题,而且通过设定PrintReal函数的第二个参数的值为70可以得到和版本三中相同的结果。End。

—————————————————————————^_^我是分隔线^_^—————————————————————————

All Rights Reserved.

Author:海峰:)

Copyright © xp_jiang.

转载请标明出处:http://www.cnblogs.com/xpjiang/p/4135919.html

点击右上角即可分享
微信分享提示