markand
markand

Reputation: 525

Converting strings to integers the right way

I would like to verify that strings are valid integers. I could just use 64 bits integers and functions like std::stoull or std::stoll but I can't verify if the value is between a given range.

Example, I want an integer between 0 and UINT64_MAX. If the string is "-1", I can convert it to a long long and I'll know it's negative. However, if the string is above the maximum of signed long long (usually INT64_MAX), the overflow makes it negative again while the string was a positive integer.

A friend first advised me to check for a minus sign but I didn't like this solution, then I thought about using a double, however I know that comparisons using doubles can be tricky so that's why I'm unsure about this solution.

template <typename T>
inline bool is_between(const string &str,
                       T min = numeric_limits<T>::min(),
                       T max = numeric_limits<T>::max()) noexcept
{
    try {
        double value = stod(str);

        if (value < min || value > max)
            return false;
    } catch (...) {
        return false;
    }

    return true;
}

Is this function safe? Because I'm afraid that a string of "1" could return a double value of "0.9999999999", then it would not fit in an interval between 1 and 100.

Then we can use the function like this:

cout << is_between<uint16_t>("0") << endl;
cout << is_between<uint16_t>("65535") << endl;
cout << is_between<uint16_t>("65535", 0, 2000) << endl;
cout << is_between<uint16_t>("-1") << endl;
cout << is_between<uint16_t>("800000") << endl;

Upvotes: 3

Views: 214

Answers (1)

Ari Hietanen
Ari Hietanen

Reputation: 1769

Your function is safe from the error you describe because small enough integer numbers can be represented exactly with floating point numbers. Hence, the conversion of 1 cannot be 0.99999 because they are different numbers if 0.99999 can be represented in doubles. The conversion of 0.99999 to 1 is possible.

However, if you have large enough number, the limit checking does not work, because 64bit double precision has less bits (usually 54, but this is not defined in standard) for the fraction part than a 64bit integer. E.g., output of a following program is 1 and not 0 as if it would be with a higher precision numbers.

#include <iostream>
#include <string>

int main(){
  long long v=(1l << 54) +1;
  std::cout << v-static_cast<long long>(std::stod(std::to_string(v))) << std::endl;
}

If you really need numbers close to UINT64_MAX it's better to use some library like Boost multiprecision. It has an arbitrary precision integer cpp_int, which can be constructed from a std::string.

Upvotes: 2

Related Questions