user267885
user267885

Reputation:

Convert a specifically formated number to long/double in C++

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:

I 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

Answers (3)

sehe
sehe

Reputation: 393039

I don't know about iostreams support for strict input formatting.

However, you can use Boost Spirit:

standard real parsers with RealPolicy

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

Two phase parsing

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

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:

  • range-based for
  • raw string literals for the regex specification

Upvotes: 2

Mooing Duck
Mooing Duck

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

Ed Swangren
Ed Swangren

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

Related Questions