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  (ein Pixel weniger als maximal möglich)
- </p>
- <p>
+
+
+
+ Konkretes Rechenbeispiel:
+
+
+ pxWidth = 99999 (ein Pixel weniger als maximal möglich)
+
+
changedMetric = MAX_ZOOM = 2000000
- </p>
- <p>
- ⟹ conformWindowToMetric() errechnet
- </p>
- <p>
- dur = 49999+1/2 µTick
- </p>
- <p style="margin-top: 0px; margin-bottom: 0px; margin-left: 0px; margin-right: 0px; text-indent: 0px">
- �� das wird abgerundet auf 49999 µTick
- </p>
- <p>
- ⟹ 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">
- ⟹ das ist <b>definitiv größer</b> 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 @@
+
+
+