代码之家  ›  专栏  ›  技术社区  ›  Daniel

为什么clang std::ostream要写std::istream不能读的两倍?

  •  12
  • Daniel  · 技术社区  · 6 年前

    我使用的应用程序 std::stringstream 读取空间分隔的矩阵 double 来自文本文件。应用程序使用的代码有点像:

    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++流来编写可移植的浮点IO?

    1 回复  |  直到 6 年前
        1
  •  4
  •   Shafik Yaghmour    6 年前

    我相信Clang在这里是一致的,如果我们读到答案 std::stod throws out_of_range error for a string that should be valid 上面写着:

    C++标准允许字符串转换为 double 如果结果在次正常范围内(即使它是可表示的),则报告下溢。

    76391810 -313个 在以下范围内 双重的 ,但它在正常范围内。C++标准说 stod 电话 strtod 然后按照c标准定义 字符串转换为浮点数 .C标准表明 字符串转换为浮点数 可能下溢,它表示如果数学结果的大小很小,以致在指定类型的对象中无法表示数学结果,且没有非常大的舍入误差,则结果下溢。这是很尴尬的措辞,但它指的是当遇到低于正常值时发生的舍入错误。(低于正常值的相对误差比正常值大,因此它们的舍入误差可以说是非常大的。)

    因此,C++实现允许C++实现下溢值的下溢,即使它们是可表示的。

    我们可以确定我们依赖 strtod from [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),则返回正负巨大值、巨大值或巨大值(根据值的返回类型和符号),并且宏范围的值存储在errno中。 如果结果下溢(7.12.1),则函数返回的值的大小不大于返回类型中最小的规范化正数;errno是否获取值erange是实现定义的。

    POSIX使用 that convention 以下内容:

    [伊兰格]
    要返回的值将导致溢出或下溢。

    我们可以通过 fpclassify ( see it live )中。