diff --git a/src/lib/rational.hpp b/src/lib/rational.hpp index 6a7eef09b..9fb301024 100644 --- a/src/lib/rational.hpp +++ b/src/lib/rational.hpp @@ -42,6 +42,22 @@ ** conversion using `rational_cast (fraction)` is necessary, which ** performs the division of `numerator/denominator` in the target value domain. ** + ** # Perils of fractional arithmetics + ** + ** While the always precise results of integral numbers might seem compelling, the + ** danger of _numeric overflow_ is significantly increased by fractional computations. + ** Most notably, this danger is *not limited to large numbers*. Adding two fractional + ** number requires multiplications with both denominators, which can overflow easily. + ** Thus, for every given fractional number, there is a class of »dangerous counterparts« + ** which can not be added without derailing the computation, leading to arbitrary wrong + ** results without detectable failure. And these problematic counterparts are distributed + ** _over the whole valid numeric range._ To give an extreme example, any numbers of the + ** form `n / INT_MAX` can not be added or subtracted with any other rational number > 1, + ** while being themselves perfectly valid and representable. + ** \par rule of thumb + ** Use fractional arithmetics only where it is possible to control the denominators involved. + ** Never use them for computations drawing from arbitrary (external) input. + ** ** @see zoom-window.hpp ** @see timevalue.hpp */ @@ -64,11 +80,11 @@ namespace util { } // namespace util -/** +/** * user defined literal for constant rational numbers. * \code * Rat twoThirds = 2_r/3; - * \endcode + * \endcode */ inline util::Rat operator""_r (unsigned long long num) diff --git a/src/stage/model/zoom-window.hpp b/src/stage/model/zoom-window.hpp index b0709a50e..af38a9f53 100644 --- a/src/stage/model/zoom-window.hpp +++ b/src/stage/model/zoom-window.hpp @@ -124,6 +124,33 @@ namespace model { { return FSecs{_raw(timeVal), TimeValue::SCALE}; } + + /** + * @return `true` if the given duration can be represented cleanly as µ-ticks. + * @todo should likewise be member of a FSecs wrapper type... + */ + inline bool + isMicroGridAligned (FSecs duration) + { + return 0 == (duration.numerator() * Time::SCALE) + % duration.denominator(); + } + + /////////////////////////////////////////////////////////////////////////////////////////////////////////TICKET #1261 : why the hell did I define time entities to be immutable. Looks like a "functional programming" fad in hindsight + /** @todo we need these only because the blurry distinction between + * lib::time::TimeValue and lib::time::Time, which in turn is caused + * by a lack of clarity in the fundamental conception (see #1261) + */ + inline TimeVar + operator+ (Time const& tval, TimeVar const& tvar) + { + return TimeVar(tval) += tvar; + } + inline TimeVar + operator- (Time const& tval, TimeVar const& tvar) + { + return TimeVar(tval) -= tvar; + } } @@ -136,7 +163,6 @@ namespace model { const uint MAX_PX_WIDTH{1000000}; const FSecs MAX_TIMESPAN{_FSecs(Time::MAX-Time::MIN)}; const FSecs MICRO_TICK{1_r/Time::SCALE}; - const Rat FRACT_ULP{1_r/std::numeric_limits::max()}; } @@ -365,14 +391,17 @@ namespace model { FSecs dur{afterWin_-startWin_}; uint pxWidth = rational_cast (px_per_sec_*dur); dur = Rat(pxWidth) / changedMetric; - dur += MICRO_TICK - FRACT_ULP; // prefer bias towards increased window instead of increased metric dur = min (dur, MAX_TIMESPAN); dur = max (dur, MICRO_TICK); // prevent window going void - if (startWin_<= Time::MAX - Time{dur}) - afterWin_ = startWin_ + Time{dur}; + TimeVar timeDur{dur}; + // prefer bias towards increased window instead of increased metric + if (not isMicroGridAligned (dur)) + timeDur = timeDur + TimeValue(1); + if (startWin_<= Time::MAX - timeDur) + afterWin_ = startWin_ + timeDur; else { - startWin_ = Time::MAX - Time{dur}; + startWin_ = Time::MAX - timeDur; afterWin_ = Time::MAX; } // re-check metric to maintain precise pxWidth @@ -559,10 +588,10 @@ namespace model { return 1_r/2; // then anchor zooming in the middle // use a 3rd degree parabola to favour positions in the middle - FSecs posFactor = FSecs{startWin_-startAll_} / possibleRange; - posFactor = (2*posFactor - 1); // -1 ... +1 - posFactor = posFactor*posFactor*posFactor; // -1 ... +1 but accelerating towards boundraries - posFactor = (posFactor + 1) / 2; // 0 ... 1 + Rat posFactor = FSecs{startWin_-startAll_} / possibleRange; + posFactor = (2*posFactor - 1); // -1 ... +1 + posFactor = posFactor*posFactor*posFactor; // -1 ... +1 but accelerating towards boundaries + posFactor = (posFactor + 1) / 2; // 0 ... 1 return posFactor; } diff --git a/tests/stage/model/zoom-window-test.cpp b/tests/stage/model/zoom-window-test.cpp index 6838ed401..a8faa918c 100644 --- a/tests/stage/model/zoom-window-test.cpp +++ b/tests/stage/model/zoom-window-test.cpp @@ -28,6 +28,7 @@ #include "lib/test/run.hpp" #include "lib/test/test-helper.hpp" #include "stage/model/zoom-window.hpp" +#include "lib/format-cout.hpp"//////////////TODO @@ -68,7 +69,7 @@ namespace test { CHECK (FSecs(10)/3 == 10_r/3); // _r is a user defined literal to denote 64-bit fractional CHECK (Rat(10,3) == 10_r/3); CHECK (Rat(10,3) == boost::rational(10,3)); // using Rat = boost::rational - CHECK (rational_cast (10_r/3) == 3.33333f); // rational_cast calculates division after type conversion + CHECK (rational_cast (10_r/3) == 3.3333333f); // rational_cast calculates division after type conversion verify_simpleUsage(); verify_setup(); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 56f681c9e..695ee5845 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -38861,7 +38861,7 @@ - + @@ -38926,51 +38926,46 @@ -<html> - <head> + + - - </head> - <body> - <p> - <u>Konkretes Rechenbeispiel:</u> - </p> - <p> - pxWidth = 99999&#160;&#160;(ein Pixel weniger als maximal m&#246;glich) - </p> - <p> + + +

