diff --git a/src/lib/util.hpp b/src/lib/util.hpp index cc1d18909..c8ea896ed 100644 --- a/src/lib/util.hpp +++ b/src/lib/util.hpp @@ -56,6 +56,27 @@ namespace util { return n1 < n2? N1(n2) : n1; } + /** floor function for integer arithmetics. + * Unlike the built-in integer division, this function + * always rounds towards the next \em smaller integer, + * even for negative numbers. + * @warning floor on doubles performs way better + * @see UtilFloordiv_test + */ + template + inline LI + floordiv (LI num, LI den) + { + if (0 < (num^den)) + return num/den; + else + { + ldiv_t res = ldiv(num,den); + return (res.rem)? res.quot-1 + : res.quot; + } + } + /** a family of util functions providing a "no value whatsoever" test. Works on strings and all STL containers, includes NULL test for pointers */ diff --git a/src/tool/try.cpp b/src/tool/try.cpp index ef05aaa97..f73e9dd6f 100644 --- a/src/tool/try.cpp +++ b/src/tool/try.cpp @@ -27,6 +27,7 @@ #include //#include #include +#include #include @@ -37,10 +38,31 @@ using std::string; using std::cout; using std::endl; +long +floordiv (long num, long den) + { + if (0 < (num^den)) + return num/den; + else + { + ldiv_t res = ldiv(num,den); + return (res.rem)? res.quot-1 + : res.quot; + } + } + +long +floordiv2 (long num, long den) + { + ldiv_t res = ldiv(num,den); + return (0 >= res.quot && res.rem)? res.quot-1 + : res.quot; + } + void checkDiv(int lhs, int rhs) { - cout << format ("%f / %f = %f \n") % lhs % rhs % (lhs / rhs); + cout << format ("%f / %f = %f \tfloor=%f floordiv=%f \n") % lhs % rhs % (lhs / rhs) % floor(double(lhs)/rhs) % floordiv2(lhs,rhs); } int @@ -57,6 +79,14 @@ main (int, char**) checkDiv (9,-4); checkDiv (-8,-4); checkDiv (-9,-4); + + checkDiv (0,4); + checkDiv (0,-4); + checkDiv (1,4); + checkDiv (1,-4); + checkDiv (-1,4); + checkDiv (-1,-4); + int64_t muks = std::numeric_limits::max(); diff --git a/tests/40components.tests b/tests/40components.tests index ac27d14bf..0afb978db 100644 --- a/tests/40components.tests +++ b/tests/40components.tests @@ -1083,6 +1083,11 @@ return: 0 END +TEST "integer rounding utility" UtilFloordiv_test < + + This program is free software; you can redistribute it and/or + modify it under the terms of the GNU General Public License as + published by the Free Software Foundation; either version 2 of + the License, or (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + +* *****************************************************/ + + +#include "lib/test/run.hpp" +#include "lib/util.hpp" + +#include +#include +#include +#include + +using ::Test; +using std::cout; +using std::rand; +using boost::format; + + +namespace util { +namespace test { + + + + namespace{ // Test data and operations + + const uint NUM_ELMS_PERFORMANCE_TEST = 50000000; + const uint NUMBER_LIMIT = 1 << 30; + + typedef std::vector VecI; + + VecI + buildTestNumberz (uint cnt) + { + VecI data; + for (uint i=0; i= res.quot && res.rem)? res.quot-1 + : res.quot; + } + + } // (End) test data and operations + + + + /********************************************************************** + * @test Evaluate a custom built integer floor function. + * This function is crucial for Lumiera's rule of quantisation + * of time values into frame intervals. This rule requires time + * points to be rounded towards the next lower frame border always, + * irrespective of the relation to the actual time origin. + * Contrast this to the built-in integer division operator, which + * truncates towards zero. + * + * @note if invoked with an non empty parameter, this test performs + * some interesting timing comparisons, which initially were + * used to tweak the implementation a bit. + * @see util.hpp + * @see QuantiserBasics_test + */ + class UtilFloordiv_test : public Test + { + + virtual void + run (Arg arg) + { + verifyBehaviour (); + + if (arg.size()) + runPerformanceTest(); + } + + + void + verifyBehaviour () + { + CHECK ( 3 == floordiv ( 12,4)); + CHECK ( 2 == floordiv ( 11,4)); + CHECK ( 2 == floordiv ( 10,4)); + CHECK ( 2 == floordiv ( 9,4)); + CHECK ( 2 == floordiv ( 8,4)); + CHECK ( 1 == floordiv ( 7,4)); + CHECK ( 1 == floordiv ( 6,4)); + CHECK ( 1 == floordiv ( 5,4)); + CHECK ( 1 == floordiv ( 4,4)); + CHECK ( 0 == floordiv ( 3,4)); + CHECK ( 0 == floordiv ( 2,4)); + CHECK ( 0 == floordiv ( 1,4)); + CHECK ( 0 == floordiv ( 0,4)); + CHECK (-1 == floordiv (- 1,4)); + CHECK (-1 == floordiv (- 2,4)); + CHECK (-1 == floordiv (- 3,4)); + CHECK (-1 == floordiv (- 4,4)); + CHECK (-2 == floordiv (- 5,4)); + CHECK (-2 == floordiv (- 6,4)); + CHECK (-2 == floordiv (- 7,4)); + CHECK (-2 == floordiv (- 8,4)); + CHECK (-3 == floordiv (- 9,4)); + CHECK (-3 == floordiv (-10,4)); + CHECK (-3 == floordiv (-11,4)); + CHECK (-3 == floordiv (-12,4)); + } + + + + /** @test timing measurements to compare implementation details. + * This test uses a sequence of random integers, where the values + * used as denominator are ensured not to be zero. + * + * \par measurement results + * My experiments (AMD Athlon-64 4200 X2) gave me + * the following timing measurements in nanoseconds: + * + * Verification.......... 127.7 + * Integer_div........... 111.7 + * double_floor.......... 74.8 + * floordiv_int.......... 112.7 + * floordiv_long......... 119.8 + * floordiv_int64_t...... 121.4 + * floordiv_long_alt..... 122.7 + * + * These figures are the average of 6 runs with 50 million + * iterations each (as produced by this function) + * + * \par conclusions + * The most significant result is the striking performance of the + * fpu based calculation. Consequently, integer arithmetics should + * only be used when necessary due to resolution requirements, as + * is the case for int64_t based Lumiera Time values, which require + * a precision beyond the 16 digits provided by double. + * Besides that, we can conclude that the additional tests and + * adjustment of the custom floordiv only creates a slight overhead + * compared to the built-in integer div function. The comparison + * with the \c floordiv instantiation is largely moot, because + * this internally calls \c fdiv on values promoted to long. Another + * oddity in the same category is the slightly better performance + * of long over int64_t. Also, the alternative formulation of + * the function, which uses the \c fdiv() function also to divide + * the positive results, performs only slightly worse. So this + * actual implementation was chosen mainly because it seems + * to state its intent more clearly in code. + */ + void + runPerformanceTest () + { + VecI testdata = buildTestNumberz (2*NUM_ELMS_PERFORMANCE_TEST); + typedef VecI::const_iterator I; + + clock_t start(0), stop(0); + format resultDisplay("timings(%s)%|30T.|%5.3fsec\n"); + +#define START_TIMINGS start=clock(); +#define DISPLAY_TIMINGS(ID) \ + stop = clock(); \ + cout << resultDisplay % STRINGIFY (ID) % (double(stop-start)/CLOCKS_PER_SEC) ; + + START_TIMINGS + for (I ii =testdata.begin(); ii!=testdata.end(); ) + { + int num = *ii; + ++ii; + int den = *ii; + ++ii; + CHECK (floor(double(num)/den) == floordiv(num,den)); + } + DISPLAY_TIMINGS (Verification) + + START_TIMINGS + for (I ii =testdata.begin(); ii!=testdata.end(); ) + { + integerDiv (*ii++, *ii++); + } + DISPLAY_TIMINGS (Integer_div) + + START_TIMINGS + for (I ii =testdata.begin(); ii!=testdata.end(); ) + { + floor (double(*ii++) / *ii++); + } + DISPLAY_TIMINGS (double_floor) + + START_TIMINGS + for (I ii =testdata.begin(); ii!=testdata.end(); ) + { + floordiv (*ii++, *ii++); + } + DISPLAY_TIMINGS (floordiv_int) + + START_TIMINGS + for (I ii =testdata.begin(); ii!=testdata.end(); ) + { + floordiv (long(*ii++), long(*ii++)); + } + DISPLAY_TIMINGS (floordiv_long) + + START_TIMINGS + for (I ii =testdata.begin(); ii!=testdata.end(); ) + { + floordiv (int64_t(*ii++), int64_t(*ii++)); + } + DISPLAY_TIMINGS (floordiv_int64_t) + + START_TIMINGS + for (I ii =testdata.begin(); ii!=testdata.end(); ) + { + floordiv_alternate (*ii++, *ii++); + } + DISPLAY_TIMINGS (floordiv_long_alt) + } + }; + + + + + LAUNCHER (UtilFloordiv_test, "unit common"); + + +}} // namespace util::test