Click here to Skip to main content
16,019,614 members
Articles / Programming Languages / C++

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

Rate me:
Please Sign up or sign in to vote.
4.70/5 (31 votes)
27 Nov 2015CPOL4 min read 57K   863   49   26
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)


Written By
Architect Robotz Software
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 4 Pin
Jim Knopf jr.3-Dec-15 23:40
Jim Knopf jr.3-Dec-15 23:40 
QuestionHow do you done it to make it thread safe? Pin
TSchind1-Dec-15 0:51
TSchind1-Dec-15 0:51 
AnswerRe: How do you done it to make it thread safe? Pin
steveb1-Dec-15 5:08
mvesteveb1-Dec-15 5:08 
GeneralRe: How do you done it to make it thread safe? Pin
TSchind1-Dec-15 21:22
TSchind1-Dec-15 21:22 
GeneralMy vote of 5 Pin
Farhad Reza8-Oct-15 2:46
Farhad Reza8-Oct-15 2:46 
Nice.
QuestionUse of country specific decimal mark Pin
Ralf Wirtz28-Oct-14 22:04
Ralf Wirtz28-Oct-14 22:04 
AnswerRe: Use of country specific decimal mark Pin
steveb29-Oct-14 3:24
mvesteveb29-Oct-14 3:24 
QuestionSome results puzzle me Pin
David Serrano Martínez28-Oct-14 3:13
David Serrano Martínez28-Oct-14 3:13 
AnswerRe: Some results puzzle me Pin
steveb29-Oct-14 3:17
mvesteveb29-Oct-14 3:17 
GeneralRe: Some results puzzle me Pin
David Serrano Martínez29-Oct-14 3:27
David Serrano Martínez29-Oct-14 3:27 
GeneralRe: Some results puzzle me Pin
steveb29-Oct-14 3:32
mvesteveb29-Oct-14 3:32 
GeneralRe: Some results puzzle me Pin
David Serrano Martínez29-Oct-14 3:42
David Serrano Martínez29-Oct-14 3:42 
GeneralMy vote of 5 Pin
Andres Cassagnes27-Oct-14 8:19
Andres Cassagnes27-Oct-14 8:19 
QuestionWhat did not like boost::lexical_cast? Pin
MOPTAHC27-Oct-14 5:53
MOPTAHC27-Oct-14 5:53 
AnswerRe: What did not like boost::lexical_cast? Pin
steveb27-Oct-14 9:01
mvesteveb27-Oct-14 9:01 
GeneralRe: What did not like boost::lexical_cast? Pin
Mass Nerder1-Dec-15 1:57
Mass Nerder1-Dec-15 1:57 
GeneralGood Article Thanks Pin
ToothRobber27-Oct-14 5:26
professionalToothRobber27-Oct-14 5:26 
QuestionGood article! Pin
Jose David Pujo27-Oct-14 1:07
Jose David Pujo27-Oct-14 1:07 
AnswerRe: Good article! Pin
steveb27-Oct-14 1:12
mvesteveb27-Oct-14 1:12 
GeneralRe: Good article! Pin
Jose David Pujo28-Oct-14 22:00
Jose David Pujo28-Oct-14 22:00 
GeneralRe: Good article! Pin
steveb29-Oct-14 3:02
mvesteveb29-Oct-14 3:02 
GeneralRe: Good article! Pin
Jose David Pujo29-Oct-14 6:49
Jose David Pujo29-Oct-14 6:49 
GeneralMy vote of 5 Pin
Franc Morales24-Oct-14 23:16
Franc Morales24-Oct-14 23:16 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.