简体   繁体   English

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

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

I am using an application that uses std::stringstream to read a matrix of space separated double s from a text file. 我正在使用一个应用程序,它使用std::stringstream从文本文件中读取空格分隔的double s矩阵。 The application uses code a little like: 该应用程序使用的代码有点像:

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

Pretty standard stuff. 很标准的东西。 I diligently wrote some code to make the data matrix ( data.dat ), using the following method for each data line: 我努力地编写了一些代码来制作数据矩阵( 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';
}

ie using std::ostream . 即使用std::ostream However, I found the application was failing to read my data file using this method (throwing the exception above), in particular it was failing to read 7.0552574226130007e-321 . 但是,我发现应用程序无法使用此方法读取我的数据文件(抛出上面的异常),特别是它无法读取7.0552574226130007e-321

I wrote the following minimal test case which shows the behaviour: 我写了以下最小测试用例,显示了行为:

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

I tested this code on LLVM 10.0.0 (clang-1000.11.45.2): 我在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

I also tried compiling with Clang 6.0.1, 6.0.0, 5.0.1, 5.0.0, 4.0.1, and 4.0.0, but got the same result. 我也尝试使用Clang 6.0.1,6.0.0,5.0.1,5.0.0,4.0.1和4.0.0进行编译,但得到了相同的结果。

Compiling with GCC 8.2.0, the code works as I would expect: 使用GCC 8.2.0进行编译,代码按照我的预期运行:

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

Why is there a difference between Clang and GCC? 为什么Clang和GCC之间存在差异? Is this a clang bug, and if not, how should one use C++ streams to write portable floating-point IO? 这是一个铿锵的错误,如果没有,应该如何使用C ++流来编写可移植的浮点IO?

I believe clang is conformant here, if we read the answer to std::stod throws out_of_range error for a string that should be valid it says: 我相信clang在这里是符合的,如果我们读到std :: stod的答案会抛出一个应该有效的字符串out_of_range错误,它说:

The C++ standard allows conversions of strings to double to report underflow if the result is in the subnormal range even though it is representable. 如果结果处于次正常范围内,则C ++标准允许将字符串转换为double以报告下溢,即使它是可表示的。

7.63918•10 -313 is within the range of double , but it is in the subnormal range. 7.63918•10 -313double精度范围内,但它处于低于正常范围。 The C++ standard says stod calls strtod and then defers to the C standard to define strtod . C ++标准说stod调用strtod ,然后按照C标准来定义strtod The C standard indicates that strtod may underflow, about which it says “The result underflows if the magnitude of the mathematical result is so small that the mathematical result cannot be represented, without extraordinary roundoff error, in an object of the specified type.” That is awkward phrasing, but it refers to the rounding errors that occur when subnormal values are encountered. C标准表明strtod可能会下溢,它表示“如果数学结果的大小如此之小,以至于数学结果无法表示,在指定类型的对象中没有非常的舍入误差,则结果会下溢。”是一种尴尬的措辞,但它指的是遇到次正常值时发生的舍入误差。 (Subnormal values are subject to larger relative errors than normal values, so their rounding errors might be said to be extraordinary.) (次正常值会受到比正常值更大的相对误差,因此可以说它们的舍入误差非常大。)

Thus, a C++ implementation is allowed by the C++ standard to underflow for subnormal values even though they are representable. 因此,C ++标准允许C ++实现对次正规值进行下溢,即使它们是可表示的。

We can confirm we are relying on strtod from [facet.num.get.virtuals]p3.3.4 : 我们可以确认我们依赖于来自[facet.num.get.virtuals] p3.3.4的strtod

  • For a double value, the function strtod. 对于double值,函数strtod。

We can test this with this small program (see it live): 我们可以用这个小程序测试一下(现场观看):

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

which the following result: 其结果如下:

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

C11 in 7.22.1.3p10 tells us: 7.22.1.3p10中的 C11告诉我们:

The functions return the converted value, if any. 函数返回转换后的值(如果有)。 If no conversion could be performed, zero is returned. 如果无法执行转换,则返回零。 If the correct value overflows and default rounding is in effect (7.12.1), plus or minus HUGE_VAL, HUGE_VALF, or HUGE_VALL is returned (according to the return type and sign of the value), and the value of the macro ERANGE is stored in errno. 如果正确的值溢出且默认舍入生效(7.12.1),则返回正或负HUGE_VAL,HUGE_VALF或HUGE_VALL(根据返回类型和值的符号),并存储宏ERANGE的值在errno。 If the result underflows (7.12.1), the functions return a value whose magnitude is no greater than the smallest normalized positive number in the return type; 如果结果下溢(7.12.1),则函数返回一个值,该值的大小不大于返回类型中的最小归一化正数; whether errno acquires the value ERANGE is implementation-defined. 是否errno获取值ERANGE是实现定义的。

POSIX uses that convention : POSIX使用该约定

[ERANGE] [ERANGE]
The value to be returned would cause overflow or underflow. 要返回的值将导致溢出或下溢。

We can verify it is subnormal via fpclassify ( see it live ). 我们可以通过fpclassify验证它是否正常( 请参见实时 )。

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

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