std::stod 为应该有效的字符串抛出out_of_range错误


#include <iostream>
#include <cmath>
#include <sstream>
using namespace std;
int main(){
stringstream ss;
double ad = 7.63918e-313;
ss << ad;
cout<<ss.str()<<endl;
//you will see that the above double is valid, and maps to the specified string
//but stod cannot map it back
stod("7.63918e-313");
//terminate called after throwing an instance of 'std::out_of_range'
}

在此处运行它:https://onlinegdb.com/Sy1MT1iQM

"7.63918e-313"将由序列化双精度值产生,但 STOD 无法反序列化它。这是怎么回事?最小的可能双精度值大约是 10^−324。

stdlib 中的某个地方是否有一对函数可以可靠地从字符串化中来回映射双精度?不应该有吗?

剧情变厚了。我们有两个奇怪的观察结果。

  • std::numeric_limits<double>::min()也不能被 stod 解析。

  • std::numeric_limits<double>::min()不是最低双倍。我们的双打更小,我发现我们可以通过简单地除以最小值来得到更小的双打,所以这并不是说我的双打是异常的或 https://onlinegdb.com/rJvilljQz

我非常担心。

转换 "7.63918e-313">

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

7.63918•10-313double范围内,但在次正常范围内。C++标准说stod调用strtod然后遵从C标准来定义strtod。C 标准指出strtod可能会下溢,关于这一点,它说"如果数学结果的大小太小,以至于数学结果无法在指定类型的对象中表示,如果没有异常的舍入误差,则结果下溢。这是尴尬的措辞,但它指的是遇到低于正常值时发生的舍入误差。(次正态值的相对误差大于正常值,因此它们的舍入误差可能非同寻常。

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

转换标准::numeric_limits::分钟()

关于您观察到std::numeric_limits<double>::min()"无法解析"(我猜您的意思是它也报告下溢),这可能是由于您将std::numeric_limits<double>::min()转换为包含十进制数字的字符串,并且十进制数字不是std::numeric_limits<double>::min()的精确表示。如果四舍五入,它略小于min(),因此它也在低于正常范围内。因此,尝试将该十进制数字转换回double可能会正确报告它低于正常范围。

std::numeric_limits::min() 不是最小双精度

关于您观察到std::numeric_limits<double>::min()不是最低double,这是正确的。std::numeric_limits<double>::min()由C++标准指定为最小正正常值。它下面可能有次正常值。

正常

值和次正常值

对于 IEEE-754 64 位二进制浮点,正常范围是从2-1022到 21024-2971。在此范围内,每个数字都用一个有效数(浮点表示的分数部分)表示,该有效数具有前导 1 位后跟 52 个附加位,因此将此范围内的任何实数舍入到最接近的可表示值时发生的误差最多是前导位位置值的2-53倍。

除了这个正常范围之外,还有一个从2-1074到 2-1022-2-1074 的次正常范围。在此区间内,浮点格式的指数部分已达到其最小值,不能再减小。为了表示此区间中越来越小的数字,有效数将降低到正常最小值 1 以下。它以 0 开头,后跟 52 个附加位。在此区间内,将实数舍入到最接近的可表示值时发生的误差可能大于前导位位置值的2-53倍。由于指数不能进一步减小,因此此区间中的数字随着越来越小,前导 0 位的数量不断增加。因此,使用这些数字所涉及的相对误差会增加。

无论出于何种原因,C++都表示,实现可能会在此间隔内报告下溢。(IEEE-754标准以复杂的方式定义了下溢,并且还允许实现一些选择。

您尝试读取为双精度值的值7.63918e-313小于体系结构上双精度值可表示的最小值。 在我的架构上,这很2.22507e-308,可以使用std::numeric_limits<double>::min()获得。

从标准: [字符串转换]

如果strtofstrtodstrtolderrno设置为ERANGE,或者转换后的值超出返回类型的可表示值范围,则引发out_of_range。

也就是说,使用std::to_stringstd::stod(或字符串流)可以将双倍映射回字符串,因为它们是可表示的。


相反,您可以使用Boost.Lexical_Cast进行解析,这似乎没有这些限制。

#include <iostream>
#include <boost/lexical_cast.hpp>
int main() {
double d = boost::lexical_cast<double>("7.63918e-313");
std::cout << d << "n";
}

现场示例

另一种选择是Boost.Spirit。

#include <iostream>
#include <boost/spirit/home/x3.hpp>
namespace x3 = boost::spirit::x3;
int main() {
double d = 0;
std::string input = "7.63918e-313";
x3::parse(input.begin(), input.end(), x3::double_, d);
std::cout << d << "n";
}

现场示例

根据strtod的规范(从std::strod的规范中引用),如果转换下溢,则允许该函数,但不需要将errno设置为ERANGE。在这种情况下是否设置errno是实现定义的。

在您的示例中,转换确实下溢。显然,在您的实现中,strtoderrno设置为ERANGEstd::stod在看到该ERANGE时无条件抛出std::out_of_rangeerrno

在不同的实现中,在这种情况下,strtod可能不会将errno设置为ERANGEstd::stod也不会抛出。

基本上,C++标准库不能保证正确转换下溢值。即使strtod决定不设置errno,在这种情况下,仍然允许返回最小的规范化值,而不是原始值。

相关内容

最新更新