49

我有一些输入要由输入文件流读取(例如):

-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。

4

6 回答 6

13

更新提供了一个简单的测试用例,表明 Boost Spirit 能够处理该领域的所有特殊值。见下文:提升精神(FTW)

标准

我能找到的唯一规范信息是在 C99 标准的第 7.19.6.1/7.19.6.2 节中。

遗憾的是,最新的 C++ 标准文档 (n3337.pdf) 的相应部分似乎没有以相同的方式指定对infinity, infand or的支持。NaN(也许我遗漏了一个引用 C99/C11 规范的脚注?)

库实现者

2000 年,Apache libstdcxx 收到了一份错误报告,指出

num_get<>facet 的成员没有考虑到do_get()特殊的字符串。切面遇到此类字符串时会报告错误。有关允许的字符串列表,请参阅 C99 的 7.19.6.1 和 7.19.6.2。[-]inf[inity][-]nan

然而,随后的讨论表明(至少对于命名的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;博士

我倾向于说 C99 指定 *printf/*scanf 的行为包括infinityNaN。遗憾的是,C++11 似乎没有指定它(甚至在存在命名语言环境的情况下禁止它)。

于 2012-07-10T19:55:34.050 回答
9

编写一个带有这样签名的函数:

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

在里面,你:

  1. 使用从流中读取字符串operator>>
  2. 尝试使用各种方法之一将字符串转换为双精度。std::stod, boost::lexical_cast, 等等...
  3. 如果转换成功,则设置双精度并返回流。
  4. 如果转换失败,请使用“inf”或“INF”等测试字符串是否相等。
  5. 如果测试通过,将双精度设置为无穷大并返回流,否则:
  6. 如果测试失败,则在流上设置失败位并返回它。
于 2012-07-10T19:54:28.953 回答
9

编辑:为了避免在 double 周围使用包装器结构,我将 anistream封装在包装器类中。

不幸的是,我无法弄清楚如何避免为double. 对于下面的实现,我在 周围创建了一个包装器结构istream,包装器类实现了输入法。输入法确定否定性,然后尝试提取双精度数。如果失败,它将开始解析。

编辑:感谢 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输入的行为不同之处在于,-如果输入是,例如,则不会使用字符"-inp"。失败时,"-inp"仍将在流中 for 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查看。

于 2012-07-10T20:40:03.873 回答
4

只需将变量读入字符串并解析它们。您不能将字符串放入双变量中并期望它们像字符串一样输出,因为如果它有效,则不需要字符串。

类似于:

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());
    }
}
于 2012-07-10T19:39:12.447 回答
1

尽管这个问题已经很老了,但我想贡献最适合我目的的解决方案。如果您考虑使用 Boost,但 Boost Spirit 似乎有点过头了,您可以尝试像这样使用boost::math::nonfinite_num_putboost::math::nonfinite_num_getlocale facets 1

#include <boost/math/special_functions/nonfinite_num_facets.hpp>
#include <iostream>
#include <limits>
#include <sstream>

int main()
{
  std::locale default_locale;
  std::locale tmp_locale(default_locale,
      new boost::math::nonfinite_num_put<char>());
  std::locale upgraded_locale(default_locale,
      new boost::math::nonfinite_num_get<char>());

  double inf = std::numeric_limits<double>::infinity();

  std::stringstream out_s;
  out_s.imbue(upgraded_locale);
  out_s << inf;
  std::cout << out_s.str() << std::endl;
  std::stringstream in_s(out_s.str());
  in_s.imbue(upgraded_locale);
  double check_inf;
  in_s >> check_inf;
  std::cout << (inf == check_inf ? "nice" : "not nice") << std::endl;
  return 0;
}

输出:

inf
nice
于 2020-04-05T09:48:42.130 回答
0

您将不得不编写自定义提取函数,因为您的具体实现显然不能正确处理它们。

于 2012-07-10T19:38:55.237 回答