StringUtil: Make TryParse of floats handle comma and dot decimal separators.

This commit is contained in:
Jordan Woyak 2019-12-30 17:28:35 -06:00
parent 7a6a4510f6
commit 0e8d4cb6ac
2 changed files with 52 additions and 56 deletions

View file

@ -235,51 +235,6 @@ std::string_view StripQuotes(std::string_view s)
return s;
}
bool TryParse(const std::string& str, u16* const output)
{
u64 value;
if (!TryParse(str, &value))
return false;
if (value >= 0x10000ull && value <= 0xFFFFFFFFFFFF0000ull)
return false;
*output = static_cast<u16>(value);
return true;
}
bool TryParse(const std::string& str, u32* const output)
{
u64 value;
if (!TryParse(str, &value))
return false;
if (value >= 0x100000000ull && value <= 0xFFFFFFFF00000000ull)
return false;
*output = static_cast<u32>(value);
return true;
}
bool TryParse(const std::string& str, u64* const output)
{
char* end_ptr = nullptr;
// Set errno to a clean slate
errno = 0;
u64 value = strtoull(str.c_str(), &end_ptr, 0);
if (end_ptr == nullptr || *end_ptr != '\0')
return false;
if (errno == ERANGE)
return false;
*output = value;
return true;
}
bool TryParse(const std::string& str, bool* const output)
{
float value;

View file

@ -6,7 +6,9 @@
#include <cstdarg>
#include <cstddef>
#include <cstdlib>
#include <iomanip>
#include <locale>
#include <sstream>
#include <string>
#include <type_traits>
@ -47,20 +49,60 @@ std::string ArrayToString(const u8* data, u32 size, int line_len = 20, bool spac
std::string_view StripSpaces(std::string_view s);
std::string_view StripQuotes(std::string_view s);
std::string ReplaceAll(std::string result, std::string_view src, std::string_view dest);
bool TryParse(const std::string& str, bool* output);
bool TryParse(const std::string& str, u16* output);
bool TryParse(const std::string& str, u32* output);
bool TryParse(const std::string& str, u64* output);
template <typename N>
static bool TryParse(const std::string& str, N* const output)
template <typename T, std::enable_if_t<std::is_integral_v<T> || std::is_enum_v<T>>* = nullptr>
bool TryParse(const std::string& str, T* output)
{
std::istringstream iss(str);
// is this right? not doing this breaks reading floats on locales that use different decimal
// separators
iss.imbue(std::locale("C"));
char* end_ptr = nullptr;
N tmp;
// Set errno to a clean slate.
errno = 0;
// Read a u64 for unsigned types and s64 otherwise.
using ReadType = std::conditional_t<std::is_unsigned_v<T>, u64, s64>;
ReadType value;
if constexpr (std::is_unsigned_v<T>)
value = std::strtoull(str.c_str(), &end_ptr, 0);
else
value = std::strtoll(str.c_str(), &end_ptr, 0);
// Fail if the end of the string wasn't reached.
if (end_ptr == nullptr || *end_ptr != '\0')
return false;
// Fail if the value was out of 64-bit range.
if (errno == ERANGE)
return false;
using LimitsType = typename std::conditional_t<std::is_enum_v<T>, std::underlying_type<T>,
std::common_type<T>>::type;
// Fail if outside numeric limits.
if (value < std::numeric_limits<LimitsType>::min() ||
value > std::numeric_limits<LimitsType>::max())
{
return false;
}
*output = static_cast<T>(value);
return true;
}
template <typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
bool TryParse(std::string str, T* const output)
{
// Replace commas with dots.
std::istringstream iss(ReplaceAll(std::move(str), ",", "."));
// Use "classic" locale to force a "dot" decimal separator.
iss.imbue(std::locale::classic());
T tmp;
// Succeed if a value was read and the entire string was used.
if (iss >> tmp && iss.eof())
{
*output = tmp;
@ -118,7 +160,6 @@ bool SplitPath(std::string_view full_path, std::string* path, std::string* filen
void BuildCompleteFilename(std::string& complete_filename, std::string_view path,
std::string_view filename);
std::string ReplaceAll(std::string result, std::string_view src, std::string_view dest);
bool StringBeginsWith(std::string_view str, std::string_view begin);
bool StringEndsWith(std::string_view str, std::string_view end);