Question

In C++, we can store denorm numbers into variables without problems:

double x = std::numeric_limits<double>::denorm_min();

Then, we can print this variable without problems:

std::cout<<std::setprecision(std::numeric_limits<double>::max_digits10)
std::cout<<std::scientific;
std::cout<<x;
std::cout<<std::endl;

And it will print:

4.94065645841246544e-324

But a problem occurs when one tries to parse this number. Imagine that this number is stored inside a file, and read as a string. The problem is that:

std::string str = "4.94065645841246544e-324";
double x = std::stod(str);

will throw an std::out_of_range exception.

So my question is: how to convert a denorm value stored in a string?

Was it helpful?

Solution

I'm not sure I have understood the problem, but using std::istringstream like this:

std::string str = "4.94065645841246544e-324";
double x;
std::istringstream iss(str);
iss >> x;
std::cout << std::setprecision(std::numeric_limits<double>::max_digits10);
std::cout << std::scientific;
std::cout << x << std::endl;

...gives me:

4.94065645841246544e-324

OTHER TIPS

Apparently, you can use the strtod (or the older atof) interface from cstdlib. I doubt whether this is guaranteed or portable.

I'm not sure if it will make a difference, but you are actually printing:

(std::numeric_limits<double>::max_digits10 + 1) = 18 decimal digits.

e.g., an IEEE-754 64-bit double with round-trip precision is "1.16" in scientific notation. Perhaps this is introducing some ULP / rounding that interferes with the conversion?

The problem with denormals and std::stod is that the latter is defined in terms of std::strtod, which may set errno=ERANGE on underflow (it's implementation-defined whether it'll do, and in glibc it does). As reminded by gcc developers, in such a case std::stod is defined by the standard to throw std::out_of_range.

So your proper workaround is to use std::strtod directly, ignoring ERANGE when the value it returns is finite and nonzero, like here:

double stringToDouble(const char* str, std::size_t* pos=nullptr)
{
    errno=0;
    char* end;
    const auto x=std::strtod(str, &end);
    if(errno==ERANGE)
    {
        // Ignore it for denormals
        if(x!=0 && x>-HUGE_VAL && x<HUGE_VAL)
            return x;
        throw std::out_of_range("strtod: ERANGE");
    }
    else if(errno)
        throw std::invalid_argument("strtod failed");
    if(pos)
        *pos=end-str;
    return x;
}

Note that, unlike std::istringstream approach suggested in another answer, this will work for hexfloats too.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top