provide follow features implementation:
[o-du/phy.git] / fhi_lib / test / common / common.hpp
diff --git a/fhi_lib/test/common/common.hpp b/fhi_lib/test/common/common.hpp
new file mode 100644 (file)
index 0000000..975fd0f
--- /dev/null
@@ -0,0 +1,671 @@
+/******************************************************************************\r
+*\r
+*   Copyright (c) 2019 Intel.\r
+*\r
+*   Licensed under the Apache License, Version 2.0 (the "License");\r
+*   you may not use this file except in compliance with the License.\r
+*   You may obtain a copy of the License at\r
+*\r
+*       http://www.apache.org/licenses/LICENSE-2.0\r
+*\r
+*   Unless required by applicable law or agreed to in writing, software\r
+*   distributed under the License is distributed on an "AS IS" BASIS,\r
+*   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\r
+*   See the License for the specific language governing permissions and\r
+*   limitations under the License.\r
+*\r
+*******************************************************************************/\r
+\r
+\r
+/* This is the new utility file for all tests, all new common functionality has to go here.\r
+   When contributing to the common.hpp please focus on readability and maintainability rather than\r
+   execution time. */\r
+#ifndef XRANLIB_COMMON_HPP\r
+#define XRANLIB_COMMON_HPP\r
+\r
+/* Disable warnings generated by JSON parser */\r
+#pragma warning(disable : 191)\r
+#pragma warning(disable : 186)\r
+#pragma warning(disable : 192)\r
+\r
+#include <exception>\r
+#include <random>\r
+#include <string>\r
+#include <utility>\r
+#include <vector>\r
+\r
+#include <immintrin.h>\r
+#include <malloc.h>\r
+\r
+#define _BBLIB_DPDK_\r
+\r
+#ifdef _BBLIB_DPDK_\r
+#include <rte_config.h>\r
+#include <rte_malloc.h>\r
+#endif\r
+\r
+#include "gtest/gtest.h"\r
+\r
+#include "common_typedef_xran.h"\r
+\r
+#include "json.hpp"\r
+\r
+using json = nlohmann::json;\r
+\r
+#define ASSERT_ARRAY_NEAR(reference, actual, size, precision) \\r
+        assert_array_near(reference, actual, size, precision)\r
+\r
+#define ASSERT_ARRAY_EQ(reference, actual, size) \\r
+        assert_array_eq(reference, actual, size)\r
+\r
+#define ASSERT_AVG_GREATER_COMPLEX(reference, actual, size, precision) \\r
+        assert_avg_greater_complex(reference, actual, size, precision)\r
+\r
+struct BenchmarkParameters\r
+{\r
+    static long repetition;\r
+    static long loop;\r
+    static unsigned cpu_id;\r
+};\r
+\r
+struct missing_config_file_exception : public std::exception\r
+{\r
+    const char * what () const throw () override {\r
+        return "JSON file cannot be opened!";\r
+    }\r
+};\r
+\r
+struct reading_input_file_exception : public std::exception\r
+{\r
+    const char * what () const throw () override {\r
+        return "Input file cannot be read!";\r
+    }\r
+};\r
+\r
+/*!\r
+    \brief Attach current process to the selected core.\r
+    \param [in] cpu Core number.\r
+    \return 0 on success, -1 otherwise.\r
+*/\r
+int bind_to_cpu(const unsigned cpu);\r
+\r
+/*!\r
+    \brief Calculate the mean and variance from the result of the run_benchmark.\r
+    \param [in] values Vector with result values.\r
+    \return std::pair where the first element is mean and the second one is standard deviation.\r
+    \note It's not a general mean/stddev function it only works properly when feed with data from\r
+          the benchmark function.\r
+*/\r
+std::pair<double, double> calculate_statistics(const std::vector<long> values);\r
+\r
+/*!\r
+    \brief For a given number return sequence of number from 0 to number - 1.\r
+    \param [in] number Positive integer value.\r
+    \return Vector with the sorted integer numbers between 0 and number - 1.\r
+*/\r
+std::vector<unsigned> get_sequence(const unsigned number);\r
+\r
+/*!\r
+    \brief Read JSON from the given file.\r
+    \param [in] filename name of the .json file.\r
+    \return JSON object with data.\r
+    \throws missing_config_file_exception when file cannot be opened.\r
+*/\r
+json read_json_from_file(const std::string &filename);\r
+\r
+/*!\r
+    \brief Read binary data from the file.\r
+    \param [in] filename name of the binary file.\r
+    \return Pointer to the allocated memory with data from the file.\r
+    \throws std::runtime_error when memory cannot be allocated.\r
+*/\r
+char* read_data_to_aligned_array(const std::string &filename);\r
+\r
+/*!\r
+    \brief Measure the TSC on the machine\r
+    \return Number of ticks per us\r
+*/\r
+unsigned long tsc_recovery();\r
+\r
+/*!\r
+    \brief Return the current value of the TSC\r
+    \return Current TSC value\r
+*/\r
+unsigned long tsc_tick();\r
+\r
+/*!\r
+    \class KernelTests\r
+\r
+    Each test class has to inherit from KernelTests class as it provides GTest support and does a lot\r
+    of setup (including JSON) an provides useful methods to operate on loaded JSON file.\r
+    Unfortunately GTest is limited in the way that all TEST_P within the class are called for all\r
+    cases/parameters, but we usually want two different data sets for functional and performance\r
+    tests (or maybe other types of tests). Because of that to use different data sets we need to\r
+    create separate classes, hence performance and functional test are in separate classes. it adds\r
+    an extra overhead, but adds much more flexibility. init_test(...) is used to select data set from\r
+    the JSON file.\r
+\r
+    Important note on the JSON file structure. Top JSON object can have as many section (JSON\r
+    objects) as needed, but each have to have a distinct name that is used by init_test. Then\r
+    each section must contain an array of objects (test cases) where each object has a name,\r
+    parameters and references. Everything inside parameters and references can be completely custom\r
+    as it's loaded by get_input/reference_parameter function. JSON values can be either literal\r
+    values, e.g. 1, 0.001, 5e-05, etc. or filename. Depends on the get type test framework can either\r
+    read the value or load data from the file - and it happens automatically (*pff* MAGIC!).\r
+*/\r
+class KernelTests : public testing::TestWithParam<unsigned>\r
+{\r
+public:\r
+    static json conf;\r
+    static std::string test_type;\r
+\r
+    static void SetUpTestCase()\r
+    {\r
+        test_type = "None";\r
+\r
+        try\r
+        {\r
+            conf = read_json_from_file("conf.json");\r
+        }\r
+        catch(missing_config_file_exception &e)\r
+        {\r
+            std::cout << "[----------] SetUpTestCase failed: " << e.what() << std::endl;\r
+            exit(-1);\r
+        }\r
+\r
+        tsc = tsc_recovery();\r
+\r
+        if(!tsc)\r
+        {\r
+            std::cout << "[----------] SetUpTestCase failed: TSC recovery failed" << std::endl;\r
+            exit(-1);\r
+        }\r
+    }\r
+\r
+    static void TearDownTestCase()\r
+    {\r
+        /* Free resources - nothing to free at the moment */\r
+    }\r
+\r
+    static unsigned get_number_of_cases(const std::string &type)\r
+    {\r
+        try\r
+        {\r
+            json json_data = read_json_from_file("conf.json");\r
+\r
+            return json_data[type].size();\r
+        }\r
+        catch(missing_config_file_exception &e)\r
+        {\r
+            std::cout << "[----------] get_number_of_cases failed: " << e.what() << std::endl;\r
+\r
+            exit(-1);\r
+        }\r
+        catch(std::domain_error &e)\r
+        {\r
+            std::cout << "[----------] get_number_of_cases failed: " << e.what() << std::endl;\r
+            std::cout << "[----------] Use a default value: 0" << std::endl;\r
+\r
+            return 0;\r
+        }\r
+    }\r
+\r
+protected:\r
+    double division_factor = 1.0;\r
+    std::string result_units = "None";\r
+    int parallelization_factor = 1;\r
+\r
+    /*!\r
+        \brief Set division factor\r
+        \param [in] factor Division factor that divides mean and standard deviation.\r
+    */\r
+    void set_division_factor(const double factor)\r
+    {\r
+        division_factor = factor;\r
+    }\r
+\r
+    /*!\r
+        \brief Set reults units\r
+        \param [in] units Units that are displayed in the report.\r
+    */\r
+    void set_results_units(const std::string &units)\r
+    {\r
+        result_units = units;\r
+    }\r
+\r
+    /*!\r
+        \brief Set size of processed data\r
+        \param [in] size Size of processed data used to calculate module throughput.\r
+    */\r
+    void set_parallelization_factor(const int factor)\r
+    {\r
+        parallelization_factor = factor;\r
+    }\r
+\r
+    /*!\r
+        \brief Run performance test case for a given function.\r
+        \param [in] isa Used Instruction Set.\r
+        \param [in] module_name name of the tested kernel.\r
+        \param [in] function function to be tested.\r
+        \param [in] args function's arguments.\r
+    */\r
+    template <typename F, typename ... Args>\r
+    void performance(const std::string &isa, const std::string &module_name, F function,\r
+                     Args ... args) {\r
+        ASSERT_EQ(0, bind_to_cpu(BenchmarkParameters::cpu_id)) << "Failed to bind to cpu!";\r
+\r
+        const auto result = run_benchmark(function, args ...);\r
+        const auto scaled_mean = result.first / division_factor / tsc;\r
+        const auto scaled_stddev = result.second / division_factor / tsc;\r
+\r
+        print_and_store_results(isa, get_case_name(), module_name, get_case_name(), result_units,\r
+                                parallelization_factor, scaled_mean, scaled_stddev);\r
+    }\r
+\r
+    /*!\r
+        \brief Print unique test description to the results xml file\r
+        \param [in] isa Used Instruction Set.\r
+        \param [in] module_name name of the tested kernel.\r
+        \param [in] function function to be tested.\r
+    */\r
+    void print_test_description(const std::string &isa, const std::string &module_name) {\r
+        print_and_store_results(isa, get_case_name(), module_name, get_case_name(), result_units,\r
+                                parallelization_factor, 0, 0);\r
+    }\r
+\r
+    //! @{\r
+    /*!\r
+        \brief Load selected data from a JSON object.\r
+         get_input_parameter loads data from parameters section of the test case in JSON file and\r
+         get_reference_parameter does the same thing for references section.\r
+\r
+         Get parameter function uses template type to figure out how to load parameters. If type\r
+         is NOT a pointer it'll load value directly from the JSON. Otherwise path to the test\r
+         vector is expected and function will allocate memory, load data from the binary file to\r
+         this memory location and return pointer to it. For example in here we request to load\r
+         pointer to float so llrs filed is expected to be\r
+         a path to the binary file.\r
+    */\r
+    template <typename T>\r
+    T get_input_parameter(const std::string &parameter_name)\r
+    {\r
+        try\r
+        {\r
+            return get_parameter<T>("parameters", parameter_name);\r
+        }\r
+        catch (std::domain_error &e)\r
+        {\r
+            std::cout << "[----------] get_input_parameter (" << parameter_name\r
+                      << ") failed: " << e.what()\r
+                      << ". Did you mispell the parameter name?" << std::endl;\r
+            throw;\r
+        }\r
+        catch(reading_input_file_exception &e)\r
+        {\r
+            std::cout << "[----------] get_input_parameter (" << parameter_name\r
+                      << ") failed: " << e.what() << std::endl;\r
+            throw;\r
+        }\r
+    }\r
+\r
+    template <typename T>\r
+    T get_reference_parameter(const std::string &parameter_name)\r
+    {\r
+        try\r
+        {\r
+            return get_parameter<T>("references", parameter_name);\r
+        }\r
+        catch (std::domain_error &e)\r
+        {\r
+            std::cout << "[----------] get_reference_parameter (" << parameter_name\r
+                      << ") failed: " << e.what()\r
+                      << ". Did you mispell the parameter name?" << std::endl;\r
+            throw;\r
+        }\r
+        catch(reading_input_file_exception &e)\r
+        {\r
+            std::cout << "[----------] get_reference_parameter (" << parameter_name\r
+                      << ") failed: " << e.what() << std::endl;\r
+            throw;\r
+        }\r
+    }\r
+    //! @}\r
+\r
+    /*!\r
+        \brief Get name of the test case from JSON file.\r
+        \return Test'ss case name or a default name if name field is missing.\r
+    */\r
+    const std::string get_case_name()\r
+    {\r
+        try\r
+        {\r
+            return conf[test_type][GetParam()]["name"];\r
+        }\r
+        catch (std::domain_error &e)\r
+        {\r
+            std::cout << "[----------] get_case_name failed: " << e.what()\r
+                      << ". Did you specify a test name in JSON?" << std::endl;\r
+            std::cout << "[----------] Using a default name instead" << std::endl;\r
+\r
+            return "Default test name";\r
+        }\r
+    }\r
+\r
+    /*!\r
+        \brief Defines section in the conf.json that is used to load parameters from.\r
+        \param [in] type Name of the section in the JSON file.\r
+    */\r
+    void init_test(const std::string &type)\r
+    {\r
+        test_type = type;\r
+        const std::string name = get_case_name();\r
+        std::cout << "[----------] Test case: " << name << std::endl;\r
+    }\r
+\r
+private:\r
+    static unsigned long tsc;\r
+\r
+    template<typename T>\r
+    struct data_reader {\r
+        static T read_parameter(const int index, const std::string &type,\r
+                                const std::string &parameter_name)\r
+        {\r
+            return conf[test_type][index][type][parameter_name];\r
+        }\r
+    };\r
+\r
+    template<typename T>\r
+    struct data_reader<std::vector<T>> {\r
+        static std::vector<T> read_parameter(const int index, const std::string &type,\r
+                                             const std::string &parameter_name)\r
+        {\r
+            auto array_size = conf[test_type][index][type][parameter_name].size();\r
+\r
+            std::vector<T> result(array_size);\r
+\r
+            for(unsigned number = 0; number < array_size; number++)\r
+                result.at(number) = conf[test_type][index][type][parameter_name][number];\r
+\r
+            return result;\r
+        }\r
+    };\r
+\r
+    template<typename T>\r
+    struct data_reader<T*> {\r
+        static T* read_parameter(const int index, const std::string &type,\r
+                                 const std::string &parameter_name)\r
+        {\r
+            return (T*) read_data_to_aligned_array(conf[test_type][index][type][parameter_name]);\r
+        }\r
+    };\r
+\r
+    template <typename T>\r
+    T get_parameter(const std::string &type, const std::string &parameter_name)\r
+    {\r
+        return data_reader<T>::read_parameter(GetParam(), type, parameter_name);\r
+    }\r
+\r
+    void print_and_store_results(const std::string &isa,\r
+                                 const std::string &parameters,\r
+                                 const std::string &module_name,\r
+                                 const std::string &test_name,\r
+                                 const std::string &unit,\r
+                                 const int para_factor,\r
+                                 const double mean,\r
+                                 const double stddev);\r
+};\r
+\r
+/*!\r
+    \brief Run the given function and return the mean run time and stddev.\r
+    \param [in] function Function to benchmark.\r
+    \param [in] args Function's arguments.\r
+    \return std::pair where the first element is mean and the second one is standard deviation.\r
+*/\r
+template <typename F, typename ... Args>\r
+std::pair<double, double> run_benchmark(F function, Args ... args)\r
+{\r
+    std::vector<long> results((unsigned long) BenchmarkParameters::repetition);\r
+\r
+    for(unsigned int outer_loop = 0; outer_loop < BenchmarkParameters::repetition; outer_loop++) {\r
+        const auto start_time =  __rdtsc();\r
+        for (unsigned int inner_loop = 0; inner_loop < BenchmarkParameters::loop; inner_loop++) {\r
+                function(args ...);\r
+        }\r
+        const auto end_time = __rdtsc();\r
+        results.push_back(end_time - start_time);\r
+    }\r
+\r
+    return calculate_statistics(results);\r
+};\r
+\r
+/*!\r
+    \brief Assert elements of two arrays. It calls ASSERT_EQ for each element of the array.\r
+    \param [in] reference Array with reference values.\r
+    \param [in] actual Array with the actual output.\r
+    \param [in] size Size of the array.\r
+*/\r
+template <typename T>\r
+void assert_array_eq(const T* reference, const T* actual, const int size)\r
+{\r
+    for(int index = 0; index < size ; index++)\r
+    {\r
+        ASSERT_EQ(reference[index], actual[index])\r
+                          <<"The wrong number is index: "<< index;\r
+    }\r
+}\r
+\r
+/*!\r
+    \brief Assert elements of two arrays. It calls ASSERT_NEAR for each element of the array.\r
+    \param [in] reference Array with reference values.\r
+    \param [in] actual Array with the actual output.\r
+    \param [in] size Size of the array.\r
+    \param [in] precision Precision fo the comparision used by ASSERT_NEAR.\r
+*/\r
+template <typename T>\r
+void assert_array_near(const T* reference, const T* actual, const int size, const double precision)\r
+{\r
+    for(int index = 0; index < size ; index++)\r
+    {\r
+        ASSERT_NEAR(reference[index], actual[index], precision)\r
+                                <<"The wrong number is index: "<< index;\r
+    }\r
+}\r
+\r
+template <>\r
+void assert_array_near<complex_float>(const complex_float* reference, const complex_float* actual, const int size, const double precision)\r
+{\r
+    for(int index = 0; index < size ; index++)\r
+    {\r
+        ASSERT_NEAR(reference[index].re, actual[index].re, precision)\r
+                             <<"The wrong number is RE, index: "<< index;\r
+        ASSERT_NEAR(reference[index].im, actual[index].im, precision)\r
+                             <<"The wrong number is IM, index: "<< index;\r
+    }\r
+}\r
+\r
+/*!\r
+    \brief Assert average diff of two arrays. It calls ASSERT_GT to check the average.\r
+    \param [in] reference Array with reference values, interleaved IQ inputs.\r
+    \param [in] actual Array with the actual output, interleaved IQ inputs.\r
+    \param [in] size Size of the array, based on complex inputs.\r
+    \param [in] precision Precision for the comparison used by ASSERT_GT.\r
+*/\r
+template<typename T>\r
+void assert_avg_greater_complex(const T* reference, const T* actual, const int size, const double precision)\r
+{\r
+    float mseDB, MSE;\r
+    double avgMSEDB = 0.0;\r
+    for (int index = 0; index < size; index++) {\r
+        T refReal = reference[2*index];\r
+        T refImag = reference[(2*index)+1];\r
+        T resReal = actual[2*index];\r
+        T resImag = actual[(2*index)+1];\r
+\r
+        T errReal = resReal - refReal;\r
+        T errIm = resImag - refImag;\r
+\r
+        /* For some unit tests, e.g. PUCCH deomdulation, the expected output is 0. To avoid a\r
+           divide by zero error, check the reference results to determine if the expected result\r
+           is 0 and, if so, add a 1 to the division. */\r
+        if (refReal == 0 && refImag == 0)\r
+            MSE = (float)(errReal*errReal + errIm*errIm)/(float)(refReal*refReal + refImag*refImag + 1);\r
+        else\r
+            MSE = (float)(errReal*errReal + errIm*errIm)/(float)(refReal*refReal + refImag*refImag);\r
+\r
+        if(MSE == 0)\r
+            mseDB = (float)(-100.0);\r
+        else\r
+            mseDB = (float)(10.0) * (float)log10(MSE);\r
+\r
+        avgMSEDB += (double)mseDB;\r
+        }\r
+\r
+        avgMSEDB /= size;\r
+\r
+        ASSERT_GT(precision, avgMSEDB);\r
+}\r
+\r
+/*!\r
+    \brief Allocates memory of the given size.\r
+\r
+    aligned_malloc is wrapper to functions that allocate memory:\r
+    'rte_malloc' from DPDK if hugepages are defined, 'memalign' otherwise.\r
+    Size is defined as a number of variables of given type e.g. floats, rather than bytes.\r
+    It hides sizeof(T) multiplication and cast hence makes things cleaner.\r
+\r
+    \param [in] size Size of the memory to allocate.\r
+    \param [in] alignment Bytes alignment of the allocated memory. If 0, the return is a pointer\r
+                that is suitably aligned for any kind of variable (in the same manner as malloc()).\r
+                Otherwise, the return is a pointer that is a multiple of align. In this case,\r
+                it must be a power of two. (Minimum alignment is the cacheline size, i.e. 64-bytes)\r
+    \return Pointer to the allocated memory.\r
+*/\r
+template <typename T>\r
+T* aligned_malloc(const int size, const unsigned alignment)\r
+{\r
+#ifdef _BBLIB_DPDK_\r
+    return (T*) rte_malloc(NULL, sizeof(T) * size, alignment);\r
+#else\r
+#ifndef _WIN64\r
+    return (T*) memalign(alignment, sizeof(T) * size);\r
+#else\r
+    return (T*)_aligned_malloc(sizeof(T)*size, alignment);\r
+#endif\r
+#endif\r
+}\r
+\r
+/*!\r
+    \brief Frees memory pointed by the given pointer.\r
+\r
+    aligned_free is a wrapper for functions that free memory allocated by\r
+    aligned_malloc: 'rte_free' from DPDK if hugepages are defined and 'free' otherwise.\r
+\r
+    \param [in] ptr Pointer to the allocated memory.\r
+*/\r
+template <typename T>\r
+void aligned_free(T* ptr)\r
+{\r
+#ifdef _BBLIB_DPDK_\r
+    rte_free((void*)ptr);\r
+#else\r
+\r
+#ifndef _WIN64\r
+    free((void*)ptr);\r
+#else\r
+    _aligned_free((void *)ptr);\r
+#endif\r
+#endif\r
+}\r
+\r
+/*!\r
+    \brief generate random numbers.\r
+\r
+    It allocates memory and populate it with random numbers using C++11 default engine and\r
+    uniform real / int distribution (where lo_range <= x <up_range). Don't forget to free\r
+    allocated memory!\r
+\r
+    \param [in] size Size of the memory to be filled with random data.\r
+    \param [in] alignment Bytes alignment of the memory.\r
+    \param [in] distribution Distribuiton for random generator.\r
+    \return Pointer to the allocated memory with random data.\r
+*/\r
+template <typename T, typename U>\r
+T* generate_random_numbers(const long size, const unsigned alignment, U& distribution)\r
+{\r
+    auto array = (T*) aligned_malloc<char>(size * sizeof(T), alignment);\r
+\r
+    std::random_device random_device;\r
+    std::default_random_engine generator(random_device());\r
+\r
+    for(long i = 0; i < size; i++)\r
+        array[i] = (T)distribution(generator);\r
+\r
+    return array;\r
+}\r
+\r
+/*!\r
+    \brief generate random data.\r
+\r
+    It allocates memory and populate it with random data using C++11 default engine and\r
+    uniform integer distribution (bytes not floats are uniformly distributed). Don't forget\r
+    to free allocated memory!\r
+\r
+    \param [in] size Size of the memory to be filled with random data.\r
+    \param [in] alignment Bytes alignment of the memory.\r
+    \return Pointer to the allocated memory with random data.\r
+*/\r
+template <typename T>\r
+T* generate_random_data(const long size, const unsigned alignment)\r
+{\r
+    std::uniform_int_distribution<> random(0, 255);\r
+\r
+    return (T*)generate_random_numbers<char, std::uniform_int_distribution<>>(size * sizeof(T), alignment, random);\r
+}\r
+\r
+/*!\r
+    \brief generate integer random numbers.\r
+\r
+    It allocates memory and populate it with random numbers using C++11 default engine and\r
+    uniform integer distribution (where lo_range <= x < up_range). Don't forget\r
+    to free allocated memory! The result type generated by the generator should be one of\r
+    int types.\r
+\r
+    \param [in] size Size of the memory to be filled with random data.\r
+    \param [in] alignment Bytes alignment of the memory.\r
+    \param [in] lo_range Lower bound of range of values returned by random generator.\r
+    \param [in] up_range Upper bound of range of values returned by random generator.\r
+    \return Pointer to the allocated memory with random data.\r
+*/\r
+template <typename T>\r
+T* generate_random_int_numbers(const long size, const unsigned alignment, const T lo_range,\r
+                               const T up_range)\r
+{\r
+    std::uniform_int_distribution<T> random(lo_range, up_range);\r
+\r
+    return generate_random_numbers<T, std::uniform_int_distribution<T>>(size, alignment, random);\r
+}\r
+\r
+/*!\r
+    \brief generate real random numbers.\r
+\r
+    It allocates memory and populate it with random numbers using C++11 default engine and\r
+    uniform real distribution (where lo_range <= x <up_range). Don't forget to free\r
+    allocated memory! The result type generated by the generator should be one of\r
+    real types: float, double or long double.\r
+\r
+    \param [in] size Size of the memory to be filled with random data.\r
+    \param [in] alignment Bytes alignment of the memory.\r
+    \param [in] lo_range Lower bound of range of values returned by random generator.\r
+    \param [in] up_range Upper bound of range of values returned by random generator.\r
+    \return Pointer to the allocated memory with random data.\r
+*/\r
+template <typename T>\r
+T* generate_random_real_numbers(const long size, const unsigned alignment, const T lo_range,\r
+                                const T up_range)\r
+{\r
+    std::uniform_real_distribution<T> distribution(lo_range, up_range);\r
+\r
+    return generate_random_numbers<T, std::uniform_real_distribution<T>>(size, alignment, distribution);\r
+}\r
+\r
+#endif //XRANLIB_COMMON_HPP\r