Почему Clang std :: ostream пишет двойной код, который std :: istream не может прочитать? - PullRequest
0 голосов
/ 19 сентября 2018

Я использую приложение, которое использует std::stringstream для чтения матрицы пробелов, отделенных double s от текстового файла.Приложение использует код, похожий на:

std::ifstream file {"data.dat"};
const auto header = read_header(file);
const auto num_columns = header.size();
std::string line;
while (std::getline(file, line)) {
    std::istringstream ss {line}; 
    double val;
    std::size_t tokens {0};
    while (ss >> val) {
        // do stuff
        ++tokens;
    }
    if (tokens < num_columns) throw std::runtime_error {"Bad data matrix..."};
}

Довольно стандартные вещи.Я старательно написал некоторый код для создания матрицы данных (data.dat), используя следующий метод для каждой строки данных:

void write_line(const std::vector<double>& data, std::ostream& out)
{
    std::copy(std::cbegin(data), std::prev(std::cend(data)),
              std::ostream_iterator<T> {out, " "});
    out << data.back() << '\n';
}

, то есть используя std::ostream.Однако я обнаружил, что приложению не удается прочитать мой файл данных с использованием этого метода (исключая вышеприведенное исключение), в частности, оно не может прочитать 7.0552574226130007e-321.

Я написал следующий минимальный тестовый пример, который показываетПоведение:

// iostream_test.cpp

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

int main()
{
    constexpr double x {1e-320};
    std::ostringstream oss {};
    oss << x;
    const auto str_x = oss.str();
    std::istringstream iss {str_x};
    double y;
    if (iss >> y) {
        std::cout << y << std::endl;
    } else {
        std::cout << "Nope" << std::endl;
    }
}

Я тестировал этот код на LLVM 10.0.0 (clang-1000.11.45.2):

$ clang++ --version
Apple LLVM version 10.0.0 (clang-1000.11.45.2)
Target: x86_64-apple-darwin17.7.0 
$ clang++ -std=c++14 -o iostream_test iostream_test.cpp
$ ./iostream_test
Nope

Я также пытался скомпилировать с Clang 6.0.1, 6.0.0, 5.0.1, 5.0.0, 4.0.1 и 4.0.0, но получили тот же результат.

При компиляции с GCC 8.2.0 код работает так, как я ожидал:

$ g++-8 -std=c++14 -o iostream_test iostream_test.cpp
$ ./iostream_test.cpp
9.99989e-321

Почему есть разница между Clang и GCC?Это ошибка clang, и если нет, то как использовать потоки C ++ для записи переносимых операций ввода-вывода с плавающей запятой?

1 Ответ

0 голосов
/ 20 сентября 2018

Я считаю, что clang здесь соответствует, если мы прочитаем ответ на ошибку std :: stod throws out_of_range для строки, которая должна быть действительной В ней говорится:

C ++Стандарт позволяет преобразовывать строки в double для сообщения о недостаточном объеме, если результат находится в субнормальном диапазоне, даже если он представим.

7.63918 • 10 -313 находится в диапазоне double, но это в пределах нормы.Стандарт C ++ говорит, что stod вызывает strtod, а затем использует стандарт C для определения strtod.Стандарт C указывает, что strtod может быть недостаточным, о чем он говорит: «Результат теряет значение, если величина математического результата настолько мала, что математический результат не может быть представлен без исключительной ошибки округления в объекте указанного типа.Это неловкое выражение, но оно относится к ошибкам округления, возникающим при обнаружении субнормальных значений.(Субнормальные значения подвержены большим относительным ошибкам, чем нормальные значения, поэтому их ошибки округления можно назвать экстраординарными.)

Таким образом, стандарт C ++ допускает снижение значения субнормальных значений в стандарте C ++.даже если они представимы.

Мы можем подтвердить, что полагаемся на strtod из [facet.num.get.virtuals] p3.3.4 :

  • Для двойного значения, функция strtod.

Мы можем проверить это с помощью этой небольшой программы (посмотреть ее вживую):

void check(const char* p) 
{
  std::string str{p};

    printf( "errno before: %d\n", errno ) ;
    double val = std::strtod(str.c_str(), nullptr);
    printf( "val: %g\n", val ) ;
    printf( "errno after: %d\n", errno ) ;
    printf( "ERANGE value: %d\n", ERANGE ) ;

}

int main()
{
 check("9.99989e-321") ;
}

, который следующий результат:

errno before: 0
val: 9.99989e-321
errno after: 34
ERANGE value: 34

C11 в 7.22.1.3p10 говорит нам:

Функции возвращают преобразованное значение, если оно есть.Если преобразование не может быть выполнено, возвращается ноль.Если корректное переполнение значения и округление по умолчанию действуют (7.12.1), возвращается плюс или минус HUGE_VAL, HUGE_VALF или HUGE_VALL (в соответствии с типом возврата и знаком значения), и значение макроса ERANGE сохраняетсяв errno. Если результат потерян (7.12.1), функции возвращают значение, величина которого не превышает наименьшего нормализованного положительного числа в типе возврата;получает ли errno значение ERANGE, определяется реализацией.

POSIX использует это соглашение :

[ERANGE]
возвращаемое значение вызовет переполнение или недостаточное значение.

Мы можем проверить, что он ненормальный, с помощью fpclassify ( посмотреть вживую ).

...