diff --git a/src/lib/par/scale.hpp b/src/lib/par/scale.hpp index 7879202eb..5e4123c84 100644 --- a/src/lib/par/scale.hpp +++ b/src/lib/par/scale.hpp @@ -42,6 +42,7 @@ namespace lib { namespace par { namespace err = lumiera::error; + using std::abs; using util::_Fmt; using std::optional; @@ -79,6 +80,7 @@ namespace par { bool isValid() const; bool isFactor() const; bool isCyclic() const; + bool isLimited() const; bool isLogarithmic() const; /* ===== conforming operations ===== */ @@ -125,7 +127,8 @@ namespace par { and (not isCyclic() or (minVal and maxVal and *minVal < *maxVal - and VAL(0) < cyclicLim) + and VAL(0) < cyclicLim + and abs (*maxVal) <= cyclicLim) ) ; } @@ -146,6 +149,14 @@ namespace par { return VAL(0) != cyclicLim; } + template + inline bool + Scale::isLimited() const + { + return (maxVal or minVal) + and not isCyclic(); + } + template inline bool Scale::isLogarithmic() const diff --git a/src/lib/par/type-handler.hpp b/src/lib/par/type-handler.hpp index 1094c0a2f..b5f67b32d 100644 --- a/src/lib/par/type-handler.hpp +++ b/src/lib/par/type-handler.hpp @@ -154,20 +154,13 @@ _Pragma("GCC diagnostic pop") template concept number = integral or floating_point; - - /** - * the best-precision floating point type to carry out - * a numeric operation to combine two values - * @note the result type is always some floating point, - * since the goal is to use the exponential / logarithm family. - * @todo 9/26 we _could_ go from integral to long double for 64bit, - * yet it is not clear if we ever need that tiny bit of extra precision... - */ - template - using CommonComputeType = std::conditional_t and integral, double - , common_type_t>; - + + namespace { + static_assert (64 <= std::numeric_limits::digits + ,"long double type can not represent uint64_t exactly"); + + /** @internal decision logic for combining numeric parameters */ template auto @@ -190,8 +183,21 @@ _Pragma("GCC diagnostic pop") } } - static_assert (64 <= std::numeric_limits::digits - ,"long double type can not represent uint64_t exactly"); + /** @internal logic to pick a suitable floating-point for intermediary computations */ + template + auto + selectCommonComputeType() + { + if constexpr (is_integral_v and is_integral_v) + { // ensure integral precision is retained to the degree possible... + if constexpr (8 > sizeof(V) and 8 > sizeof(W)) + return std::type_identity{}; + else + return std::type_identity{}; + } + else + return std::type_identity>{}; + } } /** @@ -202,8 +208,17 @@ _Pragma("GCC diagnostic pop") * - in the remaining problematic case(s), long double is used, as it can accommodate * the full 64bit value range without loss on platforms relevant for Lumiera. */ - template - using NumberJoinType = typename decltype(selectNumberJoinType())::type; + template + using NumberJoinType = typename decltype(selectNumberJoinType())::type; + + /** + * best-precision floating point type to carry out + * a numeric operation to transform and adapt value scales. + * @note the result type is always some floating point, + * since the goal is to use the exponential / logarithm family. + */ + template + using CommonComputeType = typename decltype(selectCommonComputeType())::type; diff --git a/tests/16parameter.tests b/tests/16parameter.tests index 9e1a93c40..dc8b34a41 100644 --- a/tests/16parameter.tests +++ b/tests/16parameter.tests @@ -17,7 +17,7 @@ return: 0 END -PLANNED "Parameter value scales" ParameterScale_test << END +TEST "Parameter value scales" ParameterScale_test << END return: 0 END diff --git a/tests/lib/par/parameter-scale-test.cpp b/tests/lib/par/parameter-scale-test.cpp index 68d127641..b4b3196c0 100644 --- a/tests/lib/par/parameter-scale-test.cpp +++ b/tests/lib/par/parameter-scale-test.cpp @@ -21,13 +21,6 @@ #include "lib/par/scale.hpp" #include "test/diagnostic-output.hpp"/////////////TODO -//#include -//#include -//#include - -//using std::string; -//using std::vector; -//using std::swap; using test::roughEQ; @@ -35,8 +28,6 @@ namespace lib { namespace par { namespace test{ -// using lumiera::error::LUMIERA_ERROR_LOGIC; - namespace {//Test helpers.... template @@ -59,7 +50,13 @@ namespace test{ /**************************************************************************//** - * @test cover properties of generic parameter containers. + * @test demonstrate features and verify behaviour of value Scale definitions. + * - a par::Scale is a descriptor record with some feature configuration settings + * - some combination of setting marks specific scale features to be present + * - values can be _conformed_ to comply with the scale constraints + * - a Scale can be configured to maintain cyclic wrapping values + * - values defined on different scales can be _joined_ (numerically combined) + * - scales can be defined to be logarithmic, and work together with linear scales. */ class ParameterScale_test : public Test { @@ -68,6 +65,7 @@ namespace test{ run (Arg) { simpleUsage(); + verify_Predicate(); verify_Conforming(); verify_cyclicScale(); verify_valueJoining(); @@ -88,6 +86,51 @@ namespace test{ } + /** @test verify the feature detection and descriptor predicates */ + void + verify_Predicate() + { + Scale scale; + + CHECK (scale.isValid()); + CHECK (not scale.isCyclic()); + CHECK (not scale.isLimited()); + CHECK (not scale.isLogarithmic()); + + scale.minVal = -5; + CHECK (scale.isValid()); + CHECK (not scale.isCyclic()); + CHECK ( scale.isLimited()); + + scale.cyclicLim = 10; + CHECK (not scale.isValid()); + CHECK ( scale.isCyclic()); + CHECK (not scale.isLimited()); + + scale.maxVal = +5; + CHECK ( scale.isValid()); + CHECK ( scale.isCyclic()); + CHECK (not scale.isLimited()); + + CHECK (not scale.isFactor()); + scale.neutral = 2; + CHECK (not scale.isFactor()); + scale.neutral = 1; + CHECK ( scale.isFactor()); + + CHECK (scale.metric == LIN); + CHECK (not scale.isLogarithmic()); + scale.metric = NOM; + CHECK (not scale.isLogarithmic()); + scale.metric = DEC; + CHECK ( scale.isLogarithmic()); + scale.metric = BIN; + CHECK ( scale.isLogarithmic()); + scale.metric = NAT; + CHECK ( scale.isLogarithmic()); + } + + /** @test accommodate values to comply with the scale * - optionally a maximum and minimum can be enforced * - optionally the value can be cyclically wrapped @@ -181,7 +224,9 @@ namespace test{ * - applying a linear factor to a logarithmic scale is * transformed into an addition in the logarithmic domain * - adding a linear offset to a logarithmic scale is rejected - * @todo WIP 9/26 🔁 define ⟶ ✔ implement + * - extended precision scales handled using `lflp` (≙`long double`) + * - this allows to retain full precision of 64bit integrals + * @todo WIP 9/26 ✔ define ⟶ ✔ implement */ void verify_valueJoining() @@ -276,7 +321,7 @@ namespace test{ CHECK (logb (_MAX) < logb (megaScale.cyclicLim)); // ...which the (cyclic) target scale can still handle precisely! lflp res1 = megaScale.join (-5, 1e18f, fscale); // log10 ⟼ 18, so we add -5dB + 180dB; - CHECK (roughEQ (res1, -5)); // the cyclicWrap then computes 175 - (-10) ≡ 185 % 20 (≙5) + (minVal ≙ -10) + CHECK (roughEQ (res1, -5)); // the cyclicWrap then computes 175 - (-10) ≡ 185 % 20 (≙5) + (minVal ≙ -10) // but the computation is not exact... CHECK (0 != std::fmod (res1, 1)); @@ -295,10 +340,10 @@ namespace test{ // compare to target scale with lesser precision... Scale doubleTargetScale = {.metric = DEC}; Scale longTargetScale = {.metric = DEC}; - CHECK (180 == doubleTargetScale.join (0, int64_t(1e18), lscale)); - CHECK (180 == doubleTargetScale.join (0, int64_t(1e18)+1, lscale)); - CHECK (180 == longTargetScale.join (0, int64_t(1e18), lscale)); - CHECK (180 != longTargetScale.join (0, int64_t(1e18)+1, lscale)); /////////////OOO this is a Bug! we should indeed use lflp for the interim computation + CHECK (180 == doubleTargetScale.join (0, int64_t(1e18), lscale)); // uses double for internal log10 computation + CHECK (180 == doubleTargetScale.join (0, int64_t(1e18)-10, lscale)); // offset chosen large enough to prevail in int64_t but not in double + CHECK (180 == longTargetScale.join (0, int64_t(1e18), lscale)); // 64bit integral scales use long-double for log10 computation internally + CHECK (179 == longTargetScale.join (0, int64_t(1e18)-10, lscale)); // ...and thus the offset survives the log10 and is truncated down on conversion } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index 0a072f602..d069002ce 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -86269,16 +86269,17 @@ class ScaleRegistry - + + - - + + @@ -87235,9 +87236,9 @@ Prop.1: dist%3 1 2 0 1 2 0 - - - + + + @@ -87251,7 +87252,7 @@ Prop.1: dist%3 1 2 0 1 2 0 - + @@ -87263,7 +87264,7 @@ Prop.1: dist%3 1 2 0 1 2 0 - + @@ -87285,8 +87286,7 @@ Prop.1: dist%3 1 2 0 1 2 0 - - + @@ -87315,8 +87315,7 @@ Prop.1: dist%3 1 2 0 1 2 0 Überraschung: die Logarithmen-Matematik funktioniert ... aber das dumme Mensch braucht endlos um zu kapieren daß die Beispielwerte stimmen

- - +
@@ -87332,15 +87331,26 @@ Prop.1: dist%3 1 2 0 1 2 0
- + - - - - + + + + + + + + +

+ nicht mehr versuchen, alles in einen Typ-Ausdruck zu packen; stattdessen hier auch die Technik mit einer Constexpr-Funktion, die einen type_identity-Record liefert +

+ +
+ +
@@ -87373,6 +87383,15 @@ Prop.1: dist%3 1 2 0 1 2 0
+ + + + + + + + +
@@ -87442,8 +87461,8 @@ Prop.1: dist%3 1 2 0 1 2 0 - - + + @@ -87520,7 +87539,7 @@ Prop.1: dist%3 1 2 0 1 2 0 - + @@ -87576,8 +87595,8 @@ Prop.1: dist%3 1 2 0 1 2 0 - - + + @@ -87605,6 +87624,11 @@ Prop.1: dist%3 1 2 0 1 2 0 + + + + + @@ -87640,17 +87664,17 @@ Prop.1: dist%3 1 2 0 1 2 0 - - + + - + - + @@ -87659,12 +87683,12 @@ Prop.1: dist%3 1 2 0 1 2 0 jaja ... oft weiß man nicht ob es um "overengieering" geht, oder man selber einfach faul ist

- -
+
- - + + + @@ -87693,13 +87717,12 @@ Prop.1: dist%3 1 2 0 1 2 0 Tatsächlich ist der ganze Zweig mit dem Genauigkeits-Limit nur für floating-point-Typen notwendig; und diesen Branch-Guard kann man tatsächlich schon zur compile-time machen!

- - +
- + @@ -87738,8 +87761,7 @@ Prop.1: dist%3 1 2 0 1 2 0 das ist die größtmögliche 10-er-Potenz, die noch in 63bit reinpaßt

- -
+
@@ -87756,8 +87778,8 @@ Prop.1: dist%3 1 2 0 1 2 0
- - + + @@ -87765,6 +87787,9 @@ Prop.1: dist%3 1 2 0 1 2 0
+ + +