diff --git a/src/hz/examples/CMakeLists.txt b/src/hz/examples/CMakeLists.txt index 4b5af5a..90cd907 100644 --- a/src/hz/examples/CMakeLists.txt +++ b/src/hz/examples/CMakeLists.txt @@ -18,12 +18,3 @@ target_sources(example_string_algo PRIVATE target_link_libraries(example_string_algo PRIVATE hz ) - - -add_executable(example_string_num) -target_sources(example_string_num PRIVATE - example_string_num.cpp -) -target_link_libraries(example_string_num PRIVATE - hz -) diff --git a/src/hz/examples/example_string_num.cpp b/src/hz/examples/example_string_num.cpp deleted file mode 100644 index 5994f36..0000000 --- a/src/hz/examples/example_string_num.cpp +++ /dev/null @@ -1,194 +0,0 @@ -/****************************************************************************** -License: BSD Zero Clause License -Copyright: - (C) 2008 - 2021 Alexander Shaduri -******************************************************************************/ -/// \file -/// \author Alexander Shaduri -/// \ingroup hz_examples -/// \weakgroup hz_examples -/// @{ - -// disable libdebug, we don't link to it -#undef HZ_USE_LIBDEBUG -#define HZ_USE_LIBDEBUG 0 -// enable libdebug emulation through std::cerr -#undef HZ_EMULATE_LIBDEBUG -#define HZ_EMULATE_LIBDEBUG 1 - -// The first header should be then one we're testing, to avoid missing -// header pitfalls. -#include "hz/string_num.h" - -#include -#include -#include - -#include "hz/main_tools.h" - - - -/// Main function for the test -int main() -{ - return hz::main_exception_wrapper([]() - { - using namespace hz; - - // int i = 0; - // double d = 0; - - // std::cerr << (string_is_numeric_nolocale("-1", i) ? "true" : "false"); - // std::cerr << ", parsed: i=" << i << "\n"; - - - { - int16_t int16_ = 0; - std::cerr << (string_is_numeric_nolocale("32768", int16_) ? "true" : "false"); // overflow, false - std::cerr << "\n"; - - uint16_t uint16_ = 0; - std::cerr << (string_is_numeric_nolocale("65535", uint16_) ? "true" : "false"); // true - std::cerr << "\n"; - - int32_t int32_ = 0; - std::cerr << (string_is_numeric_nolocale(" 1.33", int32_) ? "true" : "false"); // strict, false - std::cerr << "\n"; - - int32_t int32_2 = 0; - std::cerr << (string_is_numeric_nolocale("1.33", int32_2) ? "true" : "false"); // strict, false - std::cerr << "\n"; - - uint32_t uint32_ = 0; - std::cerr << (string_is_numeric_nolocale("-1", uint32_) ? "true" : "false"); // underflow, false - std::cerr << "\n"; - - int64_t int64_ = 0; - std::cerr << (string_is_numeric_nolocale("-1", int64_) ? "true" : "false"); // true - std::cerr << "\n"; - - uint64_t uint64_ = 0; - std::cerr << (string_is_numeric_nolocale("1", uint64_) ? "true" : "false"); // true - std::cerr << "\n"; - - int int_ = 0; - std::cerr << (string_is_numeric_nolocale("-1", int_) ? "true" : "false"); // true - std::cerr << "\n"; - - unsigned int uint_ = 0; - std::cerr << (string_is_numeric_nolocale("-1", uint_) ? "true" : "false"); // underflow, false - std::cerr << "\n"; - - long long_int_ = 0; - std::cerr << (string_is_numeric_nolocale("-1", long_int_) ? "true" : "false"); // true - std::cerr << "\n"; - - unsigned long long_uint_ = 0; - std::cerr << (string_is_numeric_nolocale("-1", long_uint_) ? "true" : "false"); // underflow, false - std::cerr << "\n"; - - char char_ = 0; - std::cerr << (string_is_numeric_nolocale("4", char_) ? "true" : "false"); // true - std::cerr << "\n"; - - unsigned char uchar_ = 0; - std::cerr << (string_is_numeric_nolocale("315", uchar_) ? "true" : "false"); // overflow, false - std::cerr << "\n"; - - signed char schar_2 = 0; - std::cerr << (string_is_numeric_nolocale("128", schar_2) ? "true" : "false"); // overflow, false - std::cerr << "\n"; - - signed char schar_3 = 0; - std::cerr << (string_is_numeric_nolocale("-128", schar_3) ? "true" : "false"); // true - std::cerr << "\n"; - - unsigned char uchar_2 = 0; - std::cerr << (string_is_numeric_nolocale("128", uchar_2) ? "true" : "false"); // true - std::cerr << "\n"; - - bool b = false; - std::cerr << (string_is_numeric_nolocale("true", b) ? "true" : "false"); // true - std::cerr << "\n"; - - } - - - // std::cerr << (string_is_numeric_nolocale("-1.3", i) ? "true" : "false"); - // std::cerr << ", parsed: i=" << i << "\n"; - - - { - double d = 0; - - std::cerr << (string_is_numeric_nolocale("-1.3", d) ? "true" : "false"); - std::cerr << ", parsed: d=" << d << "\n"; - - std::cerr << (string_is_numeric_nolocale(" inf", d) ? "true" : "false"); - std::cerr << ", parsed: d=" << d << "\n"; - - std::cerr << (string_is_numeric_nolocale("infinity", d) ? "true" : "false"); - std::cerr << ", parsed: d=" << d << "\n"; - - std::cerr << (string_is_numeric_nolocale("NAn", d) ? "true" : "false"); - std::cerr << ", parsed: d=" << d << "\n"; - - std::cerr << (string_is_numeric_nolocale("3.e+4", d) ? "true" : "false"); - std::cerr << ", parsed: d=" << d << "\n"; - - std::cerr << (string_is_numeric_nolocale("123 ", d) ? "true" : "false"); - std::cerr << ", parsed: d=" << d << "\n"; - - std::cerr << (string_is_numeric_nolocale("e+3", d) ? "true" : "false"); - std::cerr << ", parsed: d=" << d << "\n"; - } - - - - { - - // Note: suffixes are case-insensitive. - // Long int and long double are differentiated by '.' in constant. - - std::cerr << number_to_string_nolocale(true) << "\n"; // bool - std::cerr << number_to_string_nolocale('a') << "\n"; // char - std::cerr << number_to_string_nolocale(1.L) << "\n"; // long double - std::cerr << number_to_string_nolocale(2L) << "\n"; // long int - std::cerr << number_to_string_nolocale(6) << "\n"; // int - - std::cerr << number_to_string_nolocale(3LL) << "\n"; // long long int - std::cerr << number_to_string_nolocale(4ULL) << "\n"; // unsigned long long int - - std::cerr << number_to_string_nolocale(5.f) << "\n"; // float - std::cerr << number_to_string_nolocale(1.33) << "\n"; // double - std::cerr << number_to_string_nolocale(2.123456789123456789123456789L) << "\n"; - - std::cerr << number_to_string_nolocale(std::numeric_limits::quiet_NaN()) << "\n"; - std::cerr << number_to_string_nolocale(std::numeric_limits::signaling_NaN()) << "\n"; - std::cerr << number_to_string_nolocale(std::numeric_limits::infinity()) << "\n"; - - std::cerr << number_to_string_nolocale(uint16_t(0x1133), 16) << "\n"; - std::cerr << number_to_string_nolocale(uint32_t(0x00001234), 16) << "\n"; - std::cerr << number_to_string_nolocale(uint64_t(0x00001234), 16) << "\n"; - std::cerr << number_to_string_nolocale(uint16_t(0), 16) << "\n"; - - std::cerr << number_to_string_nolocale(uint16_t(0xffff), 8) << "\n"; - std::cerr << number_to_string_nolocale(uint16_t(0), 8) << "\n"; - - // std::cerr << number_to_string_nolocale("abc", 8) << "\n"; - - // std::cerr << number_to_string_nolocale(uint16_t(0xffff), 2) << "\n"; // won't work - // std::cerr << number_to_string_nolocale(uint16_t(0), 2) << "\n"; - - } - - return EXIT_SUCCESS; - }); -} - - - - - - -/// @} diff --git a/src/hz/string_num.h b/src/hz/string_num.h index 598d421..0f1c64d 100644 --- a/src/hz/string_num.h +++ b/src/hz/string_num.h @@ -122,6 +122,18 @@ namespace hz { namespace internal { + /// If a string starts with '-', stoul() and stoull() use unsigned wraparound rules + /// for the return value, not returning "out of range". Detect this condition. + inline bool string_starts_with_minus(const std::string& s, const std::locale& loc) + { + auto first_symbol = std::find_if(s.begin(), s.end(), [&](char c) { + return !std::isspace(c, loc); + }); + return first_symbol != s.end() && *first_symbol == '-'; + } + + + // Version for integral / floating point types template bool string_is_numeric_impl_global_locale(const std::string& s, T& number, bool strict, [[maybe_unused]] int base, const std::locale& loc = std::locale()) @@ -135,9 +147,14 @@ namespace internal { T value = T(); try { - if constexpr(std::is_same_v || std::is_same_v || std::is_same_v - || std::is_same_v || std::is_same_v || std::is_same_v - || std::is_same_v || std::is_same_v) { + if constexpr(std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v + || std::is_same_v) { int tmp = std::stoi(s, &num_read, base); if (tmp != static_cast(tmp)) { return false; // out of range @@ -147,14 +164,21 @@ namespace internal { value = std::stol(s, &num_read, base); } else if constexpr(std::is_same_v) { value = std::stoll(s, &num_read, base); - } else if constexpr(std::is_same_v || std::is_same_v || - std::is_same_v) { + } else if constexpr(std::is_same_v + || std::is_same_v + || std::is_same_v) { + if (string_starts_with_minus(s, loc)) { + return false; // "out of range" for unsigned + } unsigned long tmp = std::stoul(s, &num_read, base); if (tmp != static_cast(tmp)) { - return false; // out of range + return false; // out of range for smaller type } value = static_cast(tmp); } else if constexpr(std::is_same_v) { + if (string_starts_with_minus(s, loc)) { + return false; // "out of range" for unsigned + } value = std::stoull(s, &num_read, base); } else if constexpr(std::is_same_v) { value = std::stof(s, &num_read); diff --git a/src/hz/tests/CMakeLists.txt b/src/hz/tests/CMakeLists.txt index 2f6c498..ed1c78c 100644 --- a/src/hz/tests/CMakeLists.txt +++ b/src/hz/tests/CMakeLists.txt @@ -15,6 +15,7 @@ endif() add_library(hz_tests OBJECT) target_sources(hz_tests PRIVATE test_format_unit.cpp + test_string_num.cpp ) target_link_libraries(hz_tests PRIVATE hz diff --git a/src/hz/tests/test_string_num.cpp b/src/hz/tests/test_string_num.cpp new file mode 100644 index 0000000..eb4ed84 --- /dev/null +++ b/src/hz/tests/test_string_num.cpp @@ -0,0 +1,220 @@ +/****************************************************************************** +License: BSD Zero Clause License +Copyright: + (C) 2008 - 2022 Alexander Shaduri +******************************************************************************/ +/// \file +/// \author Alexander Shaduri +/// \ingroup hz_tests +/// \weakgroup hz_tests +/// @{ + +// Catch2 v3 +//#include "catch2/catch_test_macros.hpp" + +// Catch2 v2 +#include "catch2/catch.hpp" + +// disable libdebug, we don't link to it +#undef HZ_USE_LIBDEBUG +#define HZ_USE_LIBDEBUG 0 +// enable libdebug emulation through std::cerr +#undef HZ_EMULATE_LIBDEBUG +#define HZ_EMULATE_LIBDEBUG 1 + +// The first header should be then one we're testing, to avoid missing +// header pitfalls. +#include "hz/string_num.h" + +#include +#include + +#include "hz/main_tools.h" + + + +TEST_CASE("NumericStrings", "[hz][parser]") +{ + using namespace hz; + + { + int i = 10; + REQUIRE(string_is_numeric_nolocale("-1", i) == true); + REQUIRE(i == -1); + } + { + int i = 10; + REQUIRE(string_is_numeric_nolocale("-1.3", i) == false); + REQUIRE(i == 10); + } + { + int16_t int16_ = 10; + REQUIRE(string_is_numeric_nolocale("32768", int16_) == false); // overflow, false + REQUIRE(int16_ == 10); + + uint16_t uint16_ = 10; + REQUIRE(string_is_numeric_nolocale("65535", uint16_) == true); + REQUIRE(uint16_ == 65535); + + int32_t int32_ = 10; + REQUIRE(string_is_numeric_nolocale(" 1.33", int32_) == false); // strict, false + REQUIRE(int32_ == 10); + + int32_t int32_2 = 10; + REQUIRE(string_is_numeric_nolocale("1.33", int32_2) == false); // strict, false + REQUIRE(int32_2 == 10); + + uint32_t uint32_ = 10; + REQUIRE(string_is_numeric_nolocale("-1", uint32_) == false); // underflow, false + REQUIRE(uint32_ == 10); + + int64_t int64_ = 10; + REQUIRE(string_is_numeric_nolocale("-1", int64_) == true); + REQUIRE(int64_ == -1); + + uint64_t uint64_ = 10; + REQUIRE(string_is_numeric_nolocale("1", uint64_) == true); + REQUIRE(uint64_ == 1); + + uint64_t uint64_2 = 10; + REQUIRE(string_is_numeric_nolocale("-1", uint64_2) == false); // underflow + REQUIRE(uint64_2 == 10); + + uint64_t uint64_3 = 10; + REQUIRE(string_is_numeric_nolocale(" 1", uint64_3) == false); // strict + REQUIRE(uint64_3 == 10); + + uint64_t uint64_4 = 10; + REQUIRE(string_is_numeric_nolocale(" 1", uint64_4, false) == true); // ignore space + REQUIRE(uint64_4 == 1); + + int int_ = 10; + REQUIRE(string_is_numeric_nolocale("-1", int_) == true); + REQUIRE(int_ == -1); + + unsigned int uint_ = 10; + REQUIRE(string_is_numeric_nolocale("-1", uint_) == false); // underflow, false + REQUIRE(uint_ == 10); + + long long_int_ = 10; + REQUIRE(string_is_numeric_nolocale("-1", long_int_) == true); + REQUIRE(long_int_ == -1); + + unsigned long long_uint_ = 10; + REQUIRE(string_is_numeric_nolocale("-1", long_uint_) == false); // underflow, false + REQUIRE(long_uint_ == 10); + + char char_ = 10; + REQUIRE(string_is_numeric_nolocale("4", char_) == true); + REQUIRE(char_ == 4); + + unsigned char uchar_ = 10; + REQUIRE(string_is_numeric_nolocale("315", uchar_) == false); // overflow, false + REQUIRE(uchar_ == 10); + + unsigned char uchar_2 = 10; + REQUIRE(string_is_numeric_nolocale("128", uchar_2) == true); + REQUIRE(uchar_2 == 128); + + unsigned char uchar_3 = 10; + REQUIRE(string_is_numeric_nolocale("-2", uchar_3) == false); // underflow + REQUIRE(uchar_3 == 10); + + signed char schar_2 = 10; + REQUIRE(string_is_numeric_nolocale("128", schar_2) == false); // overflow, false + REQUIRE(schar_2 == 10); + + signed char schar_3 = 10; + REQUIRE(string_is_numeric_nolocale("-128", schar_3) == true); + REQUIRE(schar_3 == -128); + + bool b = false; + REQUIRE(string_is_numeric_nolocale("true", b) == true); + REQUIRE(b == true); + } + { + const double eps = std::numeric_limits::epsilon(); // it will do + + double d = 10; + REQUIRE(string_is_numeric_nolocale("-1.3", d) == true); + REQUIRE(std::abs(-1.3 - d) <= eps); + + d = 10; + REQUIRE(string_is_numeric_nolocale(" inf", d) == false); + REQUIRE(std::abs(10. - d) <= eps); + + d = 10; + REQUIRE(string_is_numeric_nolocale(" inf", d, false) == true); // non-strict + REQUIRE(std::isinf(d)); + + d = 10; + REQUIRE(string_is_numeric_nolocale("infinity", d) == true); + REQUIRE(std::isinf(d)); + + d = 10; + REQUIRE(string_is_numeric_nolocale("NAn", d) == true); + REQUIRE(std::isnan(d)); + + d = 10; + REQUIRE(string_is_numeric_nolocale("3.e+4", d) == true); + REQUIRE(std::abs(3e4 - d) <= eps); + + d = 10; + REQUIRE(string_is_numeric_nolocale("-3E4", d) == true); + REQUIRE(std::abs(-3e4 - d) <= eps); + + d = 10; + REQUIRE(string_is_numeric_nolocale("123 ", d) == false); // strict + REQUIRE(std::abs(10 - d) <= eps); + + d = 10; + REQUIRE(string_is_numeric_nolocale("123 ", d, false) == true); // non-strict + REQUIRE(std::abs(123 - d) <= eps); + + d = 10; + REQUIRE(string_is_numeric_nolocale("e+3", d) == false); + REQUIRE(std::abs(10 - d) <= eps); + } + { + + // Note: suffixes are case-insensitive. + // Long int and long double are differentiated by '.' in constant. + + REQUIRE(number_to_string_nolocale(true) == "true"); // bool + REQUIRE(number_to_string_nolocale('a') == "97"); // char + REQUIRE(number_to_string_nolocale(1.L) == "1"); // long double + REQUIRE(number_to_string_nolocale(2L) == "2"); // long int + REQUIRE(number_to_string_nolocale(6) == "6"); // int + + REQUIRE(number_to_string_nolocale(3LL) == "3"); // long long int + REQUIRE(number_to_string_nolocale(4ULL) == "4"); // unsigned long long int + + REQUIRE(number_to_string_nolocale(5.F) == "5"); // float + REQUIRE(number_to_string_nolocale(1.33) == "1.33"); // double + REQUIRE(number_to_string_nolocale(2.123456789123456789123456789L) == "2.123456789123456789"); + + REQUIRE(number_to_string_nolocale(std::numeric_limits::quiet_NaN()) == "nan"); + REQUIRE(number_to_string_nolocale(std::numeric_limits::signaling_NaN()) == "nan"); + REQUIRE(number_to_string_nolocale(std::numeric_limits::infinity()) == "inf"); + REQUIRE(number_to_string_nolocale(-std::numeric_limits::infinity()) == "-inf"); + + REQUIRE(number_to_string_nolocale(uint16_t(0x1133), 16) == "0x1133"); + REQUIRE(number_to_string_nolocale(uint32_t(0x00001234), 16) == "0x00001234"); + REQUIRE(number_to_string_nolocale(uint64_t(0x00001234), 16) == "0x0000000000001234"); + REQUIRE(number_to_string_nolocale(uint16_t(0), 16) == "0x0000"); + + REQUIRE(number_to_string_nolocale(uint16_t(0xffff), 8) == "0177777"); + REQUIRE(number_to_string_nolocale(uint16_t(0), 8) == "00"); + + REQUIRE(number_to_string_nolocale(uint16_t(0xffff), 2) == "65535"); + REQUIRE(number_to_string_nolocale(uint16_t(0), 2) == "0"); + + } +} + + + + + + +/// @}