是否可以使用输入流读取无穷大或NaN值?

Dri*_*ise 44 c++ numeric-limits

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

-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;
}
Run Code Online (Sandbox Code Playgroud)

输入: inf

输出:

inf
0:inf
Run Code Online (Sandbox Code Playgroud)

另见:http://ideone.com/jVvei

与此问题相关的还有NaN解析,即使我没有举例说明.

我在接受的答案中添加了一个关于ideone的完整解决方案.它还包括"Inf"和"nan"的削减,这些关键字可能来自其他程序,例如MatLab.

seh*_*ehe 13

更新提供了一个简单的测试用例,显示Boost Spirit能够处理此区域中的各种特殊值.见下文:提升精神(FTW).

标准

我能够找到的这个领域唯一的规范性信息是在C99标准的7.19.6.1/7.19.6.2节中.

可悲的是,最新的C++标准文档(n3337.pdf)的相应部分不会出现指定的支持infinity,inf并或NaN以同样的方式.(也许我错过了一个引用C99/C11规范的脚注?)

图书馆实施者

2000年,Apache的libstdcxx收到一个bug报告指出

num_get<>小的do_get()成员未能采取特殊字符串[-]inf[inity][-]nan考虑.facet在遇到此类字符串时会报告错误.有关允许的字符串列表,请参见C99的7.19.6.1和7.19.6.2.

然而,随后的讨论产生了(至少使用命名的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;
}
Run Code Online (Sandbox Code Playgroud)

提升精神(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';
}
Run Code Online (Sandbox Code Playgroud)

输出:

3.14
-inf
inf
nan
-nan
nan
42
Run Code Online (Sandbox Code Playgroud)

摘要/ TL; DR

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

  • 我会不仅仅是“倾向于说”。要查看的 C99 的正确部分是第 7.20.1.3 节,该部分清晰明确,要求支持将“inf”或“infinity”读作表示无穷大,将“nan”或“nan&lt;arbitrary&gt;”读作表示 NaN (所有这些都不区分大小写,`&lt;arbitrary&gt;` 基本上意味着任意输入序列。 (2认同)
  • @JerryCoffin你的`<任意>`不是随意的.它被指定为`NAN(n-char-sequence_opt)`,即任意部分只由数字和非数字组成,括号是必需的.否则,例如`nanometer`将被视为NaN,这是荒谬的. (2认同)

Ben*_*ley 9

编写一个带有如下签名的函数:

std::istream & ReadDouble(std::istream & is, double & d);
Run Code Online (Sandbox Code Playgroud)

在里面,你:

  1. 使用从流中读取字符串 operator>>
  2. 尝试使用各种方法之一将字符串转换为double.std::stod,boost::lexical_cast等...
  3. 如果转换成功,请设置double并返回流.
  4. 如果转换失败,请使用"inf"或"INF"等测试字符串是否相等.
  5. 如果测试通过,将double设置为infinity并返回流,否则:
  6. 如果测试失败,请在流上设置失败位并将其返回.


jxh*_*jxh 9

编辑:为了避免在double周围使用包装器结构,我将istream一个包装在一个包装类中.

不幸的是,我无法弄清楚如何避免通过添加另一种输入方法而产生的歧义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);
    }
};
Run Code Online (Sandbox Code Playgroud)

解析例程实现起来比我原先想象的要复杂一点,但我想避免尝试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;
}
Run Code Online (Sandbox Code Playgroud)

此例程对默认double输入的行为的一个区别-是,例如,如果输入是字符,则不会消耗该字符"-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;
Run Code Online (Sandbox Code Playgroud)

我系统上面代码片段的输出是:

1 nan inf -inf nan 1.2
Run Code Online (Sandbox Code Playgroud)

编辑:添加像助手类一样的"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;
}
Run Code Online (Sandbox Code Playgroud)

然后使用以下代码进行试用:

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;
}
Run Code Online (Sandbox Code Playgroud)

以上的输出是:

1 nan inf -inf nan 1.2
inf
Run Code Online (Sandbox Code Playgroud)

来自Drise的编辑:我进行了一些编辑,以接受最初未包含的Inf和nan等变体.我还将它编成了一个编译演示,可以在http://ideone.com/qIFVo上查看.