閱讀326 返回首頁    go 技術社區[雲棲]


浮點性(float)轉化為字符串類型 自定義實現和深入探討C++內部實現方法

  寫這個函數目的不是為了和C/C++庫中的函數在性能和安全性上一比高低,隻是為了給那些喜歡探討函數
內部實現的網友,提供一種從浮點性到字符串轉換的一種途徑。
  浮點數是有精度限製的,所以即使我們在使用C/C++中的sprintf或者cout << f時,默認都會有6位的精度
限製,當然這個精度限製是可以修改的。比方在C++中,我們可以cout.precision(10),不過這樣設置的整個
輸出字符長度為10,而不是特定的小數點後10位,這裏我們需要一個指定小數後幾位的參數。
 1:判斷傳遞浮點數(比方f)的正負關係,負數取正,並記錄標誌。
 2:浮點數*pow(10,6)得到轉化後的數,對這個數取整比方是i。
 3:對i進行數字到字符的轉換,一般我們用輾轉除10得到
 4:對得到的字符串進行調整,添加小數".",負號"="等操作
 5:得到最終的轉換後的字符串
具體代碼實現如下

// 字符串的指定位置插入字符
  void   URInserstr(char   *str,   int   iiNum,char   ch)  
  {  
  //   查找字符結束符號  
  while((*str++)   !=   '/0');  
  *str   =   '/0';  
   
  while(   iiNum   >=0     )  
  {  
  *str   =   *(str-1);  
  --str;  
  --iiNum;  
  }  
  *str   =   ch;  
  }
  //==============================End   Uftoa=========================================  
   
  */    
  void   URInserstr(char   *str,   int   iiNum,char   ch)  
  {  
  //   查找字符結束符號  
  while((*str++)   !=   '/0');  
  *str   =   '/0';  
   
  while(   iiNum   >=0     )  
  {  
  *str   =   *(str-1);  
  --str;  
  --iiNum;  
  }  
  *str   =   ch;  
  }
//   模擬函數ftoa  
  //==============================Start   Uftoa=========================================  
  /*  
  //   函數名:Uftoa  
  //   輸入參數:待轉換數字,存放字符串,字符串大小,浮點小數後邊位數  
  //   輸出參數:存放字符串頭指針  
  //   描述:將浮點性數據轉換為字符串  
  //   輸入值       返回值  
  //   分析:先將float轉換為整型。我們非常容易把整型轉換為char,之後我們在char數組中  
                    添加小數'.'即可。double類型也是使用相同方法  
  */    
  char*   Uftoa(float   fNum,   char   str[],int   size_t,int   size_t2=6)  
  {  
  //   定義變量  
  int   iSize   =   0;  
  char   *p   =   str;  
  bool   bState(false);  
  //   當前傳遞數字為負數  
  if(fNum   <   0)  
  {  
  bState   =   true;  
  str[iSize]   =   '-';  
  fNum   =   abs(fNum);  
  ++iSize;  
  }  
   
  int   iNum   =   static_cast(fNum*pow(10,size_t2));  
   
  //   字符溢出,和輾轉相除不為0  
  while(iSize   <   size_t   &&   iNum   !=   0   )  
  {  
  int   iTemp   =   iNum%10;  
  str[iSize++]   =   iTemp+'0';  
  iNum   =   iNum/10;  
  }  
  str[iSize]   =   '/0';  
  //   如果為負數,得到的字符串需要逆轉  
  if(bState)  
  {  
  p++;  
  Urevstr(p);  
  --p;  
  }  
  else  
  {  
  Urevstr(p);  
  }  
   
  //   加上小數字點  
  URInserstr(p,size_t2,'.');   //   這個函數就是在指定位置  
   
  //   返回指針    
  return   p;  
  }  
   
  //==============================End   Uftoa=========================================  
   
  */    

以上實現方式,並不完美。性能並無大礙,但是會存在安全性問題,當float數值比較大時,這個數字再*pow(10,6)
會產生溢出,如果float數值比較大,可以對整數部分和小數部分分別轉化,然後再合成。

為了和C++庫中的浮點型到字符串型轉化進行性能對比,分別察看了一下windows和linux平台下,C++庫最這兩種轉換
的實現方法
1:windows平台(windows2003+vs2003),通過源代碼跟隨cout<<f,通過層層代碼(把float轉為double類型),
最後在do_put函數中進行實際轉換,do_put代碼如下
經過層層驗證,我們發現最後調用的sprintf完成從float類型到string類型轉換,很出乎意外吧!


