【问题标题】:NaN ASCII I/O with Visual C++使用 Visual C++ 的 NaN ASCII I/O
【发布时间】:2011-11-20 15:14:56
【问题描述】:

我想使用 iostream 和 Visual C++ 在文本文件中读取和写入 NaN 值。写入 NaN 值时,我得到1.#QNAN。但是,读回来输出1.0

float nan = std::numeric_limits<float>::quiet_NaN ();
std::ofstream os("output.txt");

os << nan ;
os.close();

输出为1.#QNAN

std::ifstream is("output.txt");
is >> nan ;
is.close();

nan 等于 1.0

解决方案

最后,按照 awoodland 的建议,我想出了这个解决方案。我选择“nan”作为 NaN 的字符串表示。 > 运算符都被覆盖。

using namespace ::std;

class NaNStream 
{
public:
  NaNStream(ostream& _out, istream& _in):out(_out), in(_in){}
  template<typename T>
  const NaNStream& operator<<(const T& v) const {out << v;return *this;}
  template<typename T>
  const NaNStream& operator>>(T& v) const {in >> v;return *this;}
protected:
  ostream& out;
  istream& in;
};

// override << operator for float type
template <> const NaNStream& NaNStream::operator<<(const float& v) const 
{
  // test whether v is NaN 
  if( v == v )
    out << v;
  else
    out << "nan";
  return *this;
}

// override >> operator for float type
template <> const NaNStream& NaNStream::operator>>(float& v) const 
{
  if (in >> v)
    return *this;

  in.clear();
  std::string str;
  if (!(in >> str))
    return *this;

  if (str == "nan")
    v = std::numeric_limits<float>::quiet_NaN();
  else
    in.setstate(std::ios::badbit); // Whoops, we've still "stolen" the string

  return *this;
}

一个最小的工作示例:一个有限浮点数和一个 NaN 被写入一个字符串流,然后读回。

int main(int,char**) 
{
  std::stringstream ss;
  NaNStream nis(ss, ss);
  nis << 1.5f << std::numeric_limits<float>::quiet_NaN ();
  std::cout << ss.str() << std::endl; // OUTPUT : "1.5nan"

  float a, b;
  nis >> a;  nis >> b;
  std::cout << a << b << std::endl;  // OUTPUT : "1.51.#QNAN"
}

【问题讨论】:

  • 问题应该是“如何使用 NaN 进行格式化 I/O”。我想。好问题。
  • pubs.opengroup.org/onlinepubs/007904975/functions/scanf.htmlIf the fprintf() family of functions generates character string representations for infinity and NaN (a symbolic entity encoded in floating-point format) to support IEEE Std 754-1985, the fscanf() family of functions shall recognize them as input. 不惜一切代价。
  • @Moo:如果这是真的,那么至少我们现在知道 iostreams 不使用 fscanf :-) 无论如何,这都是一件可笑的事情,因为文本NaN 的表示因编译器而异,也可能因州而异。也许只是不可能读取 NaN。 (甚至连字面意思都没有,想起来了。)
  • @KerrekSB: "NaN"_my_super_duper_literal_operator :P
  • @Mooing Duck:请注意,文档引用了 C99 标准(又名 ISO/IEC 9899:1999),正如我在回答中提到的,微软的运行时不符合该标准。

标签: c++ visual-c++ floating-point iostream


【解决方案1】:

floatdouble 值打印到std::ostream 时,使用类模板std::num_put&lt;&gt; (C++03 §22.2.2.2)。它将值格式化为好像由printf 使用%e%E,%f%g%G 格式说明符之一打印,具体取决于流的标志(表58)。

同样,当输入 floatdouble 值时,它会像使用带有 %g 格式说明符的 scanf 函数一样读取它(第 22.2.2.1.2/5 节)。

那么,下一个问题是为什么scanf 不能正确解析1.#QNAN。 C89 标准在其对fprintffscanf 函数的描述中均未提及NaN。它确实说浮点数的表示是未指定的,所以这属于未指定的行为。

另一方面,C99 确实在此处指定了行为。对于fprintf(C99 §7.19.6.1/8):

