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

练习1.3 如题。

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

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

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

版本一:

// 正整数版(更大的范围可以使用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);
}

版本二:

// 强化版:所有整数
#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。其实这个值也是四舍五入得来的,那么如何看到它在内存中的全貌呢,请看版本三:

版本三:

// 强化版二: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->)。

版本四:终极进化版

//下面是我根据作者提供的核心代码补全后的版本,考虑了舍入误差(四舍五入)
#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

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

  1. C语言如何判断单个数字是否溢出:

    如何判断一个输入或者输出转化的单个数字是否溢出: if( num>0x7fffffff || num<(signed int)0x80000000) 注意: int类型的最大正数:0x7f ...

  2. 正则表达式:匹配单个数字重复n次

    匹配单个数字重复n次:(\d)\1{n-1}其中,\d表示一位数字,(\d)表示匹配之后捕获该匹配,并分组并对组进行编号\1表示被捕获的第一个分组{n-1}是因为被捕获的第一个分组已经消耗了一位数字, ...

  3. 华为 2015 机试 输出:数字后面的连续出现的(2个或多个)相同字符(数字或者字符),删去一个,非数字后面的不要删除,例如,对应输出为:33aabb55pin。

    package 华为机试; //C++ 输入:由数字和字母组成的字符串,例如:333aaabb55ppin //输出:数字后面的连续出现的(2个或多个)相同字符(数字或者字符),删去一个,非数字后面的 ...

  4. JS实现input中输入数字,控制每四位加一个空格(银行卡号格式)

    前言 今天来讲讲js中实现input中输入数字,控制每四位加一个空格的方法!这个主要是应用于我们在填写表单的时候,填写银行卡信息,要求我们输入的数字是四位一个空格!今天主要介绍两种方式来实现这个方法! ...

  5. js input框输入1位数字后自动跳到下一个input框聚焦

    // input框输入1位数字后自动跳到下一个input聚焦 function goNextInput(el){ var txts = document.querySelectorAll(el); f ...

  6. 编写一个算法,将非负的十进制整数转换为其他进制的数输出,10及其以上的数字从‘A’开始的字母表示

    编写一个算法,将非负的十进制整数转换为其他进制的数输出,10及其以上的数字从‘A’开始的字母表示. 要求: 1) 采用顺序栈实现算法: 2)从键盘输入一个十进制的数,输出相应的八进制数和十六进制数. ...

  7. 【C语言】求旋转数组的最小数字,输入一个递增排序的数组的一个旋转,输出其最小元素

    //求旋转数组的最小数字,输入一个递增排序的数组的一个旋转,输出其最小元素 #include <stdio.h> #include <string.h> int find_mi ...

  8. python新手如何编写一个猜数字小游戏

    此文章只针对新手,希望大家勿喷,感谢!话不多说先上代码: import random if __name__ == '__main__': yourname = input("你好! 你的名 ...

  9. 01-JAVA语言基础——课程作业1—编写一个程序,此程序从命令行接收多个数字,求和之后输出结果。

    1.题目:编写一个程序,此程序从命令行接收多个数字,求和之后输出结果. 2.程序设计思想: 通过运行配置输入数字后,其保存类型为String类型,因此需要采用Integer.valueOf(arg)将 ...

随机推荐

  1. Oracle Merge Into 用法详解

    原文:http://blog.csdn.net/EdgenHuang/article/details/3587912 Oracle9i引入了MERGE命令,你能够在一个SQL语句中对一个表同时执行in ...

  2. phpspec安装配置

    安装  composer require phpspec/phpspec -dev 运行  bin/phpspec 在laravel中  vendor/bin/phpspec 配置phpspec.ym ...

  3. 【转】设计模式 ( 十五 ) 中介者模式Mediator(对象行为型)

    设计模式 ( 十五 ) 中介者模式Mediator(对象行为型) 1.概述 在面向对象的软件设计与开发过程中,根据"单一职责原则",我们应该尽量将对象细化,使其只负责或呈现单一的职 ...

  4. Freemarker的第二次使用~list的元素是数组

    在上次初次使用Freemarker作为模版后,我再次使用它.这次的需求是: xml文档的某个节点的属性A和其一个子节点的某个属性B有关联,属性B的值需要随着属性A的值变化.而属性A的值有4个值,现在需 ...

  5. Bootstrap 表单和图片 (内联表单,表单合组,水平排列,复选框和单选框,下拉列表,校验状态,添加额外的图标,控制尺寸,图片)

    一.表单 基本格式 注:只有正确设置了输入框的 type 类型,才能被赋予正确的样式. 支持的输入框控件 包括:text.password.datetime.datetime-local.date.m ...

  6. Bulk Insert & BCP执行效率对比

    我们以BCP导出的CSV数据文件,分别使用Bulk insert与BCP导入数据库,对比两种方法执行效率 备注:导入目标表创建了分区聚集索引 1.BCP导出csv数据文件 数据量:15000000行, ...

  7. 原因是未找到“sgen.exe”,或未安装 .NET Framework SDK v2.0

    visual studio编译出现错误:错误 2 任务失败,原因是未找到“sgen.exe”,或未安装 .NET Framework SDK v2.0.该任务正在注册表项 HKEY_LOCAL_MAC ...

  8. Android生命周期详细说明

    提供两个关于Activity的生命周期模型图示帮助理解:                                           图1 图2 从图2所示的Activity生命周期不难看出, ...

  9. [Stanford 2011] Views 知识点

    一.view分层 (1)View的结构是分层的,一个view只能有一个父view,但可以有多个子view.子view的顺序是相关的,在数组中的位置越高或者说数字越大,就显示在后面,位置低的显示在前面. ...

  10. 免费真机调试 -- Xcode7以上版本

    刚新安装了Xcode7 , 据说这个版本可以免费真机调试,于是用了一个新的AppID测试了,发现真的可以免费真机调试了呢!新的appId账号(随便一个苹果手机账号就行),没有支付每年的99美刀,也没有 ...