diff --git a/Src/GeneralUtility.cpp b/Src/GeneralUtility.cpp index 9ff92b3..414e829 100644 --- a/Src/GeneralUtility.cpp +++ b/Src/GeneralUtility.cpp @@ -1,52 +1,5 @@ #include "GeneralUtility.h" #include -#include -#include - -// 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 supply 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); -} std::string GeneralUtility::BaseX_2_Y(const std::string &num, const std::string &set_in, const std::string &set_out, const std::uint32_t minOutLen) { if ((set_in.length() == 0) || (set_out.length() == 0)) @@ -64,7 +17,7 @@ std::string GeneralUtility::BaseX_2_Y(const std::string &num, const std::string std::string buf = num; while (buf != zeroInbase) { - const auto divRes = StringDivision(buf, set_out.length(), set_in); + const auto divRes = DigitstringDivision(buf, set_out.length(), set_in); const std::uint64_t mod = divRes.second; buf = divRes.first; ss << set_out[mod]; diff --git a/Src/GeneralUtility.h b/Src/GeneralUtility.h index 9e53911..fd72868 100644 --- a/Src/GeneralUtility.h +++ b/Src/GeneralUtility.h @@ -3,6 +3,8 @@ #include #include +#include +#include class GeneralUtility { public: @@ -21,7 +23,8 @@ public: //! \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"); + template + static std::pair DigitstringDivision(const T_Container& dividend, const unsigned int divisor, const T_Container& set); //! Will convert a number of arbitrary base to base 10 //! \param num A string representing the number @@ -95,4 +98,69 @@ int GeneralUtility::Ord(const T_Type& item, const T_Container& set) { return result - set.begin(); } +// Based on: https://www.geeksforgeeks.org/divide-large-number-represented-string/ +template +std::pair +GeneralUtility::DigitstringDivision(const T_Container& dividend, const unsigned int divisor, const T_Container& set) { + // Quick rejects: + + // No set? Throw logic error + if (set.size() == 0) + throw std::logic_error("Can't divide a number of base0! Please supply a nonempty set!"); + + // No division by 0 + if (divisor == 0) + throw std::overflow_error("Division by zero!"); + + // Dividend size 0? Return 0. + if (dividend.size() == 0) + return std::make_pair(T_Container({set[0]}), 0); + + // Verify that all digits are represented in the set/base + for (const auto& c : dividend) + if (Ord(c, set) == -1) + throw std::logic_error("The supplied dividend contains a digit that is not represented in the set/base!"); + + + // Now for the actual algorithm: + T_Container result; + + // Find prefix of number that is larger than divisor. + int idx = 0; + int temp = Ord(dividend[idx], set); + while (temp < divisor) { + idx++; + if (idx < dividend.size()) + temp = temp * set.size() + Ord(dividend[idx], set); + else + break; + } + + // 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 + result.insert(result.cend(), set[temp / divisor]); + curRest = temp % divisor; + + // Take next digit of number + idx++; + if (idx < dividend.size()) + temp = (temp % divisor) * set.size() + Ord(dividend[idx], set); + } + + // If divisor is greater than number + if (result.size() == 0) { + // Generate 0-value digitstring + result.clear(); + result.insert(result.cend(), set[0]); + return std::make_pair(result, BaseX_2_10(dividend, set)); + } + + // else return the answer + return std::make_pair(result, curRest); +} + #endif //GENERALUTILITY_GENERALUTILITY_H diff --git a/Test/CMakeLists.txt b/Test/CMakeLists.txt index cb5aef1..e00e468 100644 --- a/Test/CMakeLists.txt +++ b/Test/CMakeLists.txt @@ -11,7 +11,7 @@ add_executable(Test main.cpp Ord.cpp - StringDivision.cpp + DigitstringDivision.cpp BaseX_2_10.cpp Base10_2_X.cpp BaseX_2_Y.cpp diff --git a/Test/DigitstringDivision.cpp b/Test/DigitstringDivision.cpp new file mode 100644 index 0000000..48649a5 --- /dev/null +++ b/Test/DigitstringDivision.cpp @@ -0,0 +1,187 @@ +#include +#include "Catch2.h" +#include +#include +#include +#include +#include + +// Tests that basic division (base10) is working, with oracle values +TEST_CASE(__FILE__"/Base10", "[DigitstringDivision]") +{ + const std::string set = "0123456789"; + + SECTION("No rest") { + const auto res = GeneralUtility::DigitstringDivision("200", 10, set); + REQUIRE(res.first == "20"); + REQUIRE(res.second == 0); + } + + SECTION("With rest") { + const auto res = GeneralUtility::DigitstringDivision("205", 10, set); + REQUIRE(res.first == "20"); + REQUIRE(res.second == 5); + } + + SECTION("With rest, and divisor > dividend") { + const auto res = GeneralUtility::DigitstringDivision("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", "[DigitstringDivision]") +{ + 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::DigitstringDivision(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", "[DigitstringDivision]") +{ + const std::string set = "0123456789abcdef"; + + SECTION("No rest") { + const auto res = GeneralUtility::DigitstringDivision("1f4", 10, set); + REQUIRE(res.first == "32"); + REQUIRE(res.second == 0); + } + + SECTION("With rest") { + const auto res = GeneralUtility::DigitstringDivision("1fd", 10, set); + REQUIRE(res.first == "32"); + REQUIRE(res.second == 9); + } + + SECTION("With rest, and divisor > dividend") { + const auto res = GeneralUtility::DigitstringDivision("1f4", 999, set); + REQUIRE(res.first == "0"); + REQUIRE(res.second == 500); + } +} + +// Tests that base2 division is working, with oracle values +TEST_CASE(__FILE__"/Base2", "[DigitstringDivision]") +{ + const std::string set = "01"; + + SECTION("No rest") { + const auto res = GeneralUtility::DigitstringDivision("111110100", 10, set); + REQUIRE(res.first == "110010"); + REQUIRE(res.second == 0); + } + + SECTION("With rest") { + const auto res = GeneralUtility::DigitstringDivision("111111011", 10, set); + REQUIRE(res.first == "110010"); + REQUIRE(res.second == 7); + } + SECTION("With rest, and divisor > dividend") { + const auto res = GeneralUtility::DigitstringDivision("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", "[DigitstringDivision]") +{ + const std::string set = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + + SECTION("No rest") { + const auto res = GeneralUtility::DigitstringDivision("9RieQV", 29915, set); + REQUIRE(res.first == "1DGL"); + REQUIRE(res.second == 0); + } + + SECTION("With rest") { + const auto res = GeneralUtility::DigitstringDivision("9RieQZ", 29915, set); + REQUIRE(res.first == "1DGL"); + REQUIRE(res.second == 4); + } + SECTION("With rest, and divisor > dividend") { + const auto res = GeneralUtility::DigitstringDivision("8x", 600, set); + REQUIRE(res.first == "0"); + REQUIRE(res.second == 555); + } +} + +// Tests that having a dividend of size 0 returns 0 +TEST_CASE(__FILE__"/DividendLengthZero", "[DigitstringDivision]") +{ + const auto& res = GeneralUtility::DigitstringDivision("", 19, "0123456789"); + REQUIRE(res.first == "0"); + REQUIRE(res.second == 0); +} + +// Tests that a division by zero exception is thrown when appropriate +TEST_CASE(__FILE__"/DivisionByZero", "[DigitstringDivision]") +{ + REQUIRE_THROWS_AS( + GeneralUtility::DigitstringDivision("699", 0, "0123456789") + , std::overflow_error); +} + +// Tests that a logic error is thrown when the supplied set is empty +TEST_CASE(__FILE__"/NoSetSupplied", "[DigitstringDivision]") +{ + REQUIRE_THROWS_AS( + GeneralUtility::DigitstringDivision("699", 5, "") + , std::logic_error); +} + +// Tests that a logic error is thrown if the dividend contains a character not present in the set +TEST_CASE(__FILE__"/InvalidDigitsInDividend", "[DigitstringDivision]") +{ + REQUIRE_THROWS_AS( + GeneralUtility::DigitstringDivision("699z", 5, "0123465789") + , std::logic_error); +} + +// Tests that weird, abstract bases are working, with oracle values +TEST_CASE(__FILE__"/BaseWeird", "[DigitstringDivision]") +{ + const std::vector set = { "apple", "apricot", "avocado", "banana", "bell pepper", "bilberry" }; + + SECTION("No rest") { + const auto res = GeneralUtility::DigitstringDivision>({"apricot", "bilberry", "bell pepper"}, 10, set); + REQUIRE(res.first == std::vector({"apricot", "apricot"})); + REQUIRE(res.second == 0); + } + SECTION("With rest") { + const auto res = GeneralUtility::DigitstringDivision>({"avocado", "apple", "banana"}, 10, set); + REQUIRE(res.first == std::vector({"apricot", "apricot"})); + REQUIRE(res.second == 5); + } + SECTION("With rest, and divisor > dividend") { + const auto res = GeneralUtility::DigitstringDivision>({"apricot", "bilberry", "bell pepper"}, 100, set); + REQUIRE(res.first == std::vector({"apple"})); + REQUIRE(res.second == 70); + } +} diff --git a/Test/StringDivision.cpp b/Test/StringDivision.cpp deleted file mode 100644 index df36ca2..0000000 --- a/Test/StringDivision.cpp +++ /dev/null @@ -1,149 +0,0 @@ -#include -#include "Catch2.h" -#include -#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); - } -} - -// Tests that a division by zero exception is thrown when appropriate -TEST_CASE(__FILE__"/DivisionByZero", "[StringDivision]") -{ - REQUIRE_THROWS_AS( - GeneralUtility::StringDivision("699", 0) - , std::overflow_error); -} - -// Tests that a logic error is thrown when the supplied set is empty -TEST_CASE(__FILE__"/NoSetSupplied", "[StringDivision]") -{ - REQUIRE_THROWS_AS( - GeneralUtility::StringDivision("699", 5, "") - , std::logic_error); -}