简体   繁体   English

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

[英]std::stod throws out_of_range error for a string that should be valid

#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'
}

Run it here: https://onlinegdb.com/Sy1MT1iQM 在此处运行: https//onlinegdb.com/Sy1MT1iQM

"7.63918e-313" will result from serializing a double, but stod cannot deserialize it. “7.63918e-313”将序列化一个double,但是stod不能反序列化它。 What's going on here? 这里发生了什么? The smallest possible double is supposedly around 10^−324. 可能的最小双倍大约是10 ^ -324。

Is there a pair of functions somewhere in the stdlib that can reliably map doubles back and forth from stringification? stdlib中是否有一对函数能够可靠地从字符串化中来回映射双精度? Shouldn't there be? 不应该吗?

The plot thickens. 情节变粗。 We have two bizarre observations. 我们有两个奇怪的观察。

  • std::numeric_limits<double>::min() cannot be parsed by stod either. stod std::numeric_limits<double>::min()也无法被stod解析。

  • std::numeric_limits<double>::min() is not the minimum double. std::numeric_limits<double>::min()不是最小的double。 Our double is smaller, and I find we can get smaller doubles by simply dividing min, so it's not that my double is anomalous or anything https://onlinegdb.com/rJvilljQz 我们的双倍小,我发现我们可以通过简单地分割min来获得更小的双打,所以这不是我的双重异常或任何东西https://onlinegdb.com/rJvilljQz

I am very concerned. 我很担心。

Converting “7.63918e-313” 转换“7.63918e-313”

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

7.63918•10 -313 is within the range of double , but it is in the subnormal range. 7.63918•10 -313double精度范围内,但它处于低于正常范围。 The C++ standard says stod calls strtod and then defers to the C standard to define strtod . C ++标准说stod调用strtod ,然后按照C标准来定义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. C标准表明strtod可能会下溢,它表示“如果数学结果的大小如此之小,以至于数学结果无法表示,在指定类型的对象中没有非常的舍入误差,则结果会下溢。”是一种尴尬的措辞,但它指的是遇到次正常值时发生的舍入误差。 (Subnormal values are subject to larger relative errors than normal values, so their rounding errors might be said to be extraordinary.) (次正常值会受到比正常值更大的相对误差,因此可以说它们的舍入误差非常大。)

Thus, a C++ implementation is allowed by the C++ standard to underflow for subnormal values even though they are representable. 因此,C ++标准允许C ++实现对次正规值进行下溢,即使它们是可表示的。

Converting std::numeric_limits::min() 转换std :: numeric_limits :: min()

Regarding your observation that std::numeric_limits<double>::min() “cannot be parsed” either (I presume you mean it also reports underflow), this may be due to the fact that you converted std::numeric_limits<double>::min() to a string containing a decimal numeral, and that decimal numeral was not an exact representation of std::numeric_limits<double>::min() . 关于你的观察, std::numeric_limits<double>::min() “无法解析”(我认为你的意思是它也报告下溢),这可能是由于你转换了std::numeric_limits<double>::min()为包含十进制数字的字符串,该十进制数字不是std::numeric_limits<double>::min()的精确表示。 If it was rounded down, it is slightly less than min() , and hence it is also in the subnormal range. 如果它向下舍入,则略小于min() ,因此它也处于低于正常范围。 Thus, attempting to convert that decimal numeral back to a double may correctly report it is below the normal range. 因此,尝试将该十进制数字转换回double可以正确地报告它低于正常范围。

std::numeric_limits::min() is not the minimum double std :: numeric_limits :: min()不是最小的double

Regarding your observation that std::numeric_limits<double>::min() is not the minimum double , that is correct. 关于你观察到std::numeric_limits<double>::min()不是最小值double ,这是正确的。 std::numeric_limits<double>::min() is specified by the C++ standard to be the minimum positive normal value. std::numeric_limits<double>::min()由C ++标准指定为最小正正常值。 There may be subnormal values below it. 它下面可能有低于正常值。

Normal and subnormal values 正常和次正常值

For IEEE-754 64-bit binary floating-point, the normal range is from 2 -1022 to 2 1024 -2 971 . 对于IEEE-754 64位二进制浮点,正常范围为2 -1022到2 1024 -2 971 Within this range, every number is represented with a signficand (the fraction portion of the floating-point representation) that has a leading 1 bit followed by 52 additional bits, and so the error that occurs when rounding any real number in this range to the nearest representable value is at most 2 -53 times the position value of the leading bit. 在此范围内,每个数字都用signficand(浮点表示的小数部分)表示,其前导1位后跟52个附加位,因此将此范围内的任何实数舍入到最接近的可表示值至多是前导位的位置值的2-53倍。

