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

Converting “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.

7.63918•10-313 is within the range of double, but it is in the subnormal range. The C++ standard says stod calls strtod and then defers to the C standard to define 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. (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.

Converting 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(). If it was rounded down, it is slightly less than min(), and hence it is also in the subnormal range. Thus, attempting to convert that decimal numeral back to a double may correctly report it is below the normal range.

std::numeric_limits::min() is not the minimum double

Regarding your observation that std::numeric_limits<double>::min() is not the minimum double, that is correct. std::numeric_limits<double>::min() is specified by the C++ standard to be the minimum positive normal value. 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 21024-2971. 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.

In additional to this normal range, there is a subnormal range from 2-1074 to 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. 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. 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. 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. (The IEEE-754 standard defines underflow in complicated ways and also allows implementations some choices.)

Leave a Comment