NaN ASCII I / O с Visual C ++ - PullRequest
       16

NaN ASCII I / O с Visual C ++

17 голосов
/ 20 сентября 2011

Я хочу читать и записывать значения NaN из / в текстовые файлы, используя iostream и Visual C ++. При записи значения 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"
}

Ответы [ 2 ]

16 голосов
/ 20 сентября 2011

При печати значения float или double в std::ostream используется шаблон класса std::num_put<> (C ++ 03 §22.2.2.2). Он форматирует значение, как если бы оно печаталось с помощью printf с одним из спецификаторов формата %e, %E, %f, %g или %G, в зависимости от флагов потока (Таблица 58).

Аналогично, при вводе значения float или double оно считывается как если бы оно было выполнено с помощью функции scanf со спецификатором формата %g (§22.2.2.1.2 / 5).

Итак, следующий вопрос: почему scanf неправильно анализирует 1.#QNAN. Стандарт C89 не упоминает NaN в своих описаниях функций fprintf и fscanf. Это говорит о том, что представление чисел с плавающей запятой не определено, поэтому это поведение не определено.

C99, с другой стороны, определяет здесь поведение. Для fprintf (C99 §7.19.6.1 / 8):

A double аргумент, представляющий бесконечность, преобразуется в один из стилей [-]inf или [-]infinity - какой стиль определяется реализацией. double аргумент, представляющий NaN, преобразуется в один из стилей [-]nan или [-]nan(<em>n-char-sequence</em>) - какой стиль и значение any n-char-sequence , определяется реализацией. Спецификатор преобразования F производит INF, INFINITY или NAN вместо inf, infinity или nan, соответственно. 243)

fscanf указывается для анализа числа в соответствии с strtod(3) (C99 §7.19.6.2 / 12). strtod разбирает следующим образом (§7.20.1.3 / 3):

Ожидаемая форма предметной последовательности - необязательный знак плюс или минус, затем один из следующее:
- непустая последовательность десятичных цифр, необязательно содержащая десятичную точку символ, затем необязательная часть экспоненты, как определено в 6.4.4.2;
- 0x или 0X, затем непустая последовательность шестнадцатеричных цифр, необязательно содержащая символ десятичной точки, затем необязательная двоичная экспоненциальная часть, как определено в 6.4.4.2;
- INF или INFINITY, без учета регистра
- NAN или NAN(<em>n-char-sequence<sub>opt</sub></em>), игнорируя регистр в части NAN, где:

n-char-sequence:
    digit
    nondigit
    n-char-sequence digit
    n-char-sequence nondigit
Предметная последовательность определяется как самая длинная начальная подпоследовательность входной строки, начиная с первого символа, не являющегося пробелом, который имеет ожидаемую форму. Предмет последовательность не содержит символов, если строка ввода не имеет ожидаемой формы.

Итак, после всего этого, в конечном итоге ваша стандартная библиотека C не совместима с C99, поскольку 1.#QNAN не является допустимым выводом fprintf в соответствии с вышеприведенным. Но хорошо известно, что среда выполнения Microsoft C не совместима с C99, и, насколько я знаю, она не планирует в ближайшее время стать совместимой. Поскольку C89 не определяет здесь поведение по отношению к NaN, вам не повезло.

Вы можете попробовать переключиться на другой компилятор и среду выполнения C (например, Cygwin + GCC), но это очень большой молоток для такого маленького гвоздя. Если вам действительно нужно такое поведение, я бы порекомендовал написать класс-оболочку для чисел с плавающей точкой, который способен правильно форматировать и анализировать значения NaN.

7 голосов
/ 20 сентября 2011

С 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);
...