c++ extracting double from stream

前端 未结 3 1249
一整个雨季
一整个雨季 2021-01-14 05:35

I\'ve a funny problem with school exercise. I am given latitude and longitude and I have to ensure that it is in right format: \\(\\d+\\.\\d+[NS], \\d\\+.\\d+[EW]\\)

相关标签:
3条回答
  • 2021-01-14 06:11

    You can replace E with X (for example) in the input string, after running your code you can replace back X with E in EW variable.

    char    lBracket, rBracket, comma, NS, EW;
    int     nLat, nLon;
    double  lat, lon;
    
    std::string inputString = "(51.5N, 0.0E)";
    size_t e_pos = inputString.find( 'E' );
    if ( e_pos != std::string::npos ) {
        inputString.replace( e_pos, e_pos + 1, 'X' );
    }
    istringstream iss ( inputString );
    
    iss >> fixed >> lBracket >> nLat >> lat >> NS >> comma >> 
                       nLon >> lon >> EW >> rBracket;
    
    if ( EW == 'X' ) {
        EW = 'E';
    }
    
    lat += nLat;
    lon += nLon;
    

    Update: Sorry, didn't see your remark

    Only solution I came up with is replace the E with some other letter, which would probably work, but isn't really nice piece of code.

    If you want - I'll delete an answer

    0 讨论(0)
  • 2021-01-14 06:24

    Your problem is not what you think. .0E is not a valid floating point numeral even in scientific notation. It lacks any exponent.

    What is happening is that the stream parser is commited to the interpretation of .0E as a scientfic floating point numeral by the time it reaches the E, then finds no exponent; concludes that its interpretation is falsified; assigns 0 to the target double and set the failbit in iss, so no further extraction from the stream is possible. You could verify this by changing 0.0E to 1.1E and testing iss.fail() right after the attempted extraction of lon. You would still find that lon is set to 0, not 0.1, and that iss.fail() == true.

    I believe there is no way to avoid this in the context w.fE (w = whole part, f = fractional part) if you attempt to extract either the w.f or just the .f into a floating point variable with >>. You are out of the comfort zone of formatted >> extraction in this corner case and will need to get fiddly. And in fact that need does not spring solely from the w.fE corner case, given the pattern \(\d+\.\d+[NS], \d\+.\d+[EW]\) that you tell us the references must satisfy. The \d+\.\d+[NS] part is also too exacting for >> double >> char or for >> [unsigned|int] >> double >> char: the integral or floating point extractors would consume a leading + or - and the floating point extractors would not insist on the presence of a decimal point or on non-zero counts of digits both before and after it.

    The fiddliness of parsing your map references (without aid of regexes) would prompt me to make a map_ref class for them, such that you can try to extract a map_ref from an input stream (probably also from a string); can ask a map_ref whether it is a good or bad one (e.g. after an attempted extraction), and can insert a formatted map_ref into an output stream.

    Here is a sketch for such a class:

    #include <ostream>
    #include <istream>
    #include <iomanip>
    #include <utility>
    #include <limits>
    
    struct map_ref
    {
        map_ref() = default;
        map_ref(char NS, double lat, char EW, double lon)
        :   _NS(NS == 'N' || NS == 'S' ? NS : '?'),
            _EW(EW == 'E' || EW == 'W' ? EW : '?'),
            _lat(lat >= 0.0 && lat <= 90.0 ? lat : -1),
            _lon(lon >= 0.0 && lon <= 180.0 ? lon : -1),
            _good(_NS != '?' && _EW != '?' && _lat != -1 && _lon != -1){}
    
        // Todo: string ctor
    
        std::pair<char,double> latitude() const {
            return std::make_pair(_NS,_lat);
        }
    
        std::pair<char,double> longitude() const {
            return std::make_pair(_EW,_lon);
        }
    
        // Todo: setters, getters, equality etc.
    
        bool good() const {
            return _good;
        }
    
        bool read(std::istream & in);
    
        std::ostream & write(std::ostream & out, std::size_t precision = 4) const {
            return out << std::fixed << std::setprecision(precision) << 
                '(' << _lat << _NS << ", " << _lon << _EW << ')';
        }
    
        void clear() {
            *this = map_ref();
        }
    
    private:
        double 
        read_fixed_point(std::istream & in, char & dest, std::string const & delims);
        char _NS = '?';
        char _EW = '?';
        double _lat = -1;
        double _lon = -1;
        bool _good = false;
    };
    
    double 
    map_ref::read_fixed_point(
        std::istream & in, char & dest, std::string const & delims)
    {
        std::string s;
        unsigned whole_digs = 0, frac_digs = 0;
        while(in >> dest && 
                dest != '.' && delims.find(dest) == std::string::npos)
        {
            whole_digs += std::isdigit(dest) != 0;
            s += dest;
        }
        if (dest != '.') {
            return -1;
        }
        s += dest;
        while(in >> dest && delims.find(dest) == std::string::npos)
        {
            frac_digs += std::isdigit(dest) != 0;
            s += dest;
        }
        if (whole_digs == 0 || frac_digs == 0 ||
            whole_digs + frac_digs > std::numeric_limits<double>::digits10 ||
            s.length() != 1 + whole_digs + frac_digs) {
                return -1;
        }
        return std::stod(s);
    }
    
    
    bool map_ref::read(std::istream & in)
    {
        char lparen = 0;
        clear();
        in >> std::noskipws >> lparen;
        if (lparen != '(') {
            return _good = false;
        }
        _lat = read_fixed_point(in,_NS,"NS");
        if (_lat < 0.0 || _lat > 90.0) {
            return _good = false;
        }
        char comma = 0;
        in >> comma;
        if (comma != ',') {
            return _good = false;
        }
        while (std::isspace(in.peek())) {
            _EW = in.get();
        }
        _lon = read_fixed_point(in,_EW,"EW");
        if (_lon < 0.0 || _lon > 180.0) {
            return _good = false;
        }
        char rparen = 0;
        in >> rparen;
        return _good = rparen == ')';
    }
    
    std::istream & operator>>(std::istream & in, map_ref & mr)
    {
        mr.read(in);
        return in;
    }
    
    std::ostream & operator<<(std::ostream & out, map_ref const & mr)
    {
        return mr.write(out);
    }
    

    The class incorporates the obvious constraints that the latitude in a good map_ref shall be <= 90 North or South, and that the longitude shall be <= 180 East or West.

    The read(std::istream &) method parses map references as per your pattern, with the slight relaxation that 0 or more spaces are acceptable after the comma. Note that it classifies a parsed map reference as bad if either the latitude or longitude contains more digits than can be respresented without change in a double (i.e. more than std::numeric_limits<double>::digits10 == 15)

    The method:

    std::ostream & write(std::ostream & out, std::size_t precision = 4)
    

    lets you specify the precision with which the latitude and longitude will be represented in the output stream, as per std::set_precision(precision) or accept the default 4. If you set it to 0, then you'll lose all decimal places on output, so don't do that.

    The class comes with overloads of the global operators >> and << for formatted extraction and insertion of map_refs. They delegate respectively to map_ref::read and map_ref::write, with the default precision in the latter case; so for any other output precision, invoke map_ref::write directly.

    For some tests of map_ref parsing, you can append the following:

    #include <iostream>
    #include <sstream>
    
    static unsigned tests = 0, pass = 0, fail = 0;
    
    static void expect_good(char NS, double lat, char EW, double lon )
    {
        std::cout << "Testing (" << ++tests << ") " 
            << NS << ',' << lat << ',' << EW << ',' << lon << '\n';
        map_ref mr(NS,lat,EW,lon);
        if (!mr.good()) {
            std::cerr << "Failed (" << tests << "): Is good, got bad\n";
            ++fail;
        } else {
            ++pass;
            std::cout << "Passed (" << tests << "): Is good. Got \"" << mr << "\"\n";  
        }
    }
    
    static void expect_bad(char NS, double lat, char EW, double lon )
    {
        std::cout << "Testing (" << ++tests << ") " 
            << NS << ',' << lat << ',' << EW << ',' << lon << '\n';
        map_ref mr(NS,lat,EW,lon);
        if (mr.good()) {
            std::cerr << "Failed (" << tests << "): Is bad, got good\n";
            ++fail;
        } else {
            ++pass;
            std::cout << "Passed (" << tests << "): Is bad, got bad\n";  
        }
    }
    
    static void expect_good(std::string const & s)
    {
        std::cout << "Testing (" << ++tests << ") \"" << s << "\"\n";
        std::istringstream iss(s);
        map_ref mr;
        iss >> mr;
        if (!mr.good()) {
            std::cerr << "Failed (" << tests << "): Is good, got bad\n";
            ++fail;
        } else {
            ++pass;
            std::cout << "Passed (" << tests << "): Is good. Got \"" << mr << "\"\n";  
        }
    }
    
    static void expect_bad(std::string const & s)
    {
        std::cout << "Testing (" << ++tests << ") \"" << s << "\"\n";
        std::istringstream iss(s);
        map_ref mr;
        iss >> mr;
        if (mr.good()) {
            ++fail;
            std::cerr << "Failed (" << tests << "): Is bad, got good\n";
        } else {
            ++pass;
            std::cout << "Passed (" << tests << "): Is bad, got bad\n"; 
        }
    }
    
    
    int main()
    {
        expect_bad('E',1.00,'S',1.00);
        expect_bad('N',-1.00,'W',1.00);
        expect_bad('N',90.00,'W',180.01);
        expect_bad('S',90.01,'W',180.00);
        expect_good('S',90.00,'E',180.00);
        expect_good('S',0.0,'E',0.0);
        expect_bad("");
        expect_bad("1.1N, 2.2W");
        expect_bad("(1.1N, 2.2W");
        expect_bad("1.1N, 2.2W)");
        expect_bad("[1.1N, 2.2W)");
        expect_bad("(1.1N, 2.2W(");
        expect_bad("(N)");
        expect_bad("(N, W)");
        expect_bad("(0N, 1W)");
        expect_bad("(1.0N, 2W)");
        expect_bad("(1.0N, .2W)");
        expect_bad("(.01N, 1.2E)");
        expect_bad("(1.N, 1.2W)");
        expect_bad("(N1.1, E1.2)");
        expect_bad("(1.0N, 1.2 W)");
        expect_bad("(1.0X, 1.2W)");
        expect_bad("(1.0N, 1.2Z)");
        expect_bad("(+1.0N, 1.2E)");
        expect_bad("(1.+0N, 1.2E)");
        expect_bad("(1.0N, -1.2E)");
        expect_bad("(1.0N, 1.-2E)");
        expect_bad("(1.1N, 2.3.4E)");
        expect_bad("(0.0NN, 0.0E)");
        expect_bad("(0.0N, 0.0EW)");
        expect_bad("(0.0 1N, 0.0E)");
        expect_bad("(0.01N, 0 2.0E)");
        expect_bad("(0 .01N, 2.0E)");
        expect_bad("(0.01N, 2. 0E)");
        expect_bad("(12.34567890123456N, 2.0E)");
        expect_good("(0.0N, 0.0E)");
        expect_good("(1.0N,1.2W)");
        expect_good("(01.0N,01.2W)");
        expect_good("(1.0N,  1.2W)");
        expect_good("(0.123456789N, 0.000000001E)");
        expect_good("(0.000000001S, 0.123456789W)");
        expect_good("(0.123456789N, 0.000000001W)");
        expect_good("(0.000000001S, 0.123456789E)");
        expect_good("(1.1N, 12.3456789012345E)");
        expect_bad("(0.1E, 0.1N)");
        expect_bad("(0.1W, 0.1S)");
        expect_bad("(0.1W, 0.1N)");
        expect_bad("(0.1E, 0.1S)");
        expect_good("(90.0N, 180.0E)");
        expect_good("(90.0S, 180.0W)");
        expect_good("(90.0N, 180.0W)");
        expect_good("(90.0S, 180.0E)");
        expect_bad("(90.000001N, 180.0E)");
        expect_bad("(90.000001S, 180.0W)");
        expect_bad("(90.0N, 180.000001W)");
        expect_bad("(90.0S, 180.000001E)");
        std::cout << "Tests: " << tests << std::endl;
        std::cout << "Passed: " << pass << std::endl;
        std::cout << "Failed: " << fail << std::endl;  
        return 0;
    }
    

    (gcc 4.9.2/clang 3.6, -std=c++11)

    0 讨论(0)
  • 2021-01-14 06:24

    You're essentially looking at a parse problem. operator>>(istream&, string&) is a very simplistic parser that just tokenizes on whitespace.

    If your format specification is strict enough, and you should reject (51.5N , 0.0E) (extra space before comma) then just don't extract the comma. Instead, directly after extraction you should check that nLat contains the trailing comma and remove it. You no longer need >>comma.

    If you must support optional whitespace anywhere, it may help to preprocess the string by inserting an extra space before and after the comma (unconditionally). If there already was a space, there will now be two. That's no problem as whitespace skipping will skip any amount.

    0 讨论(0)
提交回复
热议问题