From 9221fbeef162c81c5278f844529b25ae0712b257 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Wed, 16 Sep 2026 23:04:28 +0200 Subject: [PATCH] Parameters: document and verify integration into cyclic Scale - verify using an unsigned integer, for a typical ''angle scale'' - also verify a high-res floating-point scale, and demonstrate rounding issues --- src/lib/par/scale.hpp | 8 ++- src/test/test-helper.hpp | 20 ++++--- tests/lib/cyclic-wrap-test.cpp | 1 - tests/lib/par/parameter-scale-test.cpp | 82 +++++++++++++++++++++++++- wiki/thinkPad.ichthyo.mm | 42 ++++++++++++- 5 files changed, 136 insertions(+), 17 deletions(-) diff --git a/src/lib/par/scale.hpp b/src/lib/par/scale.hpp index bde3ca386..5522e9638 100644 --- a/src/lib/par/scale.hpp +++ b/src/lib/par/scale.hpp @@ -119,7 +119,9 @@ namespace par { ) ) and (not isCyclic() - or (minVal and maxVal and *minVal < *maxVal) + or (minVal and maxVal + and *minVal < *maxVal + and VAL(0) < cyclicLim) ) ; } @@ -169,8 +171,8 @@ namespace par { 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 (%4.2g ... %4.2g("} - % rawVal % minVal % maxVal + "for cyclic wrapping into [%1.2g...%1.2g[ "} + % rawVal % *minVal % *maxVal }; return util::cyclicWrap (rawVal, *minVal, *maxVal); } diff --git a/src/test/test-helper.hpp b/src/test/test-helper.hpp index b5fc2ed9f..a0881b38c 100644 --- a/src/test/test-helper.hpp +++ b/src/test/test-helper.hpp @@ -40,6 +40,7 @@ #include "lib/random.hpp" #include +#include #include #include #include @@ -62,14 +63,15 @@ namespace test{ using util::showType; using util::showTypes; + using std::floating_point; constexpr auto ROUGH_PRECISION = util::ipow (10.0, -3); constexpr auto EPSILON_ULP = 5; - template - constexpr inline enable_if< std::is_floating_point, - bool > + template + constexpr inline + bool roughEQ (F val, N target, F limit =ROUGH_PRECISION) { REQUIRE (0 < limit); @@ -77,9 +79,9 @@ namespace test{ } //////////////////////////////////////////////////////////////////////////TICKET #1360 looks like this problem was solved several times - template - constexpr inline enable_if< std::is_floating_point, - F > + template + constexpr inline + F ulp (F val) { val = fabs (val); @@ -91,9 +93,9 @@ namespace test{ return scaledUlp; } - template - constexpr inline enable_if< std::is_floating_point, - bool > + template + constexpr inline + bool epsEQ (F val, N target, uint ulps =EPSILON_ULP) { return abs (val - target) < ulps * ulp (target); diff --git a/tests/lib/cyclic-wrap-test.cpp b/tests/lib/cyclic-wrap-test.cpp index 72db13560..f8964543a 100644 --- a/tests/lib/cyclic-wrap-test.cpp +++ b/tests/lib/cyclic-wrap-test.cpp @@ -20,7 +20,6 @@ #include "lib/util-quant.hpp" #include "lib/util.hpp" -using ::Test; using util::isLimited; using std::floating_point; diff --git a/tests/lib/par/parameter-scale-test.cpp b/tests/lib/par/parameter-scale-test.cpp index 7ee377a5e..2b669e7a1 100644 --- a/tests/lib/par/parameter-scale-test.cpp +++ b/tests/lib/par/parameter-scale-test.cpp @@ -17,7 +17,7 @@ #include "test/run.hpp" -//#include "test/test-helper.hpp" +#include "test/test-helper.hpp" #include "lib/par/scale.hpp" #include "test/diagnostic-output.hpp"/////////////TODO @@ -28,6 +28,7 @@ //using std::string; //using std::vector; //using std::swap; +using test::roughEQ; namespace lib { @@ -59,6 +60,7 @@ namespace test{ { simpleUsage(); verify_Conforming(); + verify_cyclicScale(); } @@ -76,8 +78,10 @@ namespace test{ } - /** @test - * @todo WIP 9/26 ๐Ÿ” define โŸถ implement + /** @test accommodate values to comply with the scale + * - optionally a maximum and minimum can be enforced + * - optionally the value can be cyclically wrapped + * @todo WIP 9/26 โœ” define โŸถ โœ” implement */ void verify_Conforming() @@ -87,7 +91,79 @@ namespace test{ scale.maxVal = -5.0f; CHECK (-5.0f == scale.conform (1e5f)); + + CHECK (scale.isValid()); + scale.minVal = 5.0f; + CHECK (not scale.isValid()); + scale.maxVal = 8.0f; + CHECK (scale.isValid()); + CHECK (5.0f == scale.conform (-5.0f)); + + CHECK (not scale.isCyclic()); + scale.cyclicLim = 100; + CHECK (scale.isCyclic()); + CHECK (7.0f == scale.conform (-5.0f)); + CHECK (5.0f == scale.conform (-4.0f)); + CHECK (6.0f == scale.conform (-3.0f)); + CHECK (7.0f == scale.conform (-2.0f)); + + VERIFY_FAIL ("beyond supported numeric precision" + , scale.conform (-1e5f)); } + + + /** @test Scales can be configured to wrap cyclically, e.g. for angles. + * - demonstrate an unsigned angle scale with short precision + * - demonstrate a high-resolution scale symmetric to zero + * @todo WIP 9/26 โœ”define โŸถ ๐Ÿ” implement + */ + void + verify_cyclicScale() + { + Scale angleScale = {.minVal=0, .maxVal=360, .cyclicLim=0xFFFF}; + CHECK (angleScale.isValid()); + CHECK (angleScale.isCyclic()); + + CHECK ( 0 == angleScale.conform(0)); + CHECK (180 == angleScale.conform(180)); + CHECK (240 == angleScale.conform(240)); + CHECK ( 0 == angleScale.conform(360)); + CHECK (359 == angleScale.conform(719)); + CHECK ( 0 == angleScale.conform(65520)); + CHECK ( 15 == angleScale.conform(65535)); + + + Scale tinyScale = {.minVal=-lflp(1)/100, .maxVal=lflp(1)/100}; + tinyScale.cyclicLim = util::limit_cyclicWrap(*tinyScale.maxVal - *tinyScale.minVal); + CHECK (1e16 < tinyScale.cyclicLim); + CHECK (tinyScale.isValid()); + CHECK (tinyScale.isCyclic()); + + CHECK ( tinyScale.conform(0 ) == 0 ); + CHECK (roughEQ (tinyScale.conform(0.001) , 0.001)); + CHECK (roughEQ (tinyScale.conform(0.005) , 0.005)); + CHECK (roughEQ (tinyScale.conform(0.00999), 0.00999)); + CHECK (roughEQ (tinyScale.conform(0.01 ),-0.0099999)); // surprise: 1/100 not exactly representable in floating-point + + // since the domain is symmetric and 2/100 long, the ten-folds will fall into the middle, + // but they typically do not land precisely on zero due to inherent "number dust" + CHECK (1e15 > fabs(tinyScale.conform(1))); + CHECK (0 != tinyScale.conform(1) ); + + auto beyond_precision = -10 * tinyScale.cyclicLim; + VERIFY_FAIL ("beyond supported numeric precision" + , tinyScale.conform (beyond_precision)); + } + + + /** @test + * @todo WIP 9/26 ๐Ÿ” define โŸถ implement + */ + void + verify_valueJoining() + { + Scale scale; + } }; diff --git a/wiki/thinkPad.ichthyo.mm b/wiki/thinkPad.ichthyo.mm index a2a3ea93e..c063ce820 100644 --- a/wiki/thinkPad.ichthyo.mm +++ b/wiki/thinkPad.ichthyo.mm @@ -87231,7 +87231,7 @@ Prop.1: dist%3 1 2 0 1 2 0 - + @@ -87260,6 +87260,46 @@ Prop.1: dist%3 1 2 0 1 2 0 + + + + + + + + +

+ ...da ein Test eigentlich mehr machen sollte, als nur nochmal den Code zu wiederholen; und hier passiert nicht mehr, als ein cap an der boundrary, auf einem komplett offensichtlichen und einfachen Code-Pfad +

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

+ ...denn die eigentlich trickreiche Funktionalität steckt in der Library-Funktion und ist dort schon per Unit-Test abgedeckt, und das korrekte Setup wird innerhalb des Scale-Descriptors gar nicht geprüft (weil diese Prüfung in den umschließenden Kontext gehört, wo die Scale in einen Parameter-Typ eingebunden wird) +

+ +
+
+ + + + + + + +