// 這個函數在xlocnum內實現946行處
_VIRTUAL _OutIt do_put(_OutIt _Dest,
ios_base& _Iosbase, _Elem _Fill, double _Val) const
{ // put formatted double to _Dest
char _Buf[_MAX_EXP_DIG + _MAX_SIG_DIG + 64], _Fmt[8];
streamsize _Precision = _Iosbase.precision() <= 0
&& !(_Iosbase.flags() & ios_base::fixed)
? 6 : _Iosbase.precision(); // desired precision // 精度設置
int _Significance = _MAX_SIG_DIG < _Precision
? _MAX_SIG_DIG : (int)_Precision; // actual sprintf precision
_Precision -= _Significance;
size_t _Beforepoint = 0; // zeros to add before decimal point
size_t _Afterpoint = 0; // zeros to add after decimal point

if ((_Iosbase.flags() & ios_base::floatfield) == ios_base::fixed)
{ // scale silly fixed-point value
bool _Signed = _Val < 0;
if (_Signed)
_Val = -_Val;

for (; 1e35 <= _Val && _Beforepoint < 5000; _Beforepoint += 10)
_Val /= 1e10; // drop 10 zeros before decimal point // 小數點前的數/10

if (0 < _Val)
for (; 10 <= _Precision && _Val <= 1e-35
&& _Afterpoint < 5000; _Afterpoint += 10)
{ // drop 10 zeros after decimal point
_Val *= 1e10;
_Precision -= 10;
}

if (_Signed)
_Val = -_Val;
}

return (_Fput(_Dest, _Iosbase, _Fill, _Buf,
_Beforepoint, _Afterpoint, _Precision,
::sprintf(_Buf, _Ffmt(_Fmt, 0, _Iosbase.flags()),
_Significance, _Val))); // convert and put
}

再看linux環境下(rhel4+g++3.4),g++一般本認為是高效執行c++代碼,不知道在轉化過程中是否有更高效的辦法
我們也是從cout<<f開始,和windows平台一樣,首先也會把float類型轉為double類型,查找文件順序如下
iostring->ostring->ostream.tcc->localefwd.h->locale_facets.h->locale->facets.tcc->
i386-redhat-linux/bits/c++locale.h
Ok,經過層層剝繭式的搜索我們終於找到最關鍵的實現函數,代碼如下.

// Convert numeric value of type _Tv to string and return length of
// string. If snprintf is available use it, otherwise fall back to
// the unsafe sprintf which, in general, can be dangerous and should
// be avoided.
template
int
__convert_from_v(char* __out, const int __size, const char* __fmt,
#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 2)
_Tv __v, const __c_locale& __cloc, int __prec)
{
__c_locale __old = __gnu_cxx::__uselocale(__cloc);
#else
_Tv __v, const __c_locale&, int __prec)
{
char* __old = std::setlocale(LC_ALL, NULL);
char* __sav = new char[std::strlen(__old) + 1];
std::strcpy(__sav, __old);
std::setlocale(LC_ALL, "C");
#endif

#ifdef _GLIBCXX_USE_C99
const int __ret = std::snprintf(__out, __size, __fmt, __prec, __v);
#else
const int __ret = std::sprintf(__out, __fmt, __prec, __v);
#endif

#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ > 2)
__gnu_cxx::__uselocale(__old);
#else
std::setlocale(LC_ALL, __sav);
delete [] __sav;
#endif
return __ret;
}
}

在源碼麵前沒有什麼神秘的,我們一眼就看出來也是使用的sprintf函數實現的轉換,上麵層層驗證,
隻是為了保證轉換的類型安全和安全性,這裏就不一一解釋了,說實在的我看STL源碼也一樣頭暈.

我記得從前,很多地方說過盡量少用sprintf,不安全.並且影響性能.經過上麵我們剝繭般的檢查
發現無論是windows還是linux下最終還是調用的sprintf或者snprintf,我覺這是最好說明sprintf
不會產生性能問題的例子.

現在我們剛開始進入了解C++如何實現浮點性到字符傳類型的轉換.


最後更新:2017-04-02 00:06:30

  上一篇:go 下載配置lighttpd
  下一篇:go UNIX/LINUX 平台可執行文件格式分析