天天看点

浮点性(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:得到最终的转换后的字符串

具体代码实现如下

  //==============================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;  

  }

以上实现方式,并不完美。性能并无大碍,但是会存在安全性问题,当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;

}

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);

const int __ret = std::sprintf(__out, __fmt, __prec, __v);

__gnu_cxx::__uselocale(__old);

std::setlocale(lc_all, __sav);

delete [] __sav;

return __ret;

在源码面前没有什么神秘的,我们一眼就看出来也是使用的sprintf函数实现的转换,上面层层验证,

只是为了保证转换的类型安全和安全性,这里就不一一解释了,说实在的我看stl源码也一样头晕.

我记得从前,很多地方说过尽量少用sprintf,不安全.并且影响性能.经过上面我们剥茧般的检查

发现无论是windows还是linux下最终还是调用的sprintf或者snprintf,我觉这是最好说明sprintf

不会产生性能问题的例子.

现在我们刚开始进入了解c++如何实现浮点性到字符传类型的转换.

继续阅读