为什么Clang std :: ostream写一个无法读取的std :: istream的双精度码? [英] Why does Clang std::ostream write a double that std::istream can't read?

查看:130
本文介绍了为什么Clang std :: ostream写一个无法读取的std :: istream的双精度码?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在使用一个应用程序,该应用程序使用 std :: stringstream 读取一个由 double s分隔的空格矩阵文本文件。该应用程序使用类似于以下代码:

I am using an application that uses std::stringstream to read a matrix of space separated doubles from a text file. 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..."};
}

相当标准的东西。我认真地写了一些代码来制作数据矩阵( data.dat ),对每个数据行使用以下方法:

Pretty standard stuff. I diligently wrote some code to make the data matrix (data.dat), using the following method for each data line:

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

i.e. using 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.

我写了下面的最小测试用例来显示行为:

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

我在LLVM 10.0.0(clang -1000.11.45.2):

I tested this code on 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,但得到的结果相同。

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.

使用GCC 8.2.0进行编译,代码按预期运行:

Compiling with GCC 8.2.0, the code works as I would expect:

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

为什么Clang和GCC有区别?这是一个clang漏洞吗?如果不是,是否应该使用C ++流编写可移植的浮点IO?

Why is there a difference between Clang and GCC? Is this a clang bug, and if not, how should one use C++ streams to write portable floating-point IO?

推荐答案

I如果我们阅读了 std :: stod的答案,则应该相信这里的clang符合标准。它说:

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:


C ++标准允许将字符串转换为 double 来报告下溢,如果结果是

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.

7.63918•10 -313 处于 double ,但在次标准范围内。 C ++标准说 stod 调用 strtod ,然后根据C标准定义 strtod 。 C标准表示 strtod 可能会下溢,并表示:如果数学结果的大小如此之小以至于无法表示数学结果,则结果会下溢,舍入错误,在指定类型的对象中。这很尴尬,但它指的是遇到次标准值时发生的舍入误差。 (次标准值比标准值要有更大的相对误差,因此,其四舍五入误差可能会说是非常大的。)

7.63918•10-313 is within the range of double, but it is in the subnormal range. The C++ standard says stod calls strtod and then defers to the C standard to define 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. (Subnormal values are subject to larger relative errors than normal values, so their rounding errors might be said to be extraordinary.)

因此,C ++标准允许C ++实现即使它们是可表示的,也会出现下溢的情况。

我们可以确认我们所依赖的是从[facet.num.get.virtuals] p3.3.4 中获得支持:

We can confirm we are relying on strtod from [facet.num.get.virtuals]p3.3.4:



  • 对于双精度值,该函数为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") ;
}

其结果如下:

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),则函数返回的值的大小不大于返回类型中最小的标准化正数;

POSIX使用该约定


[ERANGE]

要返回的值将导致上溢或下溢。

[ERANGE]
The value to be returned would cause overflow or underflow.

我们可以通过 fpclassify 实时查看)。

这篇关于为什么Clang std :: ostream写一个无法读取的std :: istream的双精度码?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