From 5ff48cb7105e58185ae7c25b7deaad0124959c4f Mon Sep 17 00:00:00 2001 From: Leonetienne Date: Sun, 27 Feb 2022 15:49:09 +0100 Subject: [PATCH] Added tests for StringDivision, made it work with any base, and implemented BaseX_2_10 --- Src/GeneralUtility.cpp | 71 +++++++++++++++++++++ Src/GeneralUtility.h | 15 +++++ Test/CMakeLists.txt | 1 + Test/StringDivision.cpp | 132 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 219 insertions(+) create mode 100644 Test/StringDivision.cpp diff --git a/Src/GeneralUtility.cpp b/Src/GeneralUtility.cpp index e248e85..a9dbf5c 100644 --- a/Src/GeneralUtility.cpp +++ b/Src/GeneralUtility.cpp @@ -1,2 +1,73 @@ #include "GeneralUtility.h" +#include +#include +#include +// Fast 64-bit int power function +std::uint64_t Powuli(const std::uint64_t& b, const std::uint64_t& e) { + std::uint64_t buf = 1; + + for (std::uint64_t i = 0; i < e; i++) + buf *= b; + + return buf; +} + +std::uint64_t GeneralUtility::BaseX_2_10(const std::string& num, const std::string& set) { + unsigned long long int buf = 0; + for (std::size_t i = 0; i < num.length(); i++) { + for (std::size_t j = 0; j < set.length(); j++) { + if (num[i] == set[j]) { + buf += Powuli((std::uint64_t)set.length(), (uint64_t)(num.length() - (i + 1))) * j; + break; + } + } + } + + return buf; +} + +// Based on: https://www.geeksforgeeks.org/divide-large-number-represented-string/ +std::pair +GeneralUtility::StringDivision(const std::string& dividend, const unsigned int divisor, const std::string& set) { + // No set? Throw logic error + if (set.length() == 0) + throw std::logic_error("Can't divide a number of base0! Please supple a nonempty set!"); + // No division by 0 + if (divisor == 0) + throw std::overflow_error("Division by zero!"); + + // As result can be very large store it in string + std::stringstream ss; + + // Find prefix of number that is larger than divisor. + int idx = 0; + int temp = Ord(dividend[idx], set); + while (temp < divisor) + temp = temp * set.length() + Ord(dividend[++idx], set); + + // Repeatedly divide divisor with temp. After + // every division, update temp to include one + // more digit. + int curRest = temp % divisor; + while (dividend.size() > idx) { + // Store result in answer i.e. temp / divisor + ss << (char)(set[(temp / divisor)]); + curRest = temp % divisor; + + // Take next digit of number + temp = (temp % divisor) * set.length() + Ord(dividend[++idx], set); + } + + // If divisor is greater than number + if (ss.str().length() == 0) + { + // Generate 0-value string + std::stringstream ss; + ss << set[0]; + return std::make_pair(ss.str(), BaseX_2_10(dividend, set)); + } + + // else return the answer + return std::make_pair(ss.str(), curRest); +} diff --git a/Src/GeneralUtility.h b/Src/GeneralUtility.h index e3a0f85..93e06c7 100644 --- a/Src/GeneralUtility.h +++ b/Src/GeneralUtility.h @@ -14,6 +14,21 @@ public: //! \return The index of `item` in `set`. -1 if not found. template static int Ord(const T_Type& item, const T_Container& set); + + //! Will divide a number of arbitrary base in `dividend` by an integer divisor. + //! This is a specific helper function for the base conversion functions. + //! \param dividend The number to be divided in string form + //! \param divisor The integer divisor + //! \param set The set/base of `dividend` + //! \return A pair of the result. (result, rest) + static std::pair StringDivision(const std::string& dividend, const unsigned int divisor, const std::string& set = "0123456789"); + + //! Will convert a number of arbitrary base to base 10 + //! \param num A string representing the number + //! \param set The set/base of the number + //! \return A 64-bit integer representing the number + static std::uint64_t BaseX_2_10(const std::string& num, const std::string& set); + private: // No instantiation! >:( GeneralUtility(); diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index c811fc6..7e00b6c 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -11,6 +11,7 @@ add_executable(Test main.cpp Ord.cpp + StringDivision.cpp ) target_link_libraries(Test GeneralUtility) diff --git a/Test/StringDivision.cpp b/Test/StringDivision.cpp new file mode 100644 index 0000000..50cb0f8 --- /dev/null +++ b/Test/StringDivision.cpp @@ -0,0 +1,132 @@ +#include +#include "Catch2.h" +#include +#include +#include +#include + +// Tests that basic division (base10) is working, with oracle values +TEST_CASE(__FILE__"/Base10", "[StringDivision]") +{ + const std::string set = "0123456789"; + + SECTION("No rest") { + const auto res = GeneralUtility::StringDivision("200", 10, set); + REQUIRE(res.first == "20"); + REQUIRE(res.second == 0); + } + + SECTION("With rest") { + const auto res = GeneralUtility::StringDivision("205", 10, set); + REQUIRE(res.first == "20"); + REQUIRE(res.second == 5); + } + + SECTION("With rest, and divisor > dividend") { + const auto res = GeneralUtility::StringDivision("205", 299, set); + REQUIRE(res.first == "0"); + REQUIRE(res.second == 205); + } +} + +// Tests that basic division (base10) is working, by doing a lot of random, precalculated divisions +TEST_CASE(__FILE__"/Base10_Randoms", "[StringDivision]") +{ + srand(time(0)); + const std::string set = "0123456789"; + + // Test 1000 random numbers + for (std::size_t i = 0; i < 1000; i++) { + // Generate random input + const std::uint64_t i_dividend = rand(); + const std::uint32_t i_divisor = rand() % 1000000; + + // Precalculate expected output + const std::uint32_t expected_result = i_dividend / i_divisor; + const std::uint32_t expected_rest = i_dividend % i_divisor; + + // Convert dividend to a string + std::stringstream ss; + ss << i_dividend; + const std::string dividend = ss.str(); + + // Compute results + const auto res = GeneralUtility::StringDivision(dividend, i_divisor, set); + const std::uint32_t actual_result = std::stoi(res.first); + const std::uint32_t actual_rest = res.second; + + // Assertions + REQUIRE(actual_result == expected_result); + REQUIRE(actual_rest == expected_rest); + } +} + +// Tests that base16 division is working, with oracle values +TEST_CASE(__FILE__"/Base16", "[StringDivision]") +{ + const std::string set = "0123456789abcdef"; + + SECTION("No rest") { + const auto res = GeneralUtility::StringDivision("1f4", 10, set); + REQUIRE(res.first == "32"); + REQUIRE(res.second == 0); + } + + SECTION("With rest") { + const auto res = GeneralUtility::StringDivision("1fd", 10, set); + REQUIRE(res.first == "32"); + REQUIRE(res.second == 9); + } + + SECTION("With rest, and divisor > dividend") { + const auto res = GeneralUtility::StringDivision("1f4", 999, set); + REQUIRE(res.first == "0"); + REQUIRE(res.second == 500); + } +} + +// Tests that base2 division is working, with oracle values +TEST_CASE(__FILE__"/Base2", "[StringDivision]") +{ + const std::string set = "01"; + + SECTION("No rest") { + const auto res = GeneralUtility::StringDivision("111110100", 10, set); + REQUIRE(res.first == "110010"); + REQUIRE(res.second == 0); + } + + SECTION("With rest") { + const auto res = GeneralUtility::StringDivision("111111011", 10, set); + REQUIRE(res.first == "110010"); + REQUIRE(res.second == 7); + } + SECTION("With rest, and divisor > dividend") { + const auto res = GeneralUtility::StringDivision("111110100", 599, set); + REQUIRE(res.first == "0"); + REQUIRE(res.second == 500); + } +} + +// Tests that fucking big bases are working, with oracle values +TEST_CASE(__FILE__"/BaseFuckingBig", "[StringDivision]") +{ + const std::string set = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + SECTION("No rest") { + const auto res = GeneralUtility::StringDivision("9RieQV", 29915, set); + REQUIRE(res.first == "1DGL"); + REQUIRE(res.second == 0); + } + + SECTION("With rest") { + const auto res = GeneralUtility::StringDivision("9RieQZ", 29915, set); + REQUIRE(res.first == "1DGL"); + REQUIRE(res.second == 4); + } + SECTION("With rest, and divisor > dividend") { + const auto res = GeneralUtility::StringDivision("8x", 600, set); + REQUIRE(res.first == "0"); + REQUIRE(res.second == 555); + } +}