diff --git a/cpp_utils/include/cpp_utils/math/math.hpp b/cpp_utils/include/cpp_utils/math/math.hpp index e0fcfde7..0925e830 100644 --- a/cpp_utils/include/cpp_utils/math/math.hpp +++ b/cpp_utils/include/cpp_utils/math/math.hpp @@ -27,26 +27,96 @@ namespace eprosima { namespace utils { +/** + * @brief Optimize % 2 operation + * + * @param number to calculate whether it is even + * + * @return whether \c number is even + */ +CPP_UTILS_DllAPI bool is_even( + unsigned int number) noexcept; + +/** + * @brief Calculate whether the argument is a power of 2 value. + * + * is_power_of_2(x) <=> E(n) : 2^n = x + * + * @param number to calculate whether it is a power of 2 + * + * @return whether \c number is power of 2 + */ +CPP_UTILS_DllAPI bool is_power_of_2( + unsigned int number) noexcept; + /** * @brief Module (%) operation with performance optimization * * This function optimizes the % operation, that executes a division, by optimizing these cases: - * - If the dividend is smaller or equal than the divisor, the result is the dividend + * - If the dividend is smaller than the divisor, the result is the dividend + * - If the dividend is equal than the divisor, the result is 0 * - If the divisor is 2, the result is the dividend % 2 calculated by a logic AND operation * - If the divisor is a power of 2, the result is calculated by a logic AND operation + * - Otherwise uses % operation * * @param dividend Dividend * @param divisor Divisor (must be greater than 0 so the operation make sense) * - * @attention if divisor is 0, the result is \c dividend + * @pre \c divisor must not be 0 + * + * @return The result of the operation % * - * @return The result of the operation + * @attention Do only use this function with non literal values. Literal values are optimized by compiler. */ -CPP_UTILS_DllAPI uint32_t fast_module( - uint32_t dividend, - uint32_t divisor) noexcept; +CPP_UTILS_DllAPI unsigned int fast_module( + unsigned int dividend, + unsigned int divisor) noexcept; -} /* namespace utils */ -} /* namespace eprosima */ +/** + * @brief Integer Division (/) operation with performance optimization + * + * This function optimizes the / operation by optimizing these cases: + * - If \c dividend is smaller or equal than the \c, the result is \c dividend + * - If the \c is 2, the result is \c dividend % 2 calculated by a logic AND operation + * - If the \c is a power of 2, the result is calculated by a logic AND operation + * - Otherwise uses / operation + * + * @param dividend Dividend + * @param divisor Divisor + * + * @pre \c divisor must not be 0 + * + * @return The result of the operation / + * + * @attention Do only use this function with non literal values. Literal values are optimized by compiler. + */ +CPP_UTILS_DllAPI unsigned int fast_division( + unsigned int dividend, + unsigned int divisor) noexcept; +/** + * @brief Calculate the sum of an arithmetic progression from an initial to a final number. + * + * This function uses the fast operation to calculate an arithmetic sum: + * S = ((a1 + an) / 2) * (n) + * + * @pre \c interval must be greater than 0 + * @pre \c steps must be greater than 0 + * + * @param lowest lowest element of the arithmetic progression + * @param interval interval between two elements of the arithmetic progression + * @param steps number of steps of the arithmetic progression + * + * @return The result of the sum + * + * EXAMPLE OF USE + * 1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 + 10 = arithmetic_progression_sum(1, 1, 10) + * 0 + 2 + 4 + 6 + 8 = arithmetic_progression_sum(0, 2, 5) + */ +CPP_UTILS_DllAPI unsigned int arithmetic_progression_sum( + unsigned int lowest, + unsigned int interval, + unsigned int steps) noexcept; +} /* namespace utils */ +} /* namespace eprosima */ diff --git a/cpp_utils/src/cpp/math/math.cpp b/cpp_utils/src/cpp/math/math.cpp index 40f483af..a7e7daf0 100644 --- a/cpp_utils/src/cpp/math/math.cpp +++ b/cpp_utils/src/cpp/math/math.cpp @@ -20,12 +20,28 @@ namespace eprosima { namespace utils { +#include + #include -uint32_t fast_module( - uint32_t dividend, - uint32_t divisor) noexcept +bool is_even( + unsigned int number) noexcept +{ + return (number & 0x1) == 0; +} + +bool is_power_of_2( + unsigned int number) noexcept +{ + return number && (!(number & (number - 1))); +} + +unsigned int fast_module( + unsigned int dividend, + unsigned int divisor) noexcept { + assert(divisor != 0); + if (dividend < divisor) { // Optimize to 1 operation [if] @@ -41,12 +57,67 @@ uint32_t fast_module( // Optimize to 4 operations [if, if, if, and] return dividend & 1; } + else if (is_power_of_2(divisor)) + { + // Optimize to ~6 operations [if, if, if, if(and), and] + return dividend & (divisor - 1); + } + else + { + // Not optimum + return dividend % divisor; + } +} + +unsigned int fast_division( + unsigned int dividend, + unsigned int divisor) noexcept +{ + assert(divisor != 0); + + if (dividend < divisor) + { + // Optimize to 1 operation [if] + return 0; + } + else if (dividend == divisor) + { + // Optimize to 2 operation [if, if] + return 1; + } + else if (divisor == 1) + { + // Optimize to 3 operations [if, if, if] + return dividend; + } + else if (divisor == 2) + { + // Optimize to 5 operations [if, if, if, if, swift] + return (dividend >> 1); + } + else if (is_power_of_2(divisor)) + { + while (divisor != 1) + { + dividend >>= 1; + divisor >>= 1; + } + return dividend; + } else { - // Optimize to 7 operations [if, if, if, -, and, -, and] in case E(n){divisor = 2^n} - return divisor & (divisor - 1) ? dividend % divisor : dividend& (divisor - 1); + // Not optimum + return dividend / divisor; } } +unsigned int arithmetic_progression_sum( + unsigned int lowest, + unsigned int interval, + unsigned int steps) noexcept +{ + return (((2 * lowest + ((steps - 1) * interval)) * steps) / 2); +} + } /* namespace utils */ } /* namespace eprosima */ diff --git a/cpp_utils/test/unittest/math/CMakeLists.txt b/cpp_utils/test/unittest/math/CMakeLists.txt index e8aebb0e..08d64803 100644 --- a/cpp_utils/test/unittest/math/CMakeLists.txt +++ b/cpp_utils/test/unittest/math/CMakeLists.txt @@ -20,7 +20,11 @@ set(TEST_SOURCES ) set(TEST_LIST + is_even + is_power_of_2 fast_module + fast_division + arithmetic_progression_sum ) set(TEST_EXTRA_LIBRARIES diff --git a/cpp_utils/test/unittest/math/mathTest.cpp b/cpp_utils/test/unittest/math/mathTest.cpp index 7a2ee115..8851ea0f 100644 --- a/cpp_utils/test/unittest/math/mathTest.cpp +++ b/cpp_utils/test/unittest/math/mathTest.cpp @@ -14,7 +14,7 @@ #include -#include +#include #include #include @@ -25,11 +25,75 @@ namespace eprosima { namespace utils { namespace test { -void compare_fast_module( - uint32_t dividend, - uint32_t divisor) +constexpr const unsigned int NUMBERS_TO_TEST = 1000; +constexpr const unsigned int NUMBERS_TO_TEST_SHORT = 100; + +bool compare_is_even( + unsigned int number) +{ + return ( + is_even(number) + == + (number % 2 == 0)); +} + +bool compare_is_power_of_2( + unsigned int number) +{ + // Check power of 2 numbers until they are equal or greater number + unsigned int x__ = 1; + while (x__ < number) + { + x__ *= 2; + } + + // If x == number it means number is a power of 2 + // Notice that 0 will not be power of 2, as expected + bool it_is_actually_power_of_2 = (x__ == number); + + return ( + is_power_of_2(number) + == + it_is_actually_power_of_2); +} + +bool compare_fast_module( + unsigned int dividend, + unsigned int divisor) { - ASSERT_EQ(fast_module(dividend, divisor), dividend % divisor) << dividend << " % " << divisor; + return ( + fast_module(dividend, divisor) + == + dividend % divisor); +} + +bool compare_fast_division( + unsigned int dividend, + unsigned int divisor) +{ + return ( + fast_division(dividend, divisor) + == + dividend / divisor); +} + +bool compare_arithmetic_progression_sum( + unsigned int lowest, + unsigned int interval, + unsigned int steps) +{ + unsigned int current_number = lowest; + unsigned int real_result = 0; + for (unsigned int i = 0; i < steps; ++i) + { + real_result += current_number; + current_number += interval; + } + + return ( + arithmetic_progression_sum(lowest, interval, steps) + == + real_result); } } /* namespace test */ @@ -37,62 +101,79 @@ void compare_fast_module( } /* namespace eprosima */ /** - * Test \c is_file_accesible method - * - * CASES: - * - dividend lower than divisor - * - dividend equal to divisor - * - divisor = 2 - * - divisor = 2^N - * - divisor even no 2^N - * - divisor odd + * Test \c is_even method */ -TEST(mathTest, fast_module) +TEST(mathTest, is_even) { - // dividend lower than divisor + // calculate module in many cases + for (unsigned int number = 0; number < test::NUMBERS_TO_TEST; ++number) { - test::compare_fast_module(3, 4); - test::compare_fast_module(0, 4); - test::compare_fast_module(101223, 20921341); - } - - // dividend equal to divisor - { - test::compare_fast_module(3, 3); - test::compare_fast_module(4, 4); - test::compare_fast_module(66666, 66666); + ASSERT_TRUE(test::compare_is_even(number)) + << number; } +} - // divisor = 2 +/** + * Test \c is_even method + */ +TEST(mathTest, is_power_of_2) +{ + // calculate module in many cases + for (unsigned int number = 0; number < test::NUMBERS_TO_TEST; ++number) { - test::compare_fast_module(3, 2); - test::compare_fast_module(4, 2); - test::compare_fast_module(66666, 2); - test::compare_fast_module(431253426, 2); + ASSERT_TRUE(test::compare_is_power_of_2(number)) + << number; } +} - // divisor = 2^N +/** + * Test \c fast_module method + */ +TEST(mathTest, fast_module) +{ + // calculate module in many cases + for (unsigned int dividend = 0; dividend < test::NUMBERS_TO_TEST; ++dividend) { - test::compare_fast_module(3, 4); - test::compare_fast_module(32, 8); - test::compare_fast_module(66666, 128); - test::compare_fast_module(431253426, 2048); + for (unsigned int divisor = 1; divisor < test::NUMBERS_TO_TEST; ++divisor) + { + ASSERT_TRUE(test::compare_fast_module(dividend, divisor)) + << dividend << " % " << divisor; + } } +} - // divisor even no 2^N +/** + * Test \c fast_division method + */ +TEST(mathTest, fast_division) +{ + // calculate module in many cases + for (unsigned int dividend = 0; dividend < test::NUMBERS_TO_TEST; ++dividend) { - test::compare_fast_module(3, 8); - test::compare_fast_module(12, 10); - test::compare_fast_module(66666, 120); - test::compare_fast_module(431253426, 2040); + for (unsigned int divisor = 1; divisor < test::NUMBERS_TO_TEST; ++divisor) + { + ASSERT_TRUE(test::compare_fast_division(dividend, divisor)) + << dividend << " % " << divisor; + } } +} - // divisor odd +/** + * Test \c arithmetic_progression_sum method + */ +TEST(mathTest, arithmetic_progression_sum) +{ + // calculate module in many cases + for (unsigned int lowest = 0; lowest < test::NUMBERS_TO_TEST_SHORT; ++lowest) { - test::compare_fast_module(3, 5); - test::compare_fast_module(12, 11); - test::compare_fast_module(66666, 127); - test::compare_fast_module(431253426, 2041); + for (unsigned int interval = 1; interval < test::NUMBERS_TO_TEST_SHORT; ++interval) + { + for (unsigned int steps = 1; steps < test::NUMBERS_TO_TEST_SHORT; ++steps) + { + ASSERT_TRUE(test::compare_arithmetic_progression_sum(lowest, interval, steps)) + << lowest << " , " << interval << " , " << steps; + } + } } }