¿Por qué Clang std :: ostream escribir el doble que std :: istream no puede leer?

votos
12

Estoy utilizando una aplicación que utiliza std::stringstreampara leer una matriz de espacio separado doubles de un archivo de texto. La aplicación utiliza el código un poco como:

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...};
}

Cosas bastante estándar. I diligentemente escribí algo de código para hacer que la matriz de datos ( data.dat), utilizando el método siguiente para cada línea de datos:

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';
}

es decir, utilizando std::ostream. Sin embargo, me encontré con la aplicación estaba fallando para leer mi archivo de datos usando este método (devolver la excepción arriba), en particular, que estaba fallando a leer 7.0552574226130007e-321.

Escribí el siguiente caso de prueba mínima, que muestra el comportamiento:

// 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;
    }
}

Probé este código en LLVM 10.0.0 (sonido metálico-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

También probé compilar con Clang 6.0.1, 6.0.0, 5.0.1, 5.0.0, 4.0.1, 4.0.0 y, pero no obtuve el mismo resultado.

Compilar con GCC 8.2.0, el código funciona como me esperaba:

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

¿Por qué hay una diferencia entre Clang y GCC? Es esto un error ruido metálico, y si no, ¿cómo se debe utilizar flujos de C ++ para escribir portátil de punto flotante IO?

Publicado el 19/09/2018 a las 17:03
fuente por usuario
En otros idiomas...                            


1 respuestas

votos
4

Creo sonido metálico es conforme aquí, si leemos la respuesta a std :: stod lanza out_of_range error para una cadena que debe ser válido que dice:

El estándar de C ++ permite conversiones de cuerdas para doubleque le informe de flujo inferior si el resultado está en el rango inferior a la normal a pesar de que es representable.

• 7,63918 10 -313 está dentro de la gama de double, pero es en el rango inferior a la normal. El estándar de C ++ dice stodllamadas strtody luego se remite a la norma C para definir strtod. El estándar C indica que strtodpuede subdesbordamiento, de la que se dice que “el underflow resultado si la magnitud del resultado matemático es tan pequeña que el resultado matemático no puede ser representado, sin error de redondeo extraordinaria, en un objeto del tipo especificado”. Es decir redacción torpe, pero se refiere a los errores de redondeo que se producen cuando se encuentran valores subnormales. (Valores subnormales están sujetos a errores relativos mayores que los valores normales, por lo que sus errores de redondeo pueden decirse que es extraordinario.)

Por lo tanto, una aplicación C ++ está permitido por la norma del C ++ para subdesbordamiento para valores subnormales a pesar de que son representables.

Podemos confirmar estamos confiando en strtod de p3.3.4 [facet.num.get.virtuals] :

  • Para un valor doble, la función strtod.

Podemos probar esto con este pequeño programa (verlo en directo):

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") ;
}

que el siguiente resultado:

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

C11 en 7.22.1.3p10 nos dice:

Las funciones devuelven el valor convertido, si los hay. Si no se pudo realizar la conversión, se devuelve cero. Si el correcto desbordamientos de valor y por defecto redondeo es en efecto (7.12.1), más o menos HUGE_VAL, HUGE_VALF, o HUGE_VALL se devuelve (de acuerdo con el tipo de retorno y el signo del valor), y el valor de la ERANGE macro se almacena en errno. Si los subdesbordamientos resultado (7.12.1), las funciones devuelven un valor cuya magnitud no es mayor que el número positivo normalizado más pequeña en el tipo de retorno; si errno adquiere la ERANGE valor es definido por la implementación.

POSIX utiliza esa convención :

[ERANGE]
El valor a ser devuelto causaría desbordamiento o subdesbordamiento.

Podemos comprobar que es inferior a la normal a través de fpclassify ( ver en directo ).

Respondida el 19/09/2018 a las 22:07
fuente por usuario

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more