浮点性(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++如何实现浮点性到字符传类型的转换.


目录
相关文章
|
3天前
|
存储 Java C++
C++ 引用和指针:内存地址、创建方法及应用解析
C++中的引用是现有变量的别名,创建时需用`&`运算符,如`string &meal = food;`。指针存储变量的内存地址,使用`*`创建,如`string* ptr = &food;`。引用必须初始化且不可为空,而指针可初始化为空。引用在函数参数传递和提高效率时有用,指针适用于动态内存分配和复杂数据结构操作。选择使用取决于具体需求。
41 9
|
3天前
QT里面字符串转ieee754标准的float浮点数
QT里面字符串转ieee754标准的float浮点数
22 0
|
3天前
|
存储 C++ 索引
C++ 字符串完全指南:学习基础知识到掌握高级应用技巧
C++的字符串使用`string`类处理,如`string greeting = &quot;Hello&quot;`。字符串连接可通过`+`或`append()`函数实现。访问字符使用索引,如`myString[0]`。`length()`或`size()`可获取长度。`getline()`用于读取整行输入。注意转义字符如`\\&quot;`用于在字符串中嵌入双引号。使用`cin`读取字符串时,空格会终止输入,而`getline()`能读取整行。
27 0
|
2天前
|
C语言 C++
C++|运算符重载(2)|运算符重载的方法与规则
C++|运算符重载(2)|运算符重载的方法与规则
|
3天前
|
编解码 JavaScript 前端开发
【专栏】介绍了字符串Base64编解码的基本原理和在Java、Python、C++、JavaScript及Go等编程语言中的实现示例
【4月更文挑战第29天】本文介绍了字符串Base64编解码的基本原理和在Java、Python、C++、JavaScript及Go等编程语言中的实现示例。Base64编码将24位二进制数据转换为32位可打印字符,用“=”作填充。文中展示了各语言的编码解码代码,帮助开发者理解并应用于实际项目。
|
3天前
|
存储 编译器 C语言
C++字符串大小写之for语句
C++字符串大小写之for语句
18 0
|
3天前
|
C++
【C++】std::string 转换成非const类型 char* 的三种方法记录
【C++】std::string 转换成非const类型 char* 的三种方法记录
8 0
|
3天前
|
C++
【代码片段】【C++】获取当前时间戳并生成固定格式字符串
【代码片段】【C++】获取当前时间戳并生成固定格式字符串
15 0
|
3天前
|
数据安全/隐私保护 C++
C++ 类方法解析:内外定义、参数、访问控制与静态方法详解
C++ 中的类方法(成员函数)分为类内定义和类外定义,用于操作类数据。类内定义直接在类中声明和定义,而类外定义则先在类中声明,再外部定义。方法可以有参数,访问权限可通过 public、private 和 protected 控制。静态方法与类关联,不依赖对象实例,直接用类名调用。了解这些概念有助于面向对象编程。
16 0
|
3天前
|
存储 编译器 Linux
标准库中的string类(中)+仅仅反转字母+字符串中的第一个唯一字符+字符串相加——“C++”“Leetcode每日一题”
标准库中的string类(中)+仅仅反转字母+字符串中的第一个唯一字符+字符串相加——“C++”“Leetcode每日一题”