Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

"7.63918e-313" will result from serializing a double, but stod cannot deserialize it. What's going on here? The smallest possible double is supposedly around 10^−324.

Is there a pair of functions somewhere in the stdlib that can reliably map doubles back and forth from stringification? Shouldn't there be?

The plot thickens. We have two bizarre observations.

  • std::numeric_limits<double>::min() cannot be parsed by stod either.

  • std::numeric_limits<double>::min() is not the minimum 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

I am very concerned.

like image 886
mako Avatar asked Dec 24 '22 11:12

mako


1 Answers

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.)

like image 134
Eric Postpischil Avatar answered Jan 19 '23 00:01

Eric Postpischil