Reputation: 525
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
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