天天看點

僅使用處理單個數字的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中定義如下變量:

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

設定斷點,調試,a、b、c初始化之前的值如圖一,指派後的值如圖二

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

        圖一                    圖二

不難看出,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

轉載于:https://www.cnblogs.com/xpjiang/p/4135919.html