In additional to this normal range, there is a subnormal range from 2 -1074 to 2 -1022 -2 -1074 . 除此正常范围外,还存在从2 -1074到2 -1022 -2 -1074的低于正常范围。 In this interval, the exponent part of the floating-point format has reached its smallest value and cannot be decreased any more. 在此间隔中,浮点格式的指数部分已达到其最小值,不能再降低。 To represent smaller and smaller numbers in this interval, the significand is reduced below the normal minimum of 1. It starts with a 0 and is followed by 52 additional bits. 为了在该间隔中表示越来越小的数字,有效数值减少到低于正常最小值1.它从0开始,然后是52个附加位。 In this interval, the error that occurs when rounding a real number to the nearest representable value may be larger than 2 -53 times the position value of the leading bit. 在该间隔中,将实数四舍五入到最接近的可表示值时发生的误差可能大于前导位的位置值的2-53倍。 Since the exponent cannot be decreased any further, numbers in this interval have increasing numbers of leading 0 bits as they get smaller and smaller. 由于指数不能再进一步降低,因此该区间中的数字随着它们变得越来越小而具有越来越多的前导0位。 Thus the relative errors involved with using these numbers grows. 因此,使用这些数字所涉及的相对误差增加了。

For whatever reasons, the C++ has said that implementations may report underflow in this interval. 无论出于何种原因,C ++都表示实现可能会在此时间间隔内报告下溢。 (The IEEE-754 standard defines underflow in complicated ways and also allows implementations some choices.) (IEEE-754标准以复杂的方式定义下溢,并允许实现一些选择。)

The value you are trying to read as a double, 7.63918e-313 , is smaller than the minimal value that is representable by a double on your architecture. 您尝试读取的值为7.63918e-313 ,小于体系结构中双7.63918e-313可表示的最小值。 On my architecture this is 2.22507e-308 and can be obtained using std::numeric_limits<double>::min() . 在我的架构上,这是2.22507e-308 ,可以使用std::numeric_limits<double>::min()

From the standard: [string.conversions] 来自标准: [string.conversions]

Throws out_of_range if strtof , strtod , or strtold sets errno to ERANGE or if the converted value is outside the range of representable values for the return type. 如果strtofstrtodstrtolderrnoERANGE或者转换后的值超出了返回类型的可表示值范围,则抛出strtof

That is to say, mapping doubles back on forth to string is possible using std::to_string and std::stod (or stringstreams) given that they are representable. 也就是说,使用std::to_stringstd::stod (或stringstreams)可以表示它们是可表示的,因此可以将double再次映射到字符串。


Instead you could use Boost.Lexical_Cast for parsing which does not seem to have these limitations. 相反,您可以使用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 实例

Another alternative is Boost.Spirit . 另一种选择是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 实例

According to the specification of strtod (which is referred from the specification of std::strod ), if the conversion underflows , the function is allowed, but not required to set errno to ERANGE . 根据strtod的规范(从std::strod的规范中std::strod ),如果转换下溢 ,则允许该函数,但不需要将errnoERANGE It is implementation-defined whether errno is set in this case. 在这种情况下是否errno是实现定义的。

In your example the conversion does underflow. 在您的示例中,转换会发生下溢。 Apparently, in your implementation strtod sets errno to ERANGE and std::stod unconditionally throws std::out_of_range when it sees that ERANGE in errno . 显然,在你的实现中, strtoderrnoERANGE并且std::stoderrno看到ERANGE时无条件地抛出std::out_of_range

In a different implementation strtod might not set errno to ERANGE in this case, and std::stod would not throw. 在另一种实现中,在这种情况下, strtod可能不会将errnoERANGE ,并且std::stod不会抛出。

Basically, C++ standard library is not guaranteed to properly convert underflowing values. 基本上,C ++标准库不能保证正确转换下溢值。 Even if strtod decides not to set errno , it is still allowed to return the smallest normalized value as a result in this case, instead of your original value. 即使strtod决定不设置errno ,在这种情况下仍然允许返回最小的标准化值,而不是原始值。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM