Reputation:
I am writing an interpreter for a programming language and I am doing it in a C++, a language, I am frankly quite new to.
What I am trying to accomplish is to convert a specific float format in std::string
to a double (or whatever). I want it to be completely independent of the locale and as robust as possible.
I have two cases:
.4
or 4.
), but not bothI would like it to be the "C++ way" to do it. Is there a function I could use to specify custom number formats (kind of like date
in PHP).
I will be very grateful for any pointer or code-snippet provided. Thank you!
Upvotes: 2
Views: 491
Reputation: 393039
I don't know about iostreams support for strict input formatting.
However, you can use Boost Spirit:
See http://www.boost.org/doc/libs/1_47_0/libs/spirit/doc/html/spirit/qi/reference/numeric/real.html
This will allow you to explicitely define the format(s) accepted for exponents, signs and any (thousands) separators. This is also a quite complex approach, but it is very fast and very flexible (works for non-standard numeric types too, IIRC).
You can use either Spirit Qi rules to specify the exact format, and pass the raw[]
input sequence of to the standard numeric parsers only if it matches your requirements.
The more involved, but also more optimal way, would be to use a Spirit Lexer to tokenize the input - effectively doing the same but more efficiently.
The middle ground here would be to use a plain-old (Posix|Perl|C++11|Boost) regular expression to validate the input format and pass it off to any suitable conversion (like Boost Lexical cast, or just std::stringstream >> double etc.)
A sample showing both Spirit Qi and regex pre-matching at work while parsing the number format for floats (The language is c++0x1):
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/phoenix.hpp>
#include <boost/regex.hpp>
namespace qi=boost::spirit::qi;
namespace phx=boost::phoenix;
bool is_ok(const std::vector<char>& raw)
{
static const boost::regex rx(R"(-?(\d*\.\d+|\d+\.\d*))");
return boost::regex_match(raw.begin(), raw.end(), rx);
}
template <typename Input>
void test(const Input& input)
{
auto f(std::begin(input)), l(std::end(input));
double parsed = 0;
bool ok = qi::phrase_parse(f, l,
// this is the parser expression
&(+qi::char_)[ qi::_pass = phx::bind(is_ok, qi::_1) ]
>> qi::double_,
// end parser expression
qi::space, parsed);
std::cout << "DEBUG: '" << input << "'\t" << std::boolalpha << ok << "\t" << parsed << std::endl;
}
int main()
{
const std::string good[] = { ".300", "300.", "-.4", "-4." };
const std::string wrong[] = { "", ".", "1", "-1", "-1111", "3..", "+1", "+.1", "+1.", "+1.0", "+-2.", "-+2." };
for (auto& input : good)
test(input);
for (auto& input : wrong)
test(input);
}
1 using c++11 featurs:
Upvotes: 2
Reputation: 66922
Assuming you mean strings are to be in the C
locale:
template<class T>
std::string tostring(const T& input)
{
stringstream ss;
if (!(ss << input))
throw std::runtime_error("cannot convert!");
return ss.str();
}
template<class T>
void fromstring(const std::string& input, T& output)
{
stringstream ss(input);
if (!(ss >> output) || ss)
throw std::runtime_error("cannot convert!");
}
//Passes output as parameter, in case it's not copiable.
int main() {
float pi = 3.14159f; //pi to string and back
std::string strpi = tostring(pi);
fromstring(strpi, pi);
std::ifstream in("in.txt"); //copies a file through a string
std::string file = tostring(in);
std::ofstream out("out.txt");
fromstring(file, out);
return 0;
}
Upvotes: 0
Reputation: 124642
integers: they should be contiguous digits from 0-9 with or without a leading minus sign (no plus sign allowed, leading zeros allowed)
floating-point numbers: [whole part].[decimal part] with or without a leading minus and without any thousands separators. Either whole part or decimal part can be ommitted (for example .4 or 4.), but not both
These are hardly "custom formats"; they can be parsed perfectly well by a stringstream
(or, if you are using BOOST, a lexical_cast
).
#include <iostream>
#include <string>
#include <sstream>
int main( ... ) {
std::string s = "-1.0";
float f = 0;
if( std::stringstream(s) >> f ) {
std::cout << f;
}
else {
std::cout << "No good!";
}
return 0;
}
Upvotes: 0