簡體   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