diff --git a/src/lib/time.c b/src/lib/time.c index f81a2cfc7..6f62a8ada 100644 --- a/src/lib/time.c +++ b/src/lib/time.c @@ -65,10 +65,10 @@ lumiera_tmpbuf_print_time (gavl_time_t time) static double calculate_quantisation (gavl_time_t time, double grid, gavl_time_t origin) { - double val = time; + double val = time; //////TODO this solution doesn't work due to precission loss! val -= origin; val /= grid; - return floor (val); + return floor (val); //////TODO need a hand coded floor-function for integers } static double diff --git a/src/tool/try.cpp b/src/tool/try.cpp index af04c66eb..16716ccae 100644 --- a/src/tool/try.cpp +++ b/src/tool/try.cpp @@ -16,27 +16,32 @@ // 12/9 - tracking down a strange "warning: type qualifiers ignored on function return type" // 1/10 - can we determine at compile time the presence of a certain function (for duck-typing)? // 4/10 - pretty printing STL containers with python enabled GDB? +// 1/11 - exploring numeric limits //#include -//#define LUMIERA_LOGGING_CXX -//#include "include/logging.h" -//#include "include/nobugcfg.h" #include //#include #include -#include -#include -#include +//#include +#include + +#include +using boost::format; //using std::rand; using std::string; using std::cout; using std::endl; +void +checkDiv(int lhs, int rhs) + { + cout << format ("%f / %f = %f \n") % lhs % rhs % (lhs / rhs); + } int main (int, char**) @@ -44,21 +49,26 @@ main (int, char**) // NOBUG_INIT; - std::string str = "hullo wöld"; - std::vector vec (1000); + checkDiv (8,4); + checkDiv (9,4); + checkDiv (-8,4); + checkDiv (-9,4); + checkDiv (8,-4); + checkDiv (9,-4); + checkDiv (-8,-4); + checkDiv (-9,-4); - for (uint i = 0; i < vec.size(); ++i) - vec[i] = i; - cout << str << "\n.gulp.\n"; + int64_t muks = std::numeric_limits::max(); + muks -= 5; + double murks(muks); - // Note: when selecting the string or the vector in the Eclipse variables view - // (or when issuing "print str" at the GDB prompt), the GDB python pretty-printer - // should be activated. Requires an python enabled GDB (>6.8.50). Debian/Lenny isn't enough, - // but compiling the GDB package from Debian/Squeeze (GDB-7.1) is straightforward. - // Moreover, you need to check out and install the python pretty-printers and - // you need to activate them through your ~/.gdbinit - // see http://sourceware.org/gdb/wiki/STLSupport + cout << format("%f // %f || %g \n") % muks % murks % std::numeric_limits::epsilon(); + + int64_t glucks = murks; + cout << glucks <gridAlign(TimeValue(testPoint)); + return int(quantised); + } + }; + + void + coverQuantisationStandardCases() + { + TestQuant q0; + TestQuant q1(1); + + CHECK ( 6 == q0.quant(7) ); + CHECK ( 6 == q0.quant(6) ); + CHECK ( 3 == q0.quant(5) ); + CHECK ( 3 == q0.quant(4) ); + CHECK ( 3 == q0.quant(3) ); + CHECK ( 0 == q0.quant(2) ); + CHECK ( 0 == q0.quant(1) ); + CHECK ( 0 == q0.quant(0) ); + CHECK (-3 == q0.quant(-1)); + CHECK (-3 == q0.quant(-2)); + CHECK (-3 == q0.quant(-3)); + CHECK (-6 == q0.quant(-4)); + + CHECK ( 6 == q1.quant(7) ); + CHECK ( 3 == q1.quant(6) ); + CHECK ( 3 == q1.quant(5) ); + CHECK ( 3 == q1.quant(4) ); + CHECK ( 0 == q1.quant(3) ); + CHECK ( 0 == q1.quant(2) ); + CHECK ( 0 == q1.quant(1) ); + CHECK (-3 == q1.quant(0) ); + CHECK (-3 == q1.quant(-1)); + CHECK (-3 == q1.quant(-2)); + CHECK (-6 == q1.quant(-3)); + CHECK (-6 == q1.quant(-4)); + } + + + void + coverQuantisationCornerCases() + { + FixedFrameQuantiser case1 (1, Time::MIN); + CHECK (Time(0) == case1.gridAlign(Time::MIN)); + CHECK (Time(0) == case1.gridAlign(Time::MIN + TimeValue(1) )); + CHECK (Time(1) == case1.gridAlign(Time::MIN + Time(1) )); + CHECK (Time::MAX - Time(1) > case1.gridAlign( Time(-1) )); + CHECK (Time::MAX - Time(1) <= case1.gridAlign( Time (0) )); + CHECK (Time::MAX > case1.gridAlign( Time (0) )); + CHECK (Time::MAX == case1.gridAlign( Time(+1) )); + } }; diff --git a/wiki/renderengine.html b/wiki/renderengine.html index 27ecbe2da..ed5b6a57c 100644 --- a/wiki/renderengine.html +++ b/wiki/renderengine.html @@ -4192,17 +4192,55 @@ Viewed as a micro program, the processing patterns are ''weak typed'' &mdash
a given Render Engine configuration is a list of Processors. Each Processor in turn contains a Graph of ProcNode.s to do the acutal data processing. In order to cary out any calculations, the Processor needs to be called with a StateProxy containing the state information for this RenderProcess
 
-
+
The Quantiser implementation works by determining the grid interval containing a given raw time.
+These grid intervals are denoted by ordinal numbers (frame numbers), with interval #0 starting at the grid's origin and negative ordinals allowed.
 
 !frame quantisation convention
-Within Lumiera, there is a fixed convention how these frame intervals are to be defined (&rArr; time handling RfC)
+Within Lumiera, there is a fixed convention how these frame intervals are to be defined (&rArr; [[time handling RfC|http://staging.lumiera.org/documentation/devel/rfc/TimeHandling.html]])
 [img[Lumiera's frame quantisation convention|draw/FramePositions1.png]]
+Especially, this convention is agnostic of the actual zero-point of the scale and allows direct length calculations and seamless sequences of intervals.
+The //nominal coordinate// of an interval is also the starting point -- for automation keys frames we'll utilise special provisions.
 
 !range limitation problems
-because times are represented as 64bit integers, the time points addressable within a given scale grid can be limited, compared with time points addressable through raw (internal) time values. As an extreme example, consider a time scale with origin at Time::MIN -- such a scale is unable to represent any value above zero, because the resulting coordinates would exceed the range of the 64bit integer.
+because times are represented as 64bit integers, the time points addressable within a given scale grid can be limited, compared with time points addressable through raw (internal) time values. As an extreme example, consider a time scale with origin at Time::MIN -- such a scale is unable to represent any of the original scale's value above zero, because the resulting coordinates would exceed the range of the 64bit integer. Did I mention that 64bit micro ticks can represent about 300000 years?
 
-To avoid problems with larger intermediate values, the actual calculations are performed with doubles, which are clipped to the allowed value range prior to casting back to the integral data type. In all practically relevant cases, there is no danger of imprecisions or rounding errors, because the quantisation includes a floor operation. (Hypothetically, an imprecision could arise through extinction, when calculating the offset from the origin; but in practice this is irrelevant, assumed that the conversion 64bit integer to double yields reproducible double values)
+Now the actual problem is that using 64bit integers already means pushing to the limit. There is no easy escape hatch, like using a larger scale data type for intermediaries -- it //is// the largest built-in type. Basically we're touching the topic of ''safe integer arithmetics'' here, which is frequently discussed as a security concern. The situation is as follows:
+* every programmer promises "I'll do the checks when necessary" -- just to forget doing so in practice then.
+* for C programming, the situation is hopeless. Calling functions for simple arithmetics is outright impractical -- that won't happen volountarily
+* it is possible to build a ~SafeInt datatype in C++ though. While theoretically fine, in practice this also creates a host of problems:
+** actually detecting all cases and coding the checks is surprisingly hard and intricate.
+** the attempt to create a smooth integration with the built-in data types drives us right into one of the most problematic areas of C++
+** the performance hit is considerable (factor 2 - 4)  -- again luring people into "being clever".
+
+There is an existing [[SafeInt class by David LeBlanc|http://safeint.codeplex.com/]], provided by Microsoft through the ~CodePlex platform. It is part of the libraries shipped with ~VisualStudio and extensively used in Office 2010 and Windows, but also provided under a somewhat liberal license (you may use it but any derived work has to reproduce copyright and usage terms). Likely this situation also hindered the further [[development|http://thread.gmane.org/gmane.comp.lib.boost.devel/191010]] of a comparable library in boost (&rarr; [[vault|https://svn.boost.org/trac/boost/wiki/LibrariesUnderConstruction#Boost.SafeInt]]).
+
+!!!solution possibilities
+;abort operation
+:an incriminating calculation need to be detected somehow (see below).
+:the whole usage context gets aborted by excaption in case of an alarm, similar to an out of memory...
+:thus immediate corruption is avoided, but the user has to realise and avoid the general situation.
+;limit values
+:provide a limiter to kick in after detecting an alarm (see below).
+:time values will just stick to the maximum/minimum boundary value....
+:the rest of the application needs to be prepared for timing calculations to return degenerate results
+;~SafeInt
+:base ~TimeValue on a ~SafeInt<gavl_time_t>
+:this way we could easily guarantee for detecting any //situation.//
+:of course the price is the performance hit on all timing calculations.
+:Moreover -- if we want to limit values instead of raising an exception, we'd need to write our own ~SaveInt.
+;check some operations
+:time values are mostly immutable, thus it would likely be sufficient only to check some strategically important points
+:#parsing or de-serialising
+:#calculations in ~TimeVar
+:#quantisation
+:#build Time from framecount
+;limit time range
+:because the available time range is so huge, it wouldn't hurt to reduce it by one or two decimals
+:this way both the implementation of the checks could be simplified and the probability of an overflow reduced
+;ignore the problem
+:again, because of the huge time range, the problem could generally be deemed irrelevant
+:we could apply a limiter to enforce a reduced time range just in the quantisation and evaluation of timecodes