解析和转换去规范数字

Parse and convert denorm numbers?

本文关键字:数字 范数字 转换      更新时间:2023-10-16

在C++中,我们可以毫无问题地将无规范数存储到变量中:

double x = std::numeric_limits<double>::denorm_min();

然后,我们可以毫无问题地打印这个变量:

std::cout<<std::setprecision(std::numeric_limits<double>::max_digits10)
std::cout<<std::scientific;
std::cout<<x;
std::cout<<std::endl;

它将打印:

4.94065645841246544e-324

但是当尝试解析此数字时会出现问题。想象一下,这个数字存储在一个文件中,并作为一个字符串读取。问题是:

std::string str = "4.94065645841246544e-324";
double x = std::stod(str);

将引发std::out_of_range异常。

所以我的问题是:如何转换存储在字符串中的 denorm 值?

我不确定我是否理解了这个问题,但使用这样的std::istringstream

std::string str = "4.94065645841246544e-324";
double x;
std::istringstream iss(str);
iss >> x;
std::cout << std::setprecision(std::numeric_limits<double>::max_digits10);
std::cout << std::scientific;
std::cout << x << std::endl;

。给我:

4.94065645841246544e-324

显然,您可以使用cstdlib中的strtod(或较旧的atof)界面。我怀疑这是否是保证的或便携式的。

我不确定它是否会有所作为,但您实际上正在打印:

(std::numeric_limits<double>::max_digits10 + 1) = 18十进制数字。

例如,具有往返精度的 IEEE-754 64 位双精度以科学记数法"1.16"。也许这是引入了一些干扰转换的 ULP/舍入?

非正规和std::stod的问题在于后者是根据std::strtod定义的,这可能会在下溢上设置errno=ERANGE(它是由实现定义的,它是否这样做,并且在glibc中它确实如此)。正如 gcc 开发人员提醒的那样,在这种情况下,std::stod由抛出std::out_of_range的标准定义。

因此,正确的解决方法是直接使用 std::strtod,当它返回的值有限且非零时忽略ERANGE,如下所示:

double stringToDouble(const char* str, std::size_t* pos=nullptr)
{
    errno=0;
    char* end;
    const auto x=std::strtod(str, &end);
    if(errno==ERANGE)
    {
        // Ignore it for denormals
        if(x!=0 && x>-HUGE_VAL && x<HUGE_VAL)
            return x;
        throw std::out_of_range("strtod: ERANGE");
    }
    else if(errno)
        throw std::invalid_argument("strtod failed");
    if(pos)
        *pos=end-str;
    return x;
}

请注意,与另一个答案中建议std::istringstream方法不同,这也适用于十六进制浮点数。