Consider the following code
#include <iostream>
int main()
{
const std::string s("5.87747175e-39");
float f = std::stof(s);
std::cout << s << " - " << f << std::endl;
return 0;
}
When compiled with g++ and executed, this throws std::out_of_range
. However, as you can see here, 5.87747175e-39 is a valid float. How can this be?
In case it makes a difference, I am on amd64 Debian 12 which uses g++ version 12.2
For what it's worth, Python considers this a valid float
python3 -c 'print(float("5.87747175e-39"))'
Also, it appears that this problem extends to all denormalized floats. For example, "1.1754942e-38" is the largest valid IEEE-754 denormalized float, but stof() considers it an error. The smallest normalized float is "1.1754944e-38" which is what is reported as FLT_MIN
Consider the following code
#include <iostream>
int main()
{
const std::string s("5.87747175e-39");
float f = std::stof(s);
std::cout << s << " - " << f << std::endl;
return 0;
}
When compiled with g++ and executed, this throws std::out_of_range
. However, as you can see here, 5.87747175e-39 is a valid float. How can this be?
In case it makes a difference, I am on amd64 Debian 12 which uses g++ version 12.2
For what it's worth, Python considers this a valid float
python3 -c 'print(float("5.87747175e-39"))'
Also, it appears that this problem extends to all denormalized floats. For example, "1.1754942e-38" is the largest valid IEEE-754 denormalized float, but stof() considers it an error. The smallest normalized float is "1.1754944e-38" which is what is reported as FLT_MIN
Looking at GCC's implementation of std::stof
they throw this error if the float is smaller than FLT_MIN
, not FLT_TRUE_MIN
.
The reason for this is probably because FLT_TRUE_MIN
was introduced in C++17, while std::stof
in C++11.
Underflow may be reported for results in the subnormal range because such results may have larger than normal errors. The C++ standard allows conversions of strings to float
to report underflow if the result is in the subnormal range.
5.87747175•10−39 is within the range of float
, but it is in the subnormal range (discussed below). The C++ standard says stof
calls strtof
(in C++ 2020 draft N4849 21.3.4 [string.conversions] 3) and then defers to the C standard to define strtof
. The C standard indicates that strtof
may underflow. C 2018 7.12.1 6 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.” “Extraordinary roundoff error” refers to the rounding errors that occur when subnormal values are encountered. (Subnormal values have fewer significant significand bits and so 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.
This question is essentially a duplicate of this question except that one is for double
and this one is for float
.
For IEEE-754 32-bit binary floating-point, the normal range is from 2−126 to 2128−2104. 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 23 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−24 times the position value of the leading bit.
In additional to this normal range, there is a subnormal range from 2−149 to 2−126−2−149. 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 23 additional bits. In this interval, the error that occurs when rounding a real number to the nearest representable value may be larger than 2−24 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.
According to std::numeric_limits::min
#include <limits>
#include <iostream>
int main() {
std::cout << std::numeric_limits<float>::min();
}
the output for float
is 1.17549e-38
. This is consistent with the documented value of FLT_MIN
.
float
is usually implemented (in C) asdouble
which is why your value is representable. – Cory Kramer Commented Mar 3 at 18:57std::stof
is implemented in terms of the C languagestrtof
function. And the C++ equivalentstd::from_chars
does not have this issue. – Drew Dormann Commented Mar 3 at 19:39stod
instead ofstof
. – Drew Dormann Commented Mar 4 at 14:01