僅使用處理單個數字的I/O例程,編寫一個過程以輸出任意實數(可以是負的)


題目取自:《數據結構與算法分析: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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM