13,833,733 members
alternative version

#### Stats

35.9K views
49 bookmarked
Posted 24 Oct 2014
Licenced CPOL

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

, 27 Nov 2015
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:

`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:

`#include "conversion.hpp"`

All routines reside in a namespace `convert`.

## Main Interface

```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.

```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:

```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:

`T i = T();`

Same as:

`int i = int();`

and is equivalent in most compiler implementations to:

`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:

```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.

```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 `string`s 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:

```"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

```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`.

```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

```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

```// 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

```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

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

#### Fractions string to number

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

## Error handling for non integer numerator

```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

## Share

 Architect Robotz Software United States
No Biography provided

## You may also be interested in...

 First Prev Next
 My vote of 4 Jim Knopf jr.4-Dec-15 0:40 Jim Knopf jr. 4-Dec-15 0:40
 How do you done it to make it thread safe? TSchind1-Dec-15 1:51 TSchind 1-Dec-15 1:51
 Re: How do you done it to make it thread safe? steveb1-Dec-15 6:08 steveb 1-Dec-15 6:08
 Re: How do you done it to make it thread safe? TSchind1-Dec-15 22:22 TSchind 1-Dec-15 22:22
 Use of country specific decimal mark Ralf Wirtz28-Oct-14 23:04 Ralf Wirtz 28-Oct-14 23:04
 Re: Use of country specific decimal mark steveb29-Oct-14 4:24 steveb 29-Oct-14 4:24
 Some results puzzle me David Serrano Martínez28-Oct-14 4:13 David Serrano Martínez 28-Oct-14 4:13
 Re: Some results puzzle me steveb29-Oct-14 4:17 steveb 29-Oct-14 4:17
 Re: Some results puzzle me David Serrano Martínez29-Oct-14 4:27 David Serrano Martínez 29-Oct-14 4:27
 Re: Some results puzzle me steveb29-Oct-14 4:32 steveb 29-Oct-14 4:32
 Re: Some results puzzle me David Serrano Martínez29-Oct-14 4:42 David Serrano Martínez 29-Oct-14 4:42
 My vote of 5 Andres Cassagnes27-Oct-14 9:19 Andres Cassagnes 27-Oct-14 9:19
 What did not like boost::lexical_cast? MOPTAHC27-Oct-14 6:53 MOPTAHC 27-Oct-14 6:53
 Re: What did not like boost::lexical_cast? steveb27-Oct-14 10:01 steveb 27-Oct-14 10:01
 Re: What did not like boost::lexical_cast? Mass Nerder1-Dec-15 2:57 Mass Nerder 1-Dec-15 2:57
 Good Article Thanks ToothRobber27-Oct-14 6:26 ToothRobber 27-Oct-14 6:26
 Good article! Jose David Pujo27-Oct-14 2:07 Jose David Pujo 27-Oct-14 2:07
 Re: Good article! steveb27-Oct-14 2:12 steveb 27-Oct-14 2:12
 Re: Good article! Jose David Pujo28-Oct-14 23:00 Jose David Pujo 28-Oct-14 23:00
 Re: Good article! steveb29-Oct-14 4:02 steveb 29-Oct-14 4:02
 Re: Good article! Jose David Pujo29-Oct-14 7:49 Jose David Pujo 29-Oct-14 7:49
 My vote of 5 Franc Morales25-Oct-14 0:16 Franc Morales 25-Oct-14 0:16
 Last Visit: 21-Jan-19 4:21     Last Update: 21-Jan-19 4:21 Refresh 1

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

Web06 | 2.8.190114.1 | Last Updated 27 Nov 2015