web-dev-qa-db-ja.com

入力ストリームを使用して無限大またはNaN値を読み取ることは可能ですか?

入力ファイルストリームによって読み取られる入力があります(たとえば):

-365.269511 -0.356123 -Inf 0.000000

std::ifstream mystream;を使用してファイルからいくつかに読み取るとき

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

mystreamがすでに開かれていて、ファイルが有効であると仮定します)、

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

mystreamは失敗状態です。私は期待します

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

出力する

-365.269511 -0.356123 -1 -1。代わりに-365.269511 -0.356123 -Inf 0を出力したいと思います。

このデータセットは、C++ストリームを使用して出力されました。逆のプロセス(出力を読み取る)を実行できないのはなぜですか?求めている機能を取得するにはどうすればよいですか?

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;
}

入力:inf

出力:

inf
0:inf

参照: http://ideone.com/jVvei

また、この問題に関連しているのは、例を示していませんが、NaN解析です。

私は受け入れられた答えにideoneの完全な解決策を追加しました。また、「Inf」と「nan」のペアリングも含まれています。これは、MatLabなどの他のプログラムから取得される可能性のあるキーワードのバリエーションです。

44
Drise

編集:doubleのラッパー構造の使用を避けるために、代わりにラッパークラス内にistreamを囲みます。

残念ながら、doubleに別の入力メソッドを追加することによって生じるあいまいさを回避する方法を理解できません。以下の実装では、istreamの周りにラッパー構造を作成し、ラッパークラスはinputメソッドを実装します。入力メソッドは否定性を判別してから、doubleを抽出しようとします。それが失敗した場合は、解析を開始します。

編集:エラー状態をより適切にチェックしてくれたseheに感謝します。

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);
    }
};

解析ルーチンは、最初に思ったよりも実装が少し難しいものでしたが、文字列全体をputbackしようとするのは避けたかったのです。

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;
}

このルーチンがデフォルトのdouble入力と異なる動作の1つは、入力がたとえば-の場合、"-inp"文字が消費されないことです。失敗しても、"-inp"double_istreamのストリームに残りますが、通常のistreamの場合は"inp"のみがストリームに残ります。

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;

私のシステムでの上記のスニペットの出力は次のとおりです。

1 nan inf -inf nan 1.2

編集:ヘルパークラスのような「iomanip」を追加します。 double_imanipオブジェクトは、>>チェーンに複数回出現すると、トグルのように機能します。

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;
}

そして、それを試すための次のコード:

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;
}

上記の出力は次のとおりです。

1 nan inf -inf nan 1.2
inf

Driseからの編集:元々含まれていなかったInfやnanなどのバリエーションを受け入れるためにいくつかの編集を行いました。また、コンパイルされたデモンストレーションにしました。これは http://ideone.com/qIFVo で表示できます。

9
jxh

UpdateBoostSpiritがこの領域のすべての種類の特別な値を処理できることを示す簡単なテストケースを提供しました。以下を参照してください:ブーストスピリット(FTW)

標準

私が見つけたこの分野の唯一の規範的な情報は、C99標準のセクション7.19.6.1 /7.19.6.2にあります。

残念ながら、最新のC++標準ドキュメント(n3337.pdf)の対応するセクションでは、infinityinf、またはNaNのサポートが同じように指定されていないようです。 (おそらく、C99/C11仕様を参照する脚注がありませんか?)

ライブラリの実装者

2000年に、Apache libstdcxxは バグレポート を受け取りました

_num_get<>_ファセットのdo_get()メンバーは、特別な文字列_[-]inf[inity]_および_[-]nan_を考慮に入れていません。ファセットは、そのような文字列に遭遇するとエラーを報告します。許可される文字列のリストについては、C99の7.19.6.1および7.19.6.2を参照してください。

ただし、その後の議論では、(少なくともnamed locale- sの場合)、実装が特別な値を解析することは実際には違法であることがわかりました。

ルックアップテーブルの文字は「0123456789abcdefABCDEF +-」です。ライブラリの問題221は、それを「0123456789abcdefxABCDEFX +-」に修正します。 「N」はルックアップテーブルに存在しないため、num_get <> :: do_get()のステージ2は文字シーケンス「NaN」の読み取りを許可されていません。

その他のリソース

securecoding.cert.org は、解析を回避するために次の「準拠コード」が必須であることを明確に示しています無限大またはNaN。これは、一部の実装が実際にそれをサポートしていることを意味します-作成者が公開されたコードをテストしたことがあると仮定します。

_#include <cmath>

float currentBalance; /* User's cash balance */

void doDeposit() {
  float val;

  std::cin >> val;
  if (std::isinf(val)) {
    // handle infinity error
  }
  if (std::isnan(val)) {
    // handle NaN error
  }
  if (val >= MaxValue - currentBalance) {
    // Handle range error
  }

  currentBalance += val;
}
_

ブーストスピリット(FTW)

次の簡単な例には、目的の出力があります。

_#include <boost/spirit/include/qi.hpp>
namespace qi = boost::spirit::qi;

int main()
{
    const std::string input = "3.14 -inf +inf NaN -NaN +NaN 42";

    std::vector<double> data;
    std::string::const_iterator f(input.begin()), l(input.end());

    bool ok = qi::parse(f,l,qi::double_ % ' ',data);

    for(auto d : data)
        std::cout << d << '\n';
}
_

出力:

_3.14
-inf
inf
nan
-nan
nan
42
_

要約/ TL; DR

C99は、* printf/* scanfの動作に無限大NaNを含めるように指定していると言いたくなります。 C++ 11は、残念ながらそれを指定していないように見えます(または、名前付きロケールが存在する場合は禁止しているようです)。

13
sehe

次のようなシグネチャを使用して関数を記述します。

std::istream & ReadDouble(std::istream & is, double & d);

その中で、あなたは:

  1. operator>>を使用してストリームから文字列を読み取ります
  2. さまざまな方法のいずれかを使用して、文字列をdoubleに変換してみてください。 std::stodboost::lexical_castなど..
  3. 変換が成功した場合は、doubleを設定し、ストリームを返します。
  4. 変換が失敗した場合は、文字列が「inf」や「INF」などと等しいかどうかをテストします。
  5. テストに合格した場合は、doubleを無限大に設定し、ストリームを返します。それ以外の場合は、次のようにします。
  6. テストが失敗した場合は、ストリームの失敗ビットを設定して返します。
9

変数を文字列に読み込んで解析するだけです。文字列をdouble変数に入れて、文字列のように出力されることを期待することはできません。それが機能すれば、文字列は必要ないからです。

次のようなもの:

string text;
double d;
while(cin >> text)
{
    if(text == "Inf")       //you could also add it with negative infinity
    {
         d = std::numeric_limits<double>::infinity();
    }
    else
    {
        d = atof(text.c_str());
    }
}
3
Blood