对于应该有效的字符串,std :: stod会抛出out_of_range错误

时间:2018-01-03 23:55:35

标签: c++ floating-point

#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”将序列化一个double,但stod不能反序列化它。这里发生了什么?可能的最小双倍大约是10 ^ -324。

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

情节变浓。我们有两个奇怪的观察结果。

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

  • std::numeric_limits<double>::min()不是最小双倍。我们的双倍小,我发现我们可以通过简单地分割min来获得更小的双打,所以不是我的双重是异常的或任何https://onlinegdb.com/rJvilljQz

我很担心。

3 个答案:

答案 0 :(得分:6)

转换“7.63918e-313”

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

7.63918•10 -313 double范围内,但它处于低于正常范围。 C ++标准说stod调用strtod,然后按照C标准来定义strtod。 C标准表明strtod可能下溢,其中所说的“如果数学结果的大小如此之小以至于数学结果无法表示,而没有特殊的舍入误差,则在指定的对象中,结果会下溢“这是一种尴尬的措辞,但它指的是遇到次正常值时出现的舍入误差。 (低于正常值会出现较大的相对误差,因此可能会说它们的舍入误差非常大。)

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

转换std :: numeric_limits :: min()

关于你无法解析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的观察,这是正确的。 C ++标准将std::numeric_limits<double>::min()指定为最小正正常值。它下面可能有低于正常值。

正常和次正常值

对于IEEE-754 64位二进制浮点,正常范围从2 -1022 到2 1024 -2 971 。在此范围内,每个数字都用signficand(浮点表示的小数部分)表示,其前导1位后跟52个附加位,因此将此范围内的任何实数舍入到最接近的可表示值最多为2 -53 乘以前导位的位置值。

除此正常范围外,还存在从2 -1074 到2 -1022 -2 -1074 的低于正常范围。在此间隔中,浮点格式的指数部分已达到其最小值,不能再降低。为了在该间隔中表示越来越小的数字,有效数值减少到低于正常最小值1.它从0开始,然后是52个附加位。在该间隔中,将实数四舍五入到最接近的可表示值时发生的误差可能大于前导位的位置值的2 -53 倍。由于指数不能再进一步降低,因此该区间中的数字随着它们变得越来越小而具有越来越多的前导0位。因此,使用这些数字所涉及的相对误差会增加。

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

答案 1 :(得分:2)

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

来自标准:[string.conversions]

  

如果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";
}

Live example

另一种选择是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";
}

Live example

答案 2 :(得分:2)

根据strtod的规范(参考std::strod的规范),如果转换下溢,则允许该功能,但不需要设置errnoERANGE。它是实现定义的,在这种情况下是否设置了errno

在您的示例中,转换会发生下溢。显然,在strtod errno ERANGE std::stod std::out_of_range ERANGE errno strtod errno ERANGE

在其他实现中,std::stod在这种情况下可能不会将strtod设置为errno,而{{1}}也不会抛出。{/ p>

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