代表无穷大的double 参数被转换为其中一种样式 [-]inf[-]infinity — 哪种样式是实现定义的。一种 double 表示 NaN 的参数被转换为其中一种样式 [-]nan[-]nan(<em>n-char-sequence</em>) — 哪种风格,以及含义 任何n-char-sequence,都是实现定义的。 F 转换说明符 生成INFINFINITYNAN 而不是infinfinitynan243)

fscanf 被指定为根据strtod(3) 解析数字(C99 §7.19.6.2/12)。 strtod 解析如下(§7.20.1.3/3):

主题序列的预期形式是可选的加号或减号,然后是其中之一 以下:
— 一个非空的十进制数字序列,可选地包含一个小数点 字符,然后是 6.4.4.2 中定义的可选指数部分;
— 一个0x0X,然后是一个非空的十六进制数字序列,可选地包含一个 小数点字符,然后是 6.4.4.2 中定义的可选二进制指数部分;
INFINFINITY,忽略大小写
NANNAN(<em>n-char-sequence<sub>opt</sub></em>),忽略 NAN 部分的大小写,其中:

n字符序列: 数字 非数字 n字符序列数字 n字符序列非数字 主题序列定义为输入字符串的最长初始子序列, 从第一个非空白字符开始,即预期的形式。主题 如果输入字符串不是预期的形式,则序列不包含字符。

因此,在考虑了所有这些之后,最终结果是您的 C 标准库不符合 C99,因为根据上述,1.#QNAN 不是fprintf 的有效输出。但是,众所周知,Microsoft 的 C 运行时不兼容 C99,据我所知,它不打算很快兼容。由于 C89 没有在此处指定与 NaN 相关的行为,因此您不走运。

您可以尝试切换到不同的编译器和 C 运行时(例如 Cygwin+GCC),但对于这么小的钉子来说,这是一个非常大的锤子。如果您真的需要这种行为,我建议您为浮点数编写一个包装类,该类能够正确格式化和解析 NaN 值。

【讨论】:

  • 不错的答案,但切换到不同的编译器对我来说不是一个选项。
【解决方案2】:

使用 C++03,您可以借助辅助类和您自己的操作符轻松解决该问题:

#include <iostream>
#include <sstream>
#include <string>
#include <limits>

struct FloatNaNHelper {
  float value;
  operator const float&() const { return value; }
};

std::istream& operator>>(std::istream& in, FloatNaNHelper& f) {
  if (in >> f.value)
    return in;

  in.clear();
  std::string str;
  if (!(in >> str))
    return in;

  // use std::transform for lowercaseness?
  // NaN on my platform is written like this.
  if (str == "NaN")
    f.value = std::numeric_limits<float>::quiet_NaN();
  else
    in.setstate(std::ios::badbit); // Whoops, we've still "stolen" the string

  return in;
}

这在我的平台上非常适用于 NaN,但也说明了那里固有的可移植性问题 - 您的库似乎以不同的方式表示它,如果您想同时支持两者,这可能会使问题变得有些复杂。 我用这个测试:

int main() {
  std::istringstream in("1.0 555 NaN foo");
  FloatNaNHelper f1,f2,f3;
  in >> f1 >> f2 >> f3;
  std::cout << static_cast<float>(f1) << ", " << static_cast<float>(f2) << ", " << static_cast<float>(f3) << std::endl;

  if (in >> f1)
    std::cout << "OOPS!" << std::endl;
}

您还可以将其语义更改为更简洁的内容:

int main() {
  std::istringstream in("1.0 555 NaN foo");
  float f1,f2,f3;
  in >> FloatNaNHelper(f1) >> FloatNaNHelper(f2) >> FloatNaNHelper(f3);
  std::cout << f1 << ", " << f2 << ", " << f3 << std::endl;
}

需要更改FloatNaNNHelper

struct FloatNaNHelper {
  float& value;
  explicit FloatNaNHelper(float& f) : value(f) { }
};

还有运营商:

std::istream& operator>>(std::istream& in, const FloatNaNHelper& f);

【讨论】:

    猜你喜欢
    • 2014-03-17
    • 2015-05-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-20
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多