繁体   English   中英

为什么Clang std :: ostream会写一个std :: istream无法读取的双精度?

[英]Why does Clang std::ostream write a double that std::istream can't read?

我正在使用一个应用程序,它使用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之间存在差异? 这是一个铿锵的错误,如果没有,应该如何使用C ++流来编写可移植的浮点IO?

我相信clang在这里是符合的,如果我们读到std :: stod的答案会抛出一个应该有效的字符串out_of_range错误,它说:

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

7.63918•10 -313double精度范围内,但它处于低于正常范围。 C ++标准说stod调用strtod ,然后按照C标准来定义strtod C标准表明strtod可能会下溢,它表示“如果数学结果的大小如此之小,以至于数学结果无法表示,在指定类型的对象中没有非常的舍入误差,则结果会下溢。”是一种尴尬的措辞,但它指的是遇到次正常值时发生的舍入误差。 (次正常值会受到比正常值更大的相对误差,因此可以说它们的舍入误差非常大。)

因此,C ++标准允许C ++实现对次正规值进行下溢,即使它们是可表示的。

我们可以确认我们依赖于来自[facet.num.get.virtuals] p3.3.4的strtod

  • 对于double值,函数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

7.22.1.3p10中的 C11告诉我们:

函数返回转换后的值(如果有)。 如果无法执行转换,则返回零。 如果正确的值溢出且默认舍入生效(7.12.1),则返回正或负HUGE_VAL,HUGE_VALF或HUGE_VALL(根据返回类型和值的符号),并存储宏ERANGE的值在errno。 如果结果下溢(7.12.1),则函数返回一个值,该值的大小不大于返回类型中的最小归一化正数; 是否errno获取值ERANGE是实现定义的。

POSIX使用该约定

[ERANGE]
要返回的值将导致溢出或下溢。

我们可以通过fpclassify验证它是否正常( 请参见实时 )。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM