diff --git a/CMakeLists.txt b/CMakeLists.txt index 40733e9..f45c706 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -121,8 +121,8 @@ set(META_APP_AUTHOR "Martchus") set(META_APP_URL "https://github.com/${META_APP_AUTHOR}/${META_PROJECT_NAME}") set(META_APP_DESCRIPTION "Common C++ classes and routines used by my applications such as argument parser, IO and conversion utilities") set(META_VERSION_MAJOR 4) -set(META_VERSION_MINOR 8) -set(META_VERSION_PATCH 1) +set(META_VERSION_MINOR 9) +set(META_VERSION_PATCH 0) # find required 3rd party libraries include(3rdParty) diff --git a/conversion/stringconversion.h b/conversion/stringconversion.h index d0ae62b..19bf659 100644 --- a/conversion/stringconversion.h +++ b/conversion/stringconversion.h @@ -301,7 +301,7 @@ StringType numberToString(IntegralType number, typename StringType::value_type b * \tparam StringType The string type (should be an instantiation of the basic_string class template). * \remarks This function is using std::basic_stringstream interanlly and hence also has its limitations (eg. regarding * \a base and types). - * \sa stringToNumber() + * \sa stringToNumber(), bufferToNumber() */ template >...> StringType numberToString(FloatingType number, typename StringType::value_type base = 10) @@ -334,11 +334,11 @@ template CharType charToDigit(CharType character, CharType b } /*! - * \brief Converts the given \a string to a number assuming \a string uses the specified \a base. + * \brief Converts the given \a string to an unsigned number assuming \a string uses the specified \a base. * \tparam NumberType The data type used to store the converted value. * \tparam StringType The string type (should be an instantiation of the basic_string class template). * \throws A ConversionException will be thrown if the provided \a string is not a valid number. - * \sa numberToString() + * \sa numberToString(), bufferToNumber() */ template , Traits::Not>>...> IntegralType stringToNumber(const StringType &string, typename StringType::value_type base = 10) @@ -355,11 +355,11 @@ IntegralType stringToNumber(const StringType &string, typename StringType::value } /*! - * \brief Converts the given \a string to a number assuming \a string uses the specified \a base. + * \brief Converts the given \a string to a signed number assuming \a string uses the specified \a base. * \tparam NumberType The data type used to store the converted value. * \tparam StringType The string type (should be an instantiation of the basic_string class template). * \throws A ConversionException will be thrown if the provided \a string is not a valid number. - * \sa numberToString() + * \sa numberToString(), bufferToNumber() */ template , std::is_signed>...> IntegralType stringToNumber(const StringType &string, typename StringType::value_type base = 10) @@ -393,7 +393,7 @@ IntegralType stringToNumber(const StringType &string, typename StringType::value * \throws A ConversionException will be thrown if the provided \a string is not a valid number. * \remarks This function is using std::basic_stringstream interanlly and hence also has its limitations (eg. regarding * \a base and types). - * \sa numberToString() + * \sa numberToString(), bufferToNumber() */ template >...> FloatingType stringToNumber(const StringType &string, typename StringType::value_type base = 10) @@ -409,11 +409,11 @@ FloatingType stringToNumber(const StringType &string, typename StringType::value } /*! - * \brief Converts the given null-terminated \a string to a numeric value using the specified \a base. + * \brief Converts the given null-terminated \a string to an unsigned numeric value using the specified \a base. * \tparam NumberType The data type used to store the converted value. * \tparam StringType The string type (should be an instantiation of the basic_string class template). * \throws A ConversionException will be thrown if the provided \a string is not a valid number. - * \sa numberToString() + * \sa numberToString(), bufferToNumber() */ template , Traits::Not>>...> IntegralType stringToNumber(const CharType *string, unsigned char base = 10) @@ -430,11 +430,32 @@ IntegralType stringToNumber(const CharType *string, unsigned char base = 10) } /*! - * \brief Converts the given null-terminated \a string to a numeric value using the specified \a base. + * \brief Converts the given \a string of \a size characters to an unsigned numeric value using the specified \a base. * \tparam NumberType The data type used to store the converted value. * \tparam StringType The string type (should be an instantiation of the basic_string class template). * \throws A ConversionException will be thrown if the provided \a string is not a valid number. - * \sa numberToString() + * \sa numberToString(), stringToNumber() + */ +template , Traits::Not>>...> +IntegralType bufferToNumber(const CharType *string, std::size_t size, unsigned char base = 10) +{ + IntegralType result = 0; + for (const CharType *end = string + size; string != end; ++string) { + if (*string == ' ') { + continue; + } + result *= base; + result += charToDigit(*string, base); + } + return result; +} + +/*! + * \brief Converts the given null-terminated \a string to a signed numeric value using the specified \a base. + * \tparam NumberType The data type used to store the converted value. + * \tparam StringType The string type (should be an instantiation of the basic_string class template). + * \throws A ConversionException will be thrown if the provided \a string is not a valid number. + * \sa numberToString(), bufferToNumber() */ template , std::is_signed>...> IntegralType stringToNumber(const CharType *string, unsigned char base = 10) @@ -462,6 +483,40 @@ IntegralType stringToNumber(const CharType *string, unsigned char base = 10) return negative ? -result : result; } +/*! + * \brief Converts the given \a string of \a size characters to a signed numeric value using the specified \a base. + * \tparam NumberType The data type used to store the converted value. + * \tparam StringType The string type (should be an instantiation of the basic_string class template). + * \throws A ConversionException will be thrown if the provided \a string is not a valid number. + * \sa numberToString(), stringToNumber() + */ +template , std::is_signed>...> +IntegralType bufferToNumber(const CharType *string, std::size_t size, unsigned char base = 10) +{ + if (!size) { + return 0; + } + const CharType *end = string + size; + for (; string != end && *string == ' '; ++string) + ; + if (string == end) { + return 0; + } + const bool negative = (*string == '-'); + if (negative) { + ++string; + } + IntegralType result = 0; + for (; string != end; ++string) { + if (*string == ' ') { + continue; + } + result *= base; + result += charToDigit(*string, base); + } + return negative ? -result : result; +} + /*! * \brief Interprets the given \a integer at the specified position as std::string using the specified byte order. * diff --git a/tests/conversiontests.cpp b/tests/conversiontests.cpp index bfd66b6..a1b9378 100644 --- a/tests/conversiontests.cpp +++ b/tests/conversiontests.cpp @@ -228,24 +228,27 @@ void ConversionTests::testStringEncodingConversions() void ConversionTests::testStringConversions() { // stringToNumber() / numberToString() with zero and random numbers - CPPUNIT_ASSERT_EQUAL(string("0"), numberToString(0)); - CPPUNIT_ASSERT_EQUAL(string("0"), numberToString(0)); + CPPUNIT_ASSERT_EQUAL("0"s, numberToString(0)); + CPPUNIT_ASSERT_EQUAL("0"s, numberToString(0)); uniform_int_distribution randomDistSigned(numeric_limits::min()); uniform_int_distribution randomDistUnsigned(0); + const string stringMsg("string"), wideStringMsg("wide string"), bufferMsg("buffer"); for (byte b = 1; b < 100; ++b) { auto signedRandom = randomDistSigned(m_randomEngine); auto unsignedRandom = randomDistUnsigned(m_randomEngine); for (const auto base : initializer_list{ 2, 8, 10, 16 }) { - auto resultString = stringToNumber(numberToString(unsignedRandom, base), base); - auto resultWideString = stringToNumber(numberToString(unsignedRandom, base), base); - CPPUNIT_ASSERT_EQUAL(unsignedRandom, resultString); - CPPUNIT_ASSERT_EQUAL(unsignedRandom, resultWideString); + const auto asString = numberToString(unsignedRandom, static_cast(base)); + const auto asWideString = numberToString(unsignedRandom, base); + CPPUNIT_ASSERT_EQUAL_MESSAGE(stringMsg, unsignedRandom, stringToNumber(asString, static_cast(base))); + CPPUNIT_ASSERT_EQUAL_MESSAGE(wideStringMsg, unsignedRandom, stringToNumber(asWideString, base)); + CPPUNIT_ASSERT_EQUAL_MESSAGE(bufferMsg, unsignedRandom, bufferToNumber(asString.data(), asString.size(), base)); } for (const auto base : initializer_list{ 10 }) { - auto resultString = stringToNumber(numberToString(signedRandom, base), base); - auto resultWideString = stringToNumber(numberToString(signedRandom, base), base); - CPPUNIT_ASSERT_EQUAL(signedRandom, resultString); - CPPUNIT_ASSERT_EQUAL(signedRandom, resultWideString); + const auto asString = numberToString(signedRandom, static_cast(base)); + const auto asWideString = numberToString(signedRandom, base); + CPPUNIT_ASSERT_EQUAL_MESSAGE(stringMsg, signedRandom, stringToNumber(asString, static_cast(base))); + CPPUNIT_ASSERT_EQUAL_MESSAGE(wideStringMsg, signedRandom, stringToNumber(asWideString, base)); + CPPUNIT_ASSERT_EQUAL_MESSAGE(bufferMsg, signedRandom, bufferToNumber(asString.data(), asString.size(), base)); } } @@ -254,10 +257,12 @@ void ConversionTests::testStringConversions() CPPUNIT_ASSERT_EQUAL(1, stringToNumber(L"01"s)); CPPUNIT_ASSERT_EQUAL(1, stringToNumber(u"01"s)); CPPUNIT_ASSERT_EQUAL(-23, stringToNumber(" - 023"s)); + CPPUNIT_ASSERT_EQUAL(-23, bufferToNumber(" - 023", 6)); CPPUNIT_ASSERT_EQUAL(1u, stringToNumber("01")); CPPUNIT_ASSERT_EQUAL(1u, stringToNumber(L"01"s)); CPPUNIT_ASSERT_EQUAL(1u, stringToNumber(u"01"s)); CPPUNIT_ASSERT_EQUAL(23u, stringToNumber(" 023"s)); + CPPUNIT_ASSERT_EQUAL(23u, bufferToNumber(" 023", 5)); // interpretIntegerAsString() CPPUNIT_ASSERT_EQUAL("TEST"s, interpretIntegerAsString(0x54455354));