c++numeric-limits

Is it possible to read infinity or NaN values using input streams?


I have some input to be read by a input filestream (for example):

-365.269511 -0.356123 -Inf 0.000000

When I use std::ifstream mystream; to read from the file to some

double d1 = -1, d2 = -1, d3 = -1, d4 = -1;

(assume mystream has already been opened and the file is valid),

mystream >> d1 >> d2 >> d3 >> d4;

mystream is in the fail state. I would expect

std::cout << d1 << " " << d2 << " " << d3 << " " << d4 << std::endl;

to output

-365.269511 -0.356123 -1 -1. I would want it to output -365.269511 -0.356123 -Inf 0 instead.

This set of data was output using C++ streams. Why can't I do the reverse process (read in my output)? How can I get the functionality I seek?

From MooingDuck:

#include <iostream>
#include <limits>

using namespace std;

int main()
{
  double myd = std::numeric_limits<double>::infinity();
  cout << myd << '\n';
  cin >> myd;
  cout << cin.good() << ":" << myd << endl;
  return 0;
}

Input: inf

Output:

inf
0:inf

See also: http://ideone.com/jVvei

Also related to this problem is NaN parsing, even though I do not give examples for it.

I added to the accepted answer a complete solution on ideone. It also includes paring for "Inf" and "nan", some possible variations to those keywords that may come from other programs, such as MatLab.


Solution

  • Edit: To avoid the use of a wrapper structure around a double, I enclose an istream within a wrapper class instead.

    Unfortunately, I am unable to figure out how to avoid the ambiguity created by adding another input method for double. For the implementation below, I created a wrapper structure around an istream, and the wrapper class implements the input method. The input method determines negativity, then tries to extract a double. If that fails, it starts a parse.

    Edit: Thanks to sehe for getting me to check for error conditions better.

    struct double_istream {
        std::istream &in;
    
        double_istream (std::istream &i) : in(i) {}
    
        double_istream & parse_on_fail (double &x, bool neg);
    
        double_istream & operator >> (double &x) {
            bool neg = false;
            char c;
            if (!in.good()) return *this;
            while (isspace(c = in.peek())) in.get();
            if (c == '-') { neg = true; }
            in >> x;
            if (! in.fail()) return *this;
            return parse_on_fail(x, neg);
        }
    };
    

    The parsing routine was a little trickier to implement than I first thought it would be, but I wanted to avoid trying to putback an entire string.

    double_istream &
    double_istream::parse_on_fail (double &x, bool neg) {
        const char *exp[] = { "", "inf", "NaN" };
        const char *e = exp[0];
        int l = 0;
        char inf[4];
        char *c = inf;
        if (neg) *c++ = '-';
        in.clear();
        if (!(in >> *c).good()) return *this;
        switch (*c) {
        case 'i': e = exp[l=1]; break;
        case 'N': e = exp[l=2]; break;
        }
        while (*c == *e) {
            if ((e-exp[l]) == 2) break;
            ++e; if (!(in >> *++c).good()) break;
        }
        if (in.good() && *c == *e) {
            switch (l) {
            case 1: x = std::numeric_limits<double>::infinity(); break;
            case 2: x = std::numeric_limits<double>::quiet_NaN(); break;
            }
            if (neg) x = -x;
            return *this;
        } else if (!in.good()) {
            if (!in.fail()) return *this;
            in.clear(); --c;
        }
        do { in.putback(*c); } while (c-- != inf);
        in.setstate(std::ios_base::failbit);
        return *this;
    }
    

    One difference in behavior this routine will have over the the default double input is that the - character is not consumed if the input was, for example "-inp". On failure, "-inp" will still be in the stream for double_istream, but for a regular istream only "inp" will be left in the the stream.

    std::istringstream iss("1.0 -NaN inf -inf NaN 1.2");
    double_istream in(iss);
    double u, v, w, x, y, z;
    in >> u >> v >> w >> x >> y >> z;
    std::cout << u << " " << v << " " << w << " "
              << x << " " << y << " " << z << std::endl;
    

    The output of the above snippet on my system is:

    1 nan inf -inf nan 1.2
    

    Edit: Adding a "iomanip" like helper class. A double_imanip object will act like a toggle when it appears more than once in the >> chain.

    struct double_imanip {
        mutable std::istream *in;
        const double_imanip & operator >> (double &x) const {
            double_istream(*in) >> x;
            return *this;
        }
        std::istream & operator >> (const double_imanip &) const {
            return *in;
        }
    };
    
    const double_imanip &
    operator >> (std::istream &in, const double_imanip &dm) {
        dm.in = &in;
        return dm;
    }
    

    And then the following code to try it out:

    std::istringstream iss("1.0 -NaN inf -inf NaN 1.2 inf");
    double u, v, w, x, y, z, fail_double;
    std::string fail_string;
    iss >> double_imanip()
        >> u >> v >> w >> x >> y >> z
        >> double_imanip()
        >> fail_double;
    std::cout << u << " " << v << " " << w << " "
              << x << " " << y << " " << z << std::endl;
    if (iss.fail()) {
        iss.clear();
        iss >> fail_string;
        std::cout << fail_string << std::endl;
    } else {
        std::cout << "TEST FAILED" << std::endl;
    }
    

    The output of the above is:

    1 nan inf -inf nan 1.2
    inf
    

    Edit from Drise: I made a few edits to accept variations such as Inf and nan that wasn't originally included. I also made it into a compiled demonstration, which can be viewed at http://ideone.com/qIFVo.