diff --git a/src/lib/par/scale.hpp b/src/lib/par/scale.hpp index 4d6303107..67d05a6bd 100644 --- a/src/lib/par/scale.hpp +++ b/src/lib/par/scale.hpp @@ -90,16 +90,6 @@ namespace par { VAL join (VAL const&, SRC const&, Scale const&) const; VAL join (VAL const&, VAL const&) const; - /* ===== conversion helpers ===== */ - - template - auto asFactor (VAL const&) const; - - template - auto baseScale (Metric targetMetric) const; - - template - auto asLogarithm (Metric targetMetric, VAL const&) const; }; @@ -208,6 +198,80 @@ namespace par { } + namespace { // logarithmic scale conversions... + + /** transform a logarithmic value into an exponential factor */ + template + inline auto + expFactor (Metric metric, VAL const& value) + { + using WorkType = CommonComputeType; + switch (metric) + { + case NAT: return std::exp (WorkType(value)); + case BIN: return std::exp2 (WorkType(value)); + case DEC: return std::pow (WorkType(10), WorkType(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 + inline auto + asLogarithm (Metric targetMetric, VAL const& value) + { + 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::log2 (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 + inline auto + baseScale (Metric targetMetric, Metric srcMetric) + { + using WorkType = CommonComputeType; + if (srcMetric == targetMetric) + return WorkType(1); + + auto srcBase = [&]{switch (srcMetric) + { + case BIN: return WorkType(2); + case NAT: return std::exp (WorkType(1)); + 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, srcBase()); + } + }//(End) log helpers + + /** * Join and combine an additional feed value with an anchor value. * @note actual joining operation is picked based on both scales and types involved. @@ -226,7 +290,7 @@ namespace par { if (LIN == metric) { if (feedScale.isLogarithmic()) - assignConverted (res, value * feedScale.template asFactor (srcFeed)); + assignConverted (res, value * expFactor (feedScale.metric, srcFeed)); else if (feedScale.isFactor()) assignConverted (res, JoinT(value) * JoinT(srcFeed)); @@ -237,10 +301,10 @@ namespace par { if (this->isLogarithmic()) { if (feedScale.isLogarithmic()) - assignConverted (res, value + feedScale.template baseScale(metric) * srcFeed); + assignConverted (res, value + baseScale (metric,feedScale.metric) * srcFeed); else if (feedScale.isFactor()) - assignConverted (res, value + feedScale.template asLogarithm (metric, srcFeed)); + assignConverted (res, value + asLogarithm (metric, srcFeed)); else throw err::Invalid {"adding a non-logarithmic data feed on top of a logarithmic base value is pointless."}; } @@ -264,81 +328,5 @@ namespace par { } - - - /** transform a logarithmic value into an exponential factor */ - template - template - inline auto - Scale::asFactor (VAL const& value) const - { - using WorkType = CommonComputeType; - switch (metric) - { - case NAT: return std::exp (WorkType(value)); - case BIN: return std::exp2 (WorkType(value)); - case DEC: return std::pow (WorkType(10), WorkType(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 auto - Scale::asLogarithm (Metric targetMetric, VAL const& value) const - { - 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()); - } - - }} // namespace lib::par #endif /*LIB_PAR_SCALE_H*/ diff --git a/src/lib/par/type-handler.hpp b/src/lib/par/type-handler.hpp index 1e79acc00..1f51fe721 100644 --- a/src/lib/par/type-handler.hpp +++ b/src/lib/par/type-handler.hpp @@ -158,6 +158,10 @@ _Pragma("GCC diagnostic pop") /** * 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 diff --git a/tests/lib/par/parameter-scale-test.cpp b/tests/lib/par/parameter-scale-test.cpp index bafc620f1..08058a848 100644 --- a/tests/lib/par/parameter-scale-test.cpp +++ b/tests/lib/par/parameter-scale-test.cpp @@ -202,23 +202,46 @@ namespace test{ // Scale fscale = {.metric = NAT}; Scale feed_dB = {.metric = DEC}; - CHECK (roughEQ (fscale.join (0, float(20), feed_dB) , log(100.0f) )); // dB ⟼ natural logarithm; note 10dB ≙ factor 10 * lg(10) value divided / 10 + CHECK (roughEQ (fscale.join (0, float(20), feed_dB) , log(100.0f) )); // dB ⟼ natural logarithm; note 20dB ≙ factor 10 * lg(100); input 20 divided by 10 + fscale.metric = BIN; + CHECK (roughEQ (fscale.join (0, float(20), feed_dB) , log2(100.0f))); // dB ⟼ binary logarithm; (factor 1/10 on input is folded into the log factor) + fscale.metric = DEC; + CHECK (roughEQ (fscale.join (0, float(20), feed_dB) , 20.0f )); // dB ⟼ dB just passed-through and then added to value ≔ 0.0 fscale.metric = DEC; - CHECK (roughEQ (fscale.join (0, float(20), feed_dB) , 20.0f )); // dB ⟼ dB just passed-through and then added to value ≔ 0.0 - feedScale.metric = BIN; - CHECK (roughEQ (fscale.join (0, int64_t(-3), feedScale), log10(1.0f/8)*10)); // binary log (given as int value) ⟼ dB + CHECK (roughEQ (fscale.join (0, int64_t(-3), feedScale), log10(1.0f/8) *10)); // binary log (given as int value) ⟼ dB + feedScale.metric = NAT; + CHECK (roughEQ (fscale.join (0, int64_t(-3), feedScale), log10(exp(-3))*10)); // natural log (input as int) ⟼ dB + feedScale.metric = DEC; + CHECK (roughEQ (fscale.join (0, int64_t(-3), feedScale), -3 )); // dB ⟼ dB just passed-through + + fscale.metric = BIN; + feedScale.metric = BIN; + CHECK (roughEQ (fscale.join (0, int64_t(-3), feedScale), -3 )); // lb ⟼ lb just passed-through + feedScale.metric = NAT; + CHECK (roughEQ (fscale.join (0, int64_t(-3), feedScale), log2 (exp(-3)) )); // natural log ⟼ binary log + feedScale.metric = DEC; + CHECK (roughEQ (fscale.join (0, int64_t(-30),feedScale), log2 (1.0/1000) )); // conversion factor: dB/10 ⟼ binary log fscale.metric = LIN; - CHECK (roughEQ (fscale.join (1, int64_t(-3), feedScale), 1.0f/8 )); // binary log applied as factor to a linear scale + feedScale.metric = BIN; + CHECK (roughEQ (fscale.join (1, int64_t(-3), feedScale), 1.0f/8 )); // binary log applied as factor to a linear scale + feedScale.metric = NAT; + CHECK (roughEQ (fscale.join (1, int64_t(-1), feedScale), 1.0f/exp(1))); // natural log applied as factor 1/e + feedScale.metric = DEC; + CHECK (roughEQ (fscale.join (1, int64_t(-30),feedScale), 1.0f/1000 )); // decibels applied as factor : -30 dB ≙ 10^-3 fscale.metric = BIN; feedScale.metric = LIN; feedScale.neutral = 1; CHECK (feedScale.isFactor()); CHECK (fscale.isLogarithmic()); - CHECK (1 + 3 == fscale.join (1, int64_t(8), feedScale)); // feed factor 8 ⟼ to binary logarithmic fscale (8 ≡ 2^3) and added there + CHECK ( fscale.join (1, int64_t(8), feedScale) == 1 + 3); // feed factor 8 ⟼ to binary logarithmic fscale (8 ≡ 2^3) and added there (exact) + fscale.metric = NAT; + CHECK (roughEQ (fscale.join (1, int64_t(8), feedScale), 1 + logf(8))); // feed factor 8 ⟼ now applied to a natural log scale (and added to one, in float) + fscale.metric = DEC; + CHECK (roughEQ (fscale.join (1, int64_t(100),feedScale), 1 + 20)); // feed factor 8 ⟼ now applied as offset in decibels feedScale.neutral = 0; CHECK (not feedScale.isFactor()); diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index c0d22dd37..32f4feb78 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -85973,7 +85973,7 @@ - + @@ -85985,7 +85985,7 @@ The persisted form. The moment a session file records a parameter with a scale, the meaning of its qualifiers is frozen or needs migration. It's worth spending more thought on what a Scale looks like when written out than on what it looks like in memory.
  • - Keeping Scale reducible to data. You mention a possible connection to rules later. A rules system can only reason about a Scale if the Scale is  a small tuple of facts (metric, bounds, cyclic flag, domain-set id, default) with a fixed interpreter, rather than a polymorphic object whose behaviour exists only as C++. That constraint costs you nothing today, since your qualifier-based design already satisfies it. It's just worth adopting as a deliberate rule so that a future conform()  override doing something clever doesn't quietly break it. + Keeping Scale reducible to data. You mention a possible connection to rules later. A rules system can only reason about a Scale if the Scale is a small tuple of facts (metric, bounds, cyclic flag, domain-set id, default) with a fixed interpreter, rather than a polymorphic object whose behaviour exists only as C++. That constraint costs you nothing today, since your qualifier-based design already satisfies it. It's just worth adopting as a deliberate rule so that a future conform() override doing something clever doesn't quietly break it.
  • @@ -87265,14 +87265,48 @@ Prop.1: dist%3 1 2 0 1 2 0
    - + + + + + + + + +

    + ...ich muß die Formeln jedes mal wieder nachschlagen, bzw mir herleiten, weil diese Formeln so eigenartig über Kreuz aufgebaut sind: +

    +
      +
    • + logb(x) ≡ logk(x)/logk(b) +
    • +
    • + logb(x) ≡ logb(k)·logk(x) +
    • +
    + + +
    +
    + + + + + + + + + + + + + +
    - - - +

    ...und dieses Herausdividieren falte ich trickreich in den log-Basis-Konvertierungsfaktor. @@ -87288,6 +87322,27 @@ Prop.1: dist%3 1 2 0 1 2 0 + + + + +

      +
    • + Type-promotion-Bug in der Anwendung der pow()-Funktion +
    • +
    • + logb() liefert nur den exponenten, log2() den 2-er-Logarithmus +
    • +
    • + das Wiederverwenden der asLogarithm()-Funktion macht deutlich, daß diese einen unnötigen Typ-Constraint auf den VAL-Parameter der Ausgans-Scale erzwingt +
    • +
    + + +
    + + +
    @@ -87560,6 +87615,16 @@ Prop.1: dist%3 1 2 0 1 2 0
    + + + + + + + + + +