+ Konkretes Rechenbeispiel: +

+

+ pxWidth = 99999  (ein Pixel weniger als maximal möglich) +

+

changedMetric = MAX_ZOOM = 2000000 - </p> - <p> - &#10233; conformWindowToMetric() errechnet - </p> - <p> - dur = 49999+1/2 &#181;Tick - </p> - <p style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; text-indent: 0px"> - &#55358;&#56370; das wird abgerundet auf 49999 &#181;Tick - </p> - <p> - &#10233; conformMetricToWindow() errechnet - </p> - <p> +

+

+ ⟹ conformWindowToMetric() errechnet +

+

+ dur = 49999+1/2 µTick +

+

+ �� das wird abgerundet auf 49999 µTick +

+

+ +

+

+ ⟹ conformMetricToWindow() errechnet +

+

adjMetric = 99999 / (49999/1000000) = 2000020,0004000080001600032 - </p> - <p style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; text-indent: 0px"> - &#10233; das ist <b>definitiv gr&#246;&#223;er</b>&#160;als MAX_ZOOM - </p> - <p> +

+

- </p> - <p> - - </p> - <p> - - </p> - </body> -</html> - +

+

+ ⟹ das ist definitiv größer als MAX_ZOOM +

+ +
@@ -38992,7 +38987,8 @@
- + + @@ -39014,11 +39010,16 @@ - + - - - + + + + + + + + @@ -39029,8 +39030,288 @@

- - + + + + + + + + + +

+ es kommt eine negative Zahl heraus: IntMax * Scale ⟼ -Scale  (im Nenner) +

+ +
+ +
+ + + + + + + + + + + + +

+ läßt sich ein »unbeherrschbarer Summand« konstruieren +

+ +
+ +
+ + +
+ + + + + + + + + +

+ da wird die Rechnung schwierig, denn ich kann es nicht ohne Weiteres mathematisch greifen.... +

+ +
+ + + + + + + + +

+ Ansatz mit Unbekannten +

+

+ 1/S - 1/M = x / I  < 1/S +

+ +
+ + + + + + +

+ Ansatz mit Unbekannten führt auf eine nicht-lineare Gleichung, welche + die beiden Unbekannten verknüpft.... +

+

+ mit ULP = 1/M  und I (IntMax) und S (µTick Scale) setze ich an: +

+

+ +

+

+ �� 1/s - 1/M = x / I +

+

+ +

+

+ denn einen Ausdruck x/I kann man grade noch repräsentieren in + fraktionaler Arithmetik. +

+

+ Das fürhrt dann leider zu einger Gleichung, die nicht besonders sexy daherkommt +

+

+
+ �� I·S = I·M - x·M·S +

+

+ +

+

+ also +

+
    +
  • +  x = (I·M - I·S ) / (M·S) = (M - S) / (M·S/I) = I · (M - S)/(M·S) +
  • +
  • +  M = I·S / (I - x·S) = S / (1 - x·S/I) +
  • +
+

+ +

+

+ +

+ +
+
+ + + + + + + + + +

+ denn für x ≔ I/S  ergibt sich x/I = 1/s +

+ +
+ + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +

+ empirsche Kontrolle +

+

+     const Rat UU = Rat(X/I); +

+

+     SHOW_EXPR (UU); +

+

+     SHOW_EXPR ((UU < MICRO_TICK)); +

+

+     SHOW_EXPR ((Rat(X+1)/I < MICRO_TICK)); +

+

+ +

+

+ Ausgabe... +

+

+ Probe rational_cast<int64_t>(LIM) ? = 9223372036854 +

+

+ Probe UU ? = 9223372036854/9223372036854775807 +

+

+ Probe (UU < MICRO_TICK) ? = true +

+

+ Probe (Rat(X+1)/I < MICRO_TICK) ? = false +

+

+ +

+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + +
    +
  • + Wenn der Nenner selbst S ist +
  • +
  • + Wenn der Nenner ein Teiler von S ist +
  • +
  • + wenn der Nenner ein Teiler von S · pxWidth ist +
  • +
+ +
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -39046,6 +39327,9 @@ + + + @@ -55947,7 +56231,7 @@ - + @@ -56031,6 +56315,9 @@ + + +