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