string to number – the C++ way

The recent post atof – root of some evil, showed that one has to be careful using atof. Since it is a function from standard C library, what does C++ really has to offer here ? Well of course it has not an answer of thinking about code usage in a multilanguage enviroment, but gives you good mechanism to write your own conversion.

Basic

 
#include  <sstream> 
float stringToFloat(const char *in)
{
  float val = 0.0f;
  std::istringstream inputStream(in);
  inputStream >> val;
  return(val);
};

The point is that you create a istringstream object from your buffer and use the extraction operator >> to get the float value.

Something failed ..?
You can use the fail member   function of istringstream to check for failure.

 
#include  <sstream> 
float stringToFloat(const char *in)
{
  bool failed = false;
  float val = 0.0f;
  std::istringstream inputStream(in);
  failed = ( inputStream >> val).fail();
  if(true == failed) throw std::exception("extraction failed");
  return(val);
};

Better than atof  ?

The big advantage now is that the istringstream object has its  own locale and nobody can change that like with C library  setlocale. Wow, perfect encapsulation ! Of course the default locale is  “english”  including the point as a decimal separator.

Locale invariant

If you like to make the conversion more “general” you can check for  a comma in input string and change the locale of istringstream like this:

// class for decimal numbers with comma
#include  <sstream> 
class UseCommaAsSeparator: public std::numpunct<char> 
{
  protected: char do_decimal_point() const { return ','; } 
};

float stringToFloat(const char *in)
{
  bool failed = false;
  float val = 0.0f;
  std::istringstream inputStream(in);

  if( inputStream.str().find(",") != std::string::npos) 
  {
    std::locale loc = std::locale(std::locale(),new UseCommaAsSeparator);
    inputStream.imbue(loc);
  }
  failed = ( inputStream >> val).fail();
  if(true == failed) throw std::exception("extraction failed");
  return(val);
};

By overriding the do_decimalpoin() member of std::numpunct<char> and imbueing the default locale. Now the conversion is independend of the decimal separator in the input string.

Template programming ?

Are you looking for an application of C++ template programming. Here comes a good candidate, because you do not only want to convert to float ,but also  to  double,  to int etc. ! So let´s make a nice template function.

// class for decimal numbers with comma
#include  <sstream> 
class UseCommaAsSeparator: public std::numpunct<char> 
{
  protected: char do_decimal_point() const { return ','; } 
};

template <typename T >
T stringToNumber(const char *in)
{
  bool failed = false;
  T val;
  std::istringstream inputStream(in);
  if( inputStream.str().find(",") != std::string::npos) 
  {
    std::locale loc = std::locale(std::locale(),new UseCommaAsSeparator);
    inputStream.imbue(loc);
  }
  failed = ( inputStream >> val).fail();
  if(true == failed) throw std::exception("extraction failed");
  return(val);
};

 

It is really typesafe !

Ok let’s use our template function.

unsigned char  number =0;
number = stringToNumber<unsigned char>("42");
std::cout << number << "\n";

Not surprisingly the output is  not   42

Our expection that the data type unsigned char  represents a number is wrong, it represents a  character and so the  istringstream  >> operator type safe  behaves like this and only extract the first character. In this case  ‘4’.  The ASCII representation  is   decimal  52.  So if we cast number to for example int, we get 52 on the  console output.  Neither what we want.

The usage of  unsigned char for number representation is very common to represent 8Bit unsigned data.  We can implement a specialized template for this data type like this:

template<>
unsigned char stringToNumber<unsigned char>(const char *in)
{
  unsigned char val=0;
  val= static_cast<unsigned char> (stringToNumber<int>(in));
  return (val);
};

 

 

 

 

Leave a Reply