這是一位同學發到我郵箱的問題,看起來這位同學很急,不但發了郵件,還幾次短消息催我,呵呵,那我回答一下。
嗯,這裡還是先說明一下,我一般不太願意在博文中回答具體程式問題,因為C和C++語言,太靈活了,一個問題,有一萬種解法,另外,每種解法各有其适用面,貿然在博文中回答,必然不是很精确,回答效果不好。
這次呢,我看這位同學比較急迫,我就破個例,不過,正确的做法,我建議大家以後有了具體的程式問題呢,還是到課堂提問,大家來回答。那麼多個腦袋,總比我一個腦袋聰明,想問題也全面,回答效果會比較好哈。
另外,這個程式我是為了回答問題,臨時寫出來的測試代碼,不是工程代碼,大家看看,了解意思就好了,不要貿然代入自己的工程,我不保證它是不是絕對沒有bug的。它甚至不符合我《0bug-C/C++商用工程之道》裡面的無錯化程式思想。僅僅用來說明意思的哈,别被誤導了。
原文如下:
我是浙江大學軟體工程的,csdn上一直浏覽您的首頁。以前我隻重理論,導緻程式設計不行,現在開始狂編,我現在能問您個問題嗎,糾結了好久了……
用C語言模拟計算機浮點數運算
1、float stof(char *); //十進制字元串 --> float (如:"-1.0" --> 0xFF800000)
2、float fadd(float, float);
3、float fsub(float, float);
4、float fmul(float, float);
5、float fdiv(float, float);
6、void ftos(char *, float); //float --> 十進制字元串(如:0x40000000 --> "2.0")
而我就卡在第一和第六個函數上,我搜遍了網上的資料,搞了好幾天沒有搞出來,我搜到Ieee754的算法又不知道怎麼去運用,我大概可以想到把它小數整數分開都做整數處理,但是感覺還是很難以下手……你能寫個大概讓我看一下嗎,我主要就是第一和第六個函數存在問題,好糾結啊……
我的回答:
嗯,這位同學你好,你的第一個和第六個問題,我來試着回答一下。先說啊,這不是工程代碼,是我為了給你回答問題臨時寫出來的測試代碼,你看看好了,學會了思想自己寫,我不保證其正确性的。
Code:
bool IsNumber(char c)
{
if('0'>c) return false;
if('9'<c) return false;
return true;
}
double stof(char* szFloatString)
double fRet=0.0; //試了一下,不能用float,精度太低了,我改用double
double fRetLittleNumber=0.0;
int nStringLength=strlen(szFloatString);
int i=0;
if(0>=nStringLength)
goto stof_End_Process_Error;
char cTarget=0;
i=0; //這是在找整數部分
//整數部分順找,找到一個數字,基數*10+這個數字,就是把數字用10進制拼接到基數中
while(1)
{
cTarget=*(szFloatString+i);
if('.'==cTarget)
goto stof_Get_Little_Number;
if('\0'==cTarget)
goto stof_End_Process;
if(!IsNumber(cTarget))
goto stof_End_Process_Error;
cTarget-='0';
fRet*=10.0; //先*10
fRet+=cTarget; //再+,原來是1,新數字是2,1*10+2=12
i++;
}
stof_Get_Little_Number: //這是在找小數部分
//小數部分,從右向左逆找
//找到新數字,基數+新數字/10,這是把新數字拼接到左邊
i=nStringLength-1;
while(1)
goto stof_I_Get_It;
fRetLittleNumber+=cTarget; //先+新數字
fRetLittleNumber/=10.0; //再/10,0+1/10=0.1,0.1+2/10=0.21,明白沒有?
i--;
stof_I_Get_It: //找到了,拼接數字
fRet+=fRetLittleNumber;
goto stof_End_Process;
stof_End_Process_Error: //這是出錯了
printf("\"%s\" is not float!\n",szFloatString);
fRet=0.0;
stof_End_Process: //這是傳回點
return fRet;
void Test_stof(void)
char* pStr="";
pStr="123";
printf("\"%s\"=%f\n",pStr,stof(pStr));
pStr="123.321";
pStr="1234567890.0987654321";
最後的運作結果:
"123"=123.000000
"123.321"=123.321000
"1234567890.0987654321"=1234567890.098765
看見沒,由于C編譯器本身的精度問題,太長的小數被截短了。double還好點,要是float,第二個答案都是123.3209999,誤差就太大了。
C語言準确地講,不是一門太适合數學計算的語言,它浮點精度始終有限,如果要做純正的數學計算,建議用Fortran等專業的數學計算語言。
當然,C++可以提出一些替代解決方案,比如說,我們自己做個類,裡面用特殊的結構,比如就是超常數組來存儲一個高精度浮點數的很多位,并提供輸入輸出方法,以及常用的數學計算方法,說白了,就是我們自己實作一個浮點數類,那可以把精度做得很高的,不過,這顯然不是一篇博文能講清楚的,有興趣的朋友,可以自己試試。
另外,這個函數沒有處理負數,這個題目我留給大家想吧,看懂了我的代碼,加個負數支援也很容易。
好吧,第一個問題就回答在這裡。
第六個還用回答嗎?
sprintf(szBuffer,"%f",fResult);
直接用字元串輸出函數輸出到一個緩沖區就好了。
不過,為了安全起見,建議使用《0bug-C/C++商用工程之道》一書中的安全字元串構造函數,我這裡給你個原型,這個是工程代碼,我用了近十年都沒出過錯,可以直接用的。
int SafePrintf(char* szBuf,int nMaxLength,char *szFormat, ...)
int nListCount=0;
va_list pArgList;
if (!szBuf) goto SafePrintf_END_PROCESS;
va_start (pArgList,szFormat);
nListCount+=Linux_Win_vsnprintf(szBuf+nListCount,
nMaxLength-nListCount,szFormat,pArgList);
va_end(pArgList);
if(nListCount>(nMaxLength-1)) nListCount=nMaxLength-1;
*(szBuf+nListCount)='\0';
SafePrintf_END_PROCESS:
return nListCount;
簡單說,就是給出字元串緩沖區,還要給出一個緩沖區大小,我的函數保證不會打出去溢出,還有,就是不管緩沖區夠不夠,總之保證你最後得到一個'\0'的結束符。
你先看看吧,有問題再問。