diff --git a/src/lib/par/scale.hpp b/src/lib/par/scale.hpp index 48da2451c..f1b4a68e7 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 util::_Fmt; using std::optional; @@ -78,6 +79,7 @@ namespace par { bool isValid() const; bool isFactor() const; bool isCyclic() const; + bool isLogarithmic() const; /* ===== conforming operations ===== */ @@ -88,15 +90,16 @@ namespace par { VAL join (VAL const&, SRC const&, Scale const&) const; VAL join (VAL const&, VAL const&) const; - private: - template - VAL asFactor (VAL const&) const; + /* ===== conversion helpers ===== */ template - VAL baseScale (Metric targetMetric) const; + auto asFactor (VAL const&) const; template - VAL asLogarithm (Metric targetMetric, VAL const&) const; + auto baseScale (Metric targetMetric) const; + + template + auto asLogarithm (Metric targetMetric, VAL const&) const; }; @@ -153,6 +156,15 @@ namespace par { return VAL(0) != cyclicLim; } + template + inline bool + Scale::isLogarithmic() const + { + return metric == DEC + or metric == NAT + or metric == BIN; + } + /* ===== conforming operations ===== */ @@ -181,9 +193,9 @@ namespace par { REQUIRE (*minVal < *maxVal); if (std::is_floating_point_v) if (not (std::abs (rawVal) < cyclicLim)) - throw err::Invalid {util::_Fmt{"Parameter value %4.2g beyond supported numeric precision " - "for cyclic wrapping into [%1.2g...%1.2g[ "} - % rawVal % *minVal % *maxVal + throw err::Invalid {_Fmt{"Parameter value %4.2g beyond supported numeric precision " + "for cyclic wrapping into [%1.2g...%1.2g[ "} + % rawVal % *minVal % *maxVal }; return util::cyclicWrap (rawVal, *minVal, *maxVal); } @@ -213,7 +225,7 @@ namespace par { if (feedScale.isLogarithmic()) assignConverted (res, value * feedScale.template asFactor (srcFeed)); else - if (this->isFactor()) + if (feedScale.isFactor()) assignConverted (res, value * srcFeed); else assignConverted (res, value + srcFeed); @@ -247,28 +259,77 @@ namespace par { ); } + /** transform a logarithmic value into an exponential factor */ template template - inline VAL + inline auto Scale::asFactor (VAL const& value) const { - UNIMPLEMENTED ("transform a logarithmic value into an exponential factor"); + using WorkType = CommonComputeType; + switch (metric) + { + case NAT: return std::exp (WorkType(value)); + case BIN: return std::exp2 (WorkType(value)); + case DEC: return std::pow (10, value/10.0); + default: + throw err::Logic{_Fmt{"Attempt to compute exponential " + "from a non-logarithmic value with metric %d"} + % metric}; + break; + } } + /** transform this value into a suitable logarithm for the target scale */ template template - inline VAL - Scale::baseScale (Metric targetMetric) const - { - UNIMPLEMENTED ("provide the adaptation factor from this logarithmic scale to the given other logarithmic scale"); - } - - template - template - inline VAL + inline auto Scale::asLogarithm (Metric targetMetric, VAL const& value) const { - UNIMPLEMENTED ("transform this value into a suitable logarithm for the target scale"); + if (isNeg (value)) + throw err::Invalid{_Fmt{"Attempt to take logarithm from %f < 0"} + % value}; + using WorkType = CommonComputeType; + switch (targetMetric) + { + case NAT: return std::log (WorkType(value)); + case BIN: return std::logb (WorkType(value)); + case DEC: return std::log10 (WorkType(value)) * 10; + default: + throw err::Logic{_Fmt{"Attempt to compute logarithm " + "for a non-logarithmic metric %d"} + % targetMetric}; + break; + } + } + + /** provide the adaptation factor from this logarithmic scale + * to the given other logarithmic scale and target type \a VAL + * @remarks + * - use the logarithm conversion formula logb(x) ≡ logb(k)·logk(x) + * - if target is in decibel, have to apply factor 10; asLogarithm() does that already + * - if we are in decibel, log10 = dB /10; fold that factor in the logarithm of the base, + * by using log(b)·x ≡ log(b^x) and rewrite that as b^x = exp(ln(b)·x) + */ + + template + template + inline auto + Scale::baseScale (Metric targetMetric) const + { + using WorkType = CommonComputeType; + if (metric == targetMetric) + return WorkType(1); + + auto myBase = [&]{switch (metric) + { + case NAT: return std::exp(WorkType(1)); + case BIN: return WorkType(2); + case DEC: return std::exp(std::log(WorkType(10)) / 10); + default: + throw err::Logic{"Attempt to treat non-logarithmic scales as logarithms"}; + break; + }}; + return asLogarithm (targetMetric, myBase()); } diff --git a/src/lib/par/type-handler.hpp b/src/lib/par/type-handler.hpp index 7c8088c0a..fc4e4b9ff 100644 --- a/src/lib/par/type-handler.hpp +++ b/src/lib/par/type-handler.hpp @@ -42,8 +42,9 @@ namespace par { using util::isNeg; using std::same_as; - using std::floating_point; + using std::integral; using std::signed_integral; + using std::floating_point; using std::numeric_limits; @@ -146,6 +147,18 @@ _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 + */ + template + using CommonComputeType = std::conditional_t and integral, double + , std::common_type_t>; + + }} // namespace lib::par #endif /*LIB_PAR_TYPE_HANDLER_H*/ diff --git a/tests/lib/par/parameter-scale-test.cpp b/tests/lib/par/parameter-scale-test.cpp index a6d045143..b11178c79 100644 --- a/tests/lib/par/parameter-scale-test.cpp +++ b/tests/lib/par/parameter-scale-test.cpp @@ -61,6 +61,7 @@ namespace test{ simpleUsage(); verify_Conforming(); verify_cyclicScale(); + verify_valueJoining(); } @@ -156,13 +157,28 @@ namespace test{ } - /** @test + /** @test Base value and data feeds from differing Scales can be joined together * @todo WIP 9/26 🔁 define ⟶ implement */ void verify_valueJoining() { - Scale scale; + Scale scale = {.minVal = 2, .maxVal = 10}; + Scale feedScale; + + CHECK ( 5 == scale.join (2, int64_t(3), feedScale)); // 2+3 ≡ 5 (all computations done as double) + CHECK (10 == scale.join (5, int64_t(8), feedScale)); // 5+8 ≡ 13 ⟼ capped to 10 + + feedScale.neutral = 1; + CHECK (feedScale.isFactor()); + CHECK ( 6 == scale.join (2, int64_t(3), feedScale)); // 2*3 ≡ 6 + CHECK ( 2 == scale.join (2, int64_t(-3),feedScale)); // 2*-3 ≡ -6 ⟼ first conditioned to ushort domain, then capped to 2 + + feedScale.metric = DEC; // in dB + CHECK ( 7 == scale.join (4, int64_t(3), feedScale)); // 4 + 3dB ⟼ 4 * 1.995 ≡ 7.98 ⟼ truncated to 7 + + ////////////////////////////OOO die folgende Konvertierung entgleist.... +SHOW_EXPR(short(-3) * uint64_t(2)) } diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index e9785a1d7..d20e6e7a5 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -85656,13 +85656,41 @@ - + + + + + + + + +

+ man könnte hier zwar delogarithmieren / relogarithmieren, aber was ist z.B. bei negativem Ergebnis...? und, noch mehr: welchen Sinn hat es zu einem multiplikativen Faktor etwas hinzuzuaddieren (was aber nicht logarithmisch ist) +

+ + +
+
+ + + + + + +

+ Logarithmische Metrik verwendet man nur, wenn auch die Größe, in ihrer Bedeutung für die Wahrnehmung, selber logarithmisch ist. Zwar könnte man sich rein-formal vorstellen, daß so ein Fall durchaus mal auftreten kann — aber dann wäre es besser, die Situation zu hinterfragen: fehlt da vielleicht die Markierung des Feed als logarithmisch? +

+ + +
+
+
- + @@ -85678,6 +85706,80 @@ + + + + + + + + +

+ ...weil sich die Constraints typischerweise überlappen +

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

+ template<typename T> +

+

+ concept number = integral<T>  or floating_point<T>; +

+

+
+ +

+

+ /** +

+

+ * the best-precision floating point type to carry out +

+

+ * a numeric operation to combine two values +

+

+ */ +

+

+ template<number T,  number U> +

+

+ using CommonComputeType =  std::conditional_t<integral<T>  and integral<U>, double +

+

+ , std::common_type_t<T,  U>>; +

+

+ +

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