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;
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
std::string sThou = string_cast<double>(1000000, 3, convert::thou_sep);
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);
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
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.