Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / C++

C++ Numeric to String and String to Numeric Conversion Routines

4.70/5 (31 votes)
27 Nov 2015CPOL4 min read 57.1K   863  
C++ numeric to string and string to numeric conversion functions

 

Introduction

Often, the numbers must be formatted in a presentable way for the user, thousand separated or fraction values. And vice versa, strings representing thousand separated values or fractions must be converted to numeric values.

Following conversions have been provided:

  • Numbers to thousands separated strings
  • Numbers to fraction strings
  • Numbers to strings with specific precision
  • Strings containing fractions to numbers
  • Strings containing thousands separated numbers to numbers

Background

C library provides conversion library function as atof, atol, and ltoa ftoa. Writing code like this:

C++
double d = atof("90,000.9876");

Would make d equal to 90. It truncates anything not a digit after first non digit encountered character.

An acceptable behavior for the core function encountering n-illion number of calls.

In my past and current projects, the need for such conversions was abundant. I am working in the futures trading industry where majority of the products quoted in fractional. So I decided to wrap it all in a C++ routines.

Using the Code

In order to use conversion routines, you must include:

C++
#include "conversion.hpp"

All routines reside in a namespace convert.

Main Interface

C++
template <typename T> inline T numeric_cast(const char* val);

template <typename T> inline T numeric_cast(const wchar_t* val);

template<typename T>
inline std::string string_cast(const T val, std::streamsize prec=5, EFmt format=none, int denom=0);

template<typename T>
inline std::wstring wstring_cast(const T val, std::streamsize prec=5, EFmt format=none, int denom=0);

How Does It Work

Converting string to number

The interface mentioned above provides a separation layer for the client application from the implementation details. Let’s look at the implementation details of the function.

C++
template <typename T> 
inline T numeric_cast(const char* val)
{
  return detail::numeric_cast<T>(val);
}

As you will see, this function is forwarding the call to the identical function within detail namespace. This convention borrowed from the implementation of boost library. The detail namespace is usually housing the implementation details, hence the name. The code for the numeric_cast function follows:

C++
template <typename T> 
      inline T numeric_cast(const char* val)
      {
         T r = T();
         if(strlen(val) == 0)
            return r; // Do not bother

         std::stringstream ss;
         ss << detail::prepare(val);
         ss >> r;
         return r;
      }

First thing happening inside this function is default assignment of type T. Usually, the compiler sets this value to 0. For example, writing:

C++
T i = T();

Same as:

C++
int i = int();

and is equivalent in most compiler implementations to:

C++
int i = 0; 

After that, the length of the string is checked and if the string happens to be 0 length, the default value returned to the calling function. In this case, it is 0.

If length is greater than 0, the std::stringstream class is instantiated and a call to detail::prepare function is made. And this function is the core implementation of the converting const char* into std::string which later shifted into std::stringstream and afterwards shifted from the std::stringstream into the type T.

Following is the core code for the prepare function:

C++
template <typename T>
inline std::string prepare(const char* val)
{
   check_valid(val);
   std::string s;
   std::string sVal = val;
   size_t pos = sVal.find('/');
   std::locale loc (std::locale::empty(), std::locale::classic(), std::locale::numeric);
   if(pos == std::string::npos)
   {
      for(size_t i = 0; i < strlen(val); i++)
      {
         char c = val[i];
         if(std::isdigit(c, loc) || c == '.' || c == '-')
            s += c;
      }
   }
   else
   {
      std::stringstream ss;
      ss.setf(std::ios::fixed, std::ios::floatfield);
      std::string sWhole, sNom, sDenom;
      size_t nWhole = sVal.find(' ');
      T result = T();

      if(nWhole == std::string::npos)
      {
         sNom = sVal.substr(0, pos);
         sDenom = sVal.substr(pos+1, sVal.length()-1);
         T nom = (T)atof(sNom.c_str());
         T denom = (T)atof(sDenom.c_str());
         if(denom != 0)
            result = nom / denom;
      }
      else
      {
         sWhole = sVal.substr(0, nWhole);
         sNom = sVal.substr(nWhole, pos-nWhole);
         sDenom = sVal.substr(pos+1, sVal.length()-1);
         T whole = atof(sWhole.c_str());
         T nom = atof(sNom.c_str());
         T denom = atof(sDenom.c_str());
         bool bNegative = false;
         if(whole < 0)
         {
            whole = fabs(whole);
            bNegative = true;
         }
         if(denom != 0)
            result = nom / denom + whole;

         if(bNegative)
            result *= -1.0;
      }
      ss << result;
      s = ss.str();
   }
   return s;
}

First, a call to the check_valid function is made.

C++
inline void check_valid(const char* val)
{
   std::locale loc (std::locale::empty(), std::locale::classic(), std::locale::numeric);
   for(size_t i = 0; i < strlen(val); i++)
   {
      char c = val[i];
      if(std::isalpha(c, loc))
         throw std::invalid_argument("alpha character found in numeric_cast");
   }
}

This function iterates through every char in the string and verifies that this is not an alpha character. If the alpha character encountered the std::invalid_argument exception is thrown.

If no alpha characters were found in passed string, the position of forward slash ‘/’ is searched. This is for the case of the strings containing fractional numbers like “4 2/4”. If no forward slash was found, the characters appended to string strip everything but the digits, negation ‘-’ and a ‘.’. For the case that it is not a fraction, the appended string result is returned.

If forward slash ‘/’ were found, the routine becomes more interesting. Usual fractional number consists of the three parts:

  • Whole part
  • Numerator
  • Denominator

This can be mathematically represented as:

C++
"4 2/4" = 4 + 2/4 = 4 + 0.5 = 4.5

First, a space character is searched. If space was found, then the string is partitioned into whole, numerator and denominator parts. If space is missing, the string is partitioned into numerator and denominator only. Each part is then converted into typename T number. A determination of negativity is performed and if the number is negative, the negation flag is saved. The math of division and addition is performed and if the negative flag is true, the final typename T precision result is multiplied by -1.0. The remaining number is shifted into the std::stringstream and returned as a std::string.

Finally, the returned std::string is shifted into std::stringstream and then shifted from std::stringstream into typename T.

If you have seen how const char* version works, you have seen how const wchar_t* version works.

Converting number to a string

C++
template<typename T>
inline std::string string_cast(const T val, std::streamsize prec=5, EFmt format=none, int denom=0)
{
   return detail::string_cast<T>(val, prec, format, denom);
}
enum EFmt
{
   none,
   thou_sep,
   fraction,
};
  • The first parameter is a number
  • Second is decimal precision
  • Enumeration format
  • Denominator (for fractional numbers only)

The string_cast function call calls into detail::string_cast.

C++
template <typename T>
inline std::string string_cast(const T& val, std::streamsize prec, EFmt format, int denom)
{
   std::string rVal;
   std::stringstream ss;
   ss.setf(std::ios::fixed, std::ios::floatfield);
   ss.precision(prec);
   switch(format)
   {
   case thou_sep:
      ss << val;
      rVal = ss.str();
      to_thou_sep(rVal);
      break;
   case fraction:
      to_fract(val, rVal, denom);
      break;
   default:
      ss << val;
      rVal = ss.str();
      break;
   }

   return rVal;
}

This function allocates std::stringstream and sets the requested stream precision to a requested level. The format enumeration is interrogated with switch statement.

First of all, the function return type is a const char*, a pointer. Therefore the internal return value is declared static, in other words static variable inside the function is a global variable visible only from within this function. Because returning type is a pointer, returning any local variable by the pointer would expire it at the end of a function scope.

This exhausts the inspection of the internal conversion::detail guts.

If you see anything missing, please let me know.

Please refer to the conversions.hpp file for remaining queries.

How to Use

String to double conversion

C++
using namespace convert;
std::string s = "1000000.25";
std::wstring w = L"123456789";
double dS = numeric_cast<double>(s.c_str());
double dW = numeric_cast<double>(w.c_str());

Double to thousands separated string

C++
// Precision 3
std::string sThou = string_cast<double>(1000000, 3, convert::thou_sep);
// Precision 0
std::wstring wThou = wstring_cast<double>(123456, 0, connvert::thou_sep);

Double to fraction string

C++
std::string s4th = string_cast<double>(90.75, 0, convert::fraction, 4);
std::string s8th = string_cast<double>(90.75, 0, convert::fraction, 8);
std::string s32nd = string_cast<double>(90.75, 0, convert::fraction, 32);
//etc

Thousands separated number string to number

C++
double dFromThouSep = numeric_cast<double>("1,000,000");

Fractions string to number

C++
double dFromFract = numeric_cast<double>("15 4/8");

Error handling for non integer numerator

C++
try
   {
      double dFractionTest = 90.75;
      sFract = string_cast<double>(dFractionTest, 0, convert::fraction, 7);
      cout << "double to string fraction 7th" << endl;
    cout << sFract << endl;
   }
   catch (std::runtime_error& e)
   {
      cout << "Failed to convert 90.75 to 7th. Error: " << e.what() << endl;
   }

Trying to convert 90.75 to the 7th fractional denominator will fail because the resulting numerator for 0.75 is 5.25 - not an integer. Throws std::runtime_error.

 

History

  • 10/23/2014: Initial article
  • 11/27/2015: Updated code to be thread safe
  • 11/27/2015: Exception is thrown when converted to fraction numerator is not an integer

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)