Compare commits

..

3 commits

Author SHA1 Message Date
620ca918c3 Library: verification for all floating-point types
finally I was able to come up with a rather concise test
that works with float, double and long double; especially
the aliasing due to rounding could be demonstrated, and
the precision limit is made plausible.

Interestingly, `std::fabs()` really works precise enough
so that a very large positive domain can be implemented
and tested with exact match
2026-09-16 04:23:55 +02:00
0c69db19d7 Library: investigate what could be tested with floating-point
The floating-point variant turns out to be difficult to test,
since it is never ''actually exact'' -- due to the nature of floating-points.

We can not expect reasonable behaviour at the domain boundaries,
even while in some cases the computation works (and in others it
goes infinite and is sanitised to `minVal`).

The limit for rejection is itself rather a heuristic;
thus it is difficult to show "wrong" behaviour beyond the limit,
while the limit itself is typically already so large that it is
hard to handle in test code -- at least when expecting visible results.
2026-09-15 21:53:56 +02:00
078aa48879 Library: verify the new cyclicWrap library function
The implementation should cover all integral data types,
both signed and signed, over the entire value domain.

Verify that by testing the bounds and the complex computation path
2026-09-15 01:37:38 +02:00
3 changed files with 186 additions and 20 deletions

View file

@ -216,7 +216,7 @@ namespace util {
offset += PERIOD;
if (not (offset < PERIOD)) // can happen due to ε or non-finite values
offset = 0;
F res = minVal + offset; // could be rounded to maxVal
F res = minVal + offset; // could be rounded to maxVal (or become infinite)
return res < maxVal? res : minVal;
}
@ -245,6 +245,7 @@ namespace util {
limit_cyclicWrap (F period)
{
REQUIRE (std::isfinite (period));
REQUIRE (F(0) < period);
F limit = period / std::numeric_limits<F>::epsilon();
if (not std::isfinite (limit))
limit = std::numeric_limits<F>::max();

View file

@ -19,21 +19,14 @@
#include "test/run.hpp"
#include "lib/util-quant.hpp"
#include "lib/util.hpp"
#include "test/diagnostic-output.hpp"//////////////////TODO
//#include "lib/format-cout.hpp"
//#include "lib/format-string.hpp"
using ::Test;
//using util::isnil;
//using util::_Fmt;
using util::isLimited;
using std::floating_point;
namespace util {
namespace test {
namespace{ // Test helpers...
template<typename NUM>
@ -42,10 +35,14 @@ namespace test {
template<typename NUM>
constexpr NUM _MIN = std::numeric_limits<NUM>::lowest();
template<floating_point FLO>
constexpr FLO _EPS = std::numeric_limits<FLO>::epsilon();
} // (End) Test helpers
/******************************************************************//**
* @test Demonstrate and verify a cyclic wrapping numeric domain.
* - show the wrapping behaviour with simple numbers
@ -53,7 +50,9 @@ namespace test {
* - verify the computation works at the very bounds of the domain
* - verify the most difficult path with a negative minVal,
* and the smallest number in the domain
* @todo add coverage for floating-point numbers
* - show a similar simple wrapping behaviour with all float types
* - demonstrate the meaning of the precision limit
* - demonstrate aliasing and sanitised rounding errors
* @see util::cyclicWrap
* @see parameter-scale-test.cpp
*/
@ -75,6 +74,10 @@ namespace test {
verifyIntegerTypes<uint32_t>();
verifyIntegerTypes<int64_t> ();
verifyIntegerTypes<uint64_t>();
verifyFloatTypes<float>();
verifyFloatTypes<double>();
verifyFloatTypes<f128>();
}
@ -105,7 +108,7 @@ namespace test {
const I LOWER(-100); // note: expected to wrap for unsigned types
const I UPPER(LOWER+5);
auto cyc = [&](I val){ return cyclicWrap (I(val), LOWER,UPPER); };
auto cyc = [&](I val){ return cyclicWrap (val, LOWER,UPPER); };
CHECK (LOWER == cyc(LOWER+0) );
CHECK (LOWER+1 == cyc(LOWER+1) );
@ -127,6 +130,8 @@ namespace test {
CHECK (isLimited (LOWER, cyc(_MAX<I>-1), UPPER));
CHECK (isLimited (LOWER, cyc(_MIN<I>+1), UPPER));
//------ Demonstrate behaviour at lower domain bound
//
I dist = LOWER - _MIN<I>; // guaranteed to be positive and representable in-domain
I step = (dist / 5) * 5; // the next applicable grid point
I excess = dist - step;
@ -134,6 +139,54 @@ namespace test {
CHECK (LOWER + offset == cyc(_MIN<I>) );
}
template<typename F>
void
verifyFloatTypes()
{
auto cyc = [&](F val){ return cyclicWrap (val, F(-10),F(-5)); };
CHECK (almostEqual (F(-10), cyc(0)) );
CHECK (almostEqual (F( -9), cyc(1)) );
CHECK (almostEqual (F( -8), cyc(2)) );
CHECK (almostEqual (F( -7), cyc(3)) );
CHECK (almostEqual (F( -6), cyc(4)) );
CHECK (almostEqual (F(-10), cyc(5)) );
CHECK (almostEqual (F( -9), cyc(6)) );
CHECK (almostEqual (F( -8), cyc(7)) );
CHECK (almostEqual (F( -7), cyc(8)) );
CHECK (almostEqual (F( -6), cyc(9)) );
CHECK (almostEqual (F(-10), cyc(105)));
// For each period-length, a precision limit can be computed;
// whenever bounds or inputs approach that limit, the rounding errors
// approach the order of magnitude of the period-length, and thus
// computing of wrapped values becomes pointless...
F period5 = F(5);
F limit5 = limit_cyclicWrap<F> (period5);
// one ULP-Step at the limit equals already half the period-length:
CHECK (almostEqual (period5/2, limit5 * _EPS<F>));
// so small steps will alias...
CHECK (limit5 + 1 == limit5);
// HOWEWER, very large periods can be handled (within numeric precision)
CHECK (1 == cyclicWrap<F>( 1, 1,_MAX<F>) );
CHECK (2 == cyclicWrap<F>( 2, 1,_MAX<F>) ); // note: these values are *exact* -- due to std::fmod()
CHECK (3 == cyclicWrap<F>( 3, 1,_MAX<F>) );
CHECK (4 == cyclicWrap<F>( 4, 1,_MAX<F>) );
CHECK (5 == cyclicWrap<F>( 5, 1,_MAX<F>) );
CHECK (1 == cyclicWrap<F>(-1, 1,_MAX<F>) ); // yet this one gets aliased with the minVal
F epsStep = _MAX<F>*_EPS<F>;
// yet the following input is clearly below minVal and will wrap upwards...
F wrapped = cyclicWrap (-epsStep, F(1),_MAX<F>);
CHECK ( 1 < wrapped);
CHECK (wrapped < _MAX<F>);
}
};

View file

@ -86838,9 +86838,10 @@ Prop.1: dist%3 1 2 0 1 2 0
<arrowlink COLOR="#49a8d6" DESTINATION="ID_636669321" ENDARROW="Default" ENDINCLINATION="87;46;" ID="Arrow_ID_1613615444" STARTARROW="None" STARTINCLINATION="260;14;"/>
<icon BUILTIN="wizard"/>
</node>
<node BACKGROUND_COLOR="#eef0c5" COLOR="#990000" CREATED="1789423635808" ID="ID_840921741" MODIFIED="1789438193575" TEXT="Testabdeckung &#x2014; Grenzf&#xe4;lle">
<icon BUILTIN="pencil"/>
<node CREATED="1789428970477" ID="ID_1439061940" MODIFIED="1789429004081" TEXT="habe gestern schon gesehen: f&#xfc;r sich sind util::flordiv | wrap wohl &#x201e;wasserdicht&#x201c;">
<node COLOR="#338800" CREATED="1789423635808" ID="ID_840921741" MODIFIED="1789525417179" TEXT="Testabdeckung &#x2014; Grenzf&#xe4;lle">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1789428970477" FOLDED="true" ID="ID_1439061940" MODIFIED="1789525368840" TEXT="habe gestern schon gesehen: f&#xfc;r sich sind util::flordiv | wrap wohl &#x201e;wasserdicht&#x201c;">
<icon BUILTIN="idea"/>
<node CREATED="1789429005712" ID="ID_1890368766" MODIFIED="1789429031093" TEXT="die Division liefert stets einen kleineren Quotient als der Dividend"/>
<node CREATED="1789429032161" ID="ID_1469836197" MODIFIED="1789429058156" TEXT="die Korrektur-Logik arbeitet &#xbb;gl&#xfc;cklicherweise&#xab; stets zur Dom&#xe4;ne nach innen"/>
<node COLOR="#338800" CREATED="1789433766523" ID="ID_167005830" MODIFIED="1789433779815" TEXT="dies in UtilFloordiv_test dokumentieren">
@ -86853,19 +86854,16 @@ Prop.1: dist%3 1 2 0 1 2 0
</node>
<node COLOR="#338800" CREATED="1789437807095" ID="ID_932126209" MODIFIED="1789437907066" TEXT="zun&#xe4;chst eine einfache Demonstration (als Dokumentation)">
<richcontent TYPE="NOTE"><html>
<head>
</head>
<head/>
<body>
<p>
.... ich k&#246;nnte mir denken, da&#223; mancher Leser das Konzept nicht gleich versteht, deshalb ist ein solches einfaches Beispiel nie verkehrt
</p>
</body>
</html>
</richcontent>
</html></richcontent>
<icon BUILTIN="button_ok"/>
</node>
<node COLOR="#338800" CREATED="1789437908944" ID="ID_880098415" MODIFIED="1789437928788" TEXT="systematisch alle real-existierenden Integral-Typen abdecken">
<node COLOR="#338800" CREATED="1789437908944" FOLDED="true" ID="ID_880098415" MODIFIED="1789525347778" TEXT="systematisch alle real-existierenden Integral-Typen abdecken">
<icon BUILTIN="button_ok"/>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1789437929974" ID="ID_532498668" MODIFIED="1789438198770" TEXT="die eigentliche Test-Funktion als Template">
<icon BUILTIN="idea"/>
@ -86888,6 +86886,120 @@ Prop.1: dist%3 1 2 0 1 2 0
<icon BUILTIN="button_ok"/>
</node>
</node>
<node COLOR="#338800" CREATED="1789485850174" FOLDED="true" ID="ID_1074654514" MODIFIED="1789525408140" TEXT="systematische Testabdeckung f&#xfc;r Floatingpoint-Typen entwickeln">
<icon BUILTIN="button_ok"/>
<node COLOR="#435e98" CREATED="1789485871331" ID="ID_552981037" MODIFIED="1789525115154" TEXT="mu&#xdf; zun&#xe4;chst einmal herausfinden was man &#xfc;berhaupt testen k&#xf6;nnte">
<icon BUILTIN="yes"/>
<node CREATED="1789485882603" ID="ID_1932453860" MODIFIED="1789485901968" TEXT="geschenkt: einen einfachen cyclicWrap">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
ganz analog zum Integral-Fall
</p>
</body>
</html></richcontent>
</node>
<node CREATED="1789485908054" ID="ID_1390215073" MODIFIED="1789485917984" TEXT="mit dem Verhalten am Limit experimentieren"/>
<node CREATED="1789485919101" ID="ID_126516087" MODIFIED="1789485942979" TEXT="Limit-Werte anschauen &#x27f9; sie wachsen mit der Periodenl&#xe4;nge"/>
<node CREATED="1789485944250" ID="ID_758296300" MODIFIED="1789485958924" TEXT="man k&#xf6;nnte also mit Input knapp unter dem Limit testen"/>
<node CREATED="1789485959655" ID="ID_1357089333" MODIFIED="1789518269807" TEXT="oder mit einem Offset knapp unter dem Limit"/>
<node COLOR="#5b280f" CREATED="1789485980355" ID="ID_1975112618" MODIFIED="1789525168334" TEXT="man k&#xf6;nnte zeigen da&#xdf; die Berechnung ungenau wird &#xfc;ber dem Limit">
<icon BUILTIN="button_cancel"/>
<node CREATED="1789486105659" ID="ID_1422733682" MODIFIED="1789486116438" TEXT="f&#xfc;r maxVal &gt; Limit"/>
<node CREATED="1789486117635" ID="ID_1961938709" MODIFIED="1789486122240" TEXT="aber auch f&#xfc;r zu gro&#xdf;e Domain"/>
</node>
<node COLOR="#5b280f" CREATED="1789485994791" ID="ID_1294711924" MODIFIED="1789525135527" TEXT="man k&#xf6;nnte eine Domain-Gr&#xf6;&#xdf;e am Limit testen">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
die sollte dann aber nur f&#252;r Input-Werte unterhalb des Limits funktionieren, allerdings auch negative Werte; oberhalb des Limits w&#252;rde ich Entgleisungen vermuten
</p>
</body>
</html></richcontent>
<icon BUILTIN="button_cancel"/>
<node CREATED="1789525137057" ID="ID_188158552" MODIFIED="1789525146844" TEXT="nein: l&#xe4;&#xdf;t sich nicht sinnvoll im Test demonstrieren"/>
<node CREATED="1789525147624" ID="ID_888819868" MODIFIED="1789525160044" TEXT="(ich sehe die Entgleisungen wenn ich mir die Zahlen im Debugger anschaue)"/>
</node>
</node>
<node BACKGROUND_COLOR="#e0ceaa" COLOR="#690f14" CREATED="1789486294164" ID="ID_712679325" MODIFIED="1789517587212" TEXT="Beobachtungen: genau am Limit funktioniert das einfache Beispiel noch">
<icon BUILTIN="broken-line"/>
<node CREATED="1789486311768" ID="ID_204301662" MODIFIED="1789486325968" TEXT="ein Epsilon drunter wird absorbiert"/>
<node BACKGROUND_COLOR="#cab2bd" COLOR="#980009" CREATED="1789486326998" ID="ID_185831565" MODIFIED="1789517613464" TEXT="m&#xfc;&#xdf;te also tats&#xe4;chlich ein ULP daneben gehen">
<icon BUILTIN="idea"/>
</node>
<node CREATED="1789517621465" ID="ID_1146256096" MODIFIED="1789517635375" TEXT="tats&#xe4;chlich: das Stepping wird unzuverl&#xe4;ssig"/>
<node CREATED="1789517696551" ID="ID_825633898" MODIFIED="1789517744461" TEXT="limit &#xb7; &#x3b5; &#x2261; &#xbd; periode">
<node CREATED="1789517760503" ID="ID_205743420" MODIFIED="1789517788314" TEXT="das haben wir aber genau so definiert...">
<icon BUILTIN="messagebox_warning"/>
</node>
<node CREATED="1789517771893" ID="ID_624480094" MODIFIED="1789517791174" TEXT="insofern ist das kein &#xbb;Test&#xab;">
<icon BUILTIN="stop-sign"/>
<node CREATED="1789517806226" ID="ID_1031466232" MODIFIED="1789517811675" TEXT="ein Test sollte Verhalten zeigen"/>
<node CREATED="1789517812425" ID="ID_1660313139" MODIFIED="1789517821515" TEXT="und nicht eine Definition replizieren"/>
</node>
</node>
</node>
<node CREATED="1789518828341" ID="ID_452450374" MODIFIED="1789525394621">
<richcontent TYPE="NODE"><html>
<head/>
<body>
<p>
Problem ist: hier kann ich kein &#187;<b>korrekt</b>&#171; oder &#187;<b>defekt</b>&#171; zeigen
</p>
</body>
</html></richcontent>
<linktarget COLOR="#57fec0" DESTINATION="ID_452450374" ENDARROW="Default" ENDINCLINATION="-182;8;" ID="Arrow_ID_1586349868" SOURCE="ID_409017691" STARTARROW="None" STARTINCLINATION="87;8;"/>
<icon BUILTIN="broken-line"/>
<node CREATED="1789518866588" ID="ID_1949224608" MODIFIED="1789518885765" TEXT="das Verhalten wird zum Limit hin zunehmend &#xbb;unscharf&#xab;"/>
<node CREATED="1789518886522" ID="ID_515488673" MODIFIED="1789518921574" TEXT="wie viel Unsch&#xe4;rfe tolerierbar ist l&#xe4;&#xdf;t sich nicht generisch sagen">
<node CREATED="1789518978509" ID="ID_1991175199" MODIFIED="1789518986039" TEXT="es gibt keine &#xbb;Schrittweite&#xab;"/>
<node CREATED="1789518987204" ID="ID_30471047" MODIFIED="1789519013068" TEXT="wann ist ein &quot;off by 0.1&#xab; noch tolerierbar??"/>
</node>
<node CREATED="1789518902636" ID="ID_1841879477" MODIFIED="1789518930946" TEXT="letztlich sind floating-point-numbers immer &#xbb;unscharf&#xab;"/>
<node CREATED="1789518945761" ID="ID_1474263016" MODIFIED="1789518957882" TEXT="jedwede Demonstration w&#xfc;rde eben nur dieses darstellen"/>
<node BACKGROUND_COLOR="#f7dba2" COLOR="#690f14" CREATED="1789522669935" ID="ID_1829288235" MODIFIED="1789522703291" TEXT="und : eine klar verst&#xe4;ndliche Demonstration ist schwer">
<icon BUILTIN="messagebox_warning"/>
<node COLOR="#b21478" CREATED="1789522720139" ID="ID_1872531495" MODIFIED="1789522906208" TEXT="Grenzwerte sind &#xbb;unhandlich&#xab;">
<richcontent TYPE="NOTE"><html>
<head/>
<body>
<p>
sobald man mit &#949; und ULPs hantiert, sind die numerischen Werte nur so minuti&#246;s verschieden, da&#223; man es in der &#252;blichen, gerundeten Darstellung gar nicht sieht. Damit scheiden alle Test-Methoden aus, die &quot;einfache Zahlen&quot; verwenden und ein festes Ergebnis vorgeben. Hinzu kommt, da&#223; man auch einen &#949;-Vergleich machen mu&#223;, und dann ohne gesonderte &#220;berlegung nicht offensichtlich ist, welche &quot;Unsch&#228;rfe&quot; nun &quot;unscharf&quot; ist, und welche Teil des Test-Subjekts ist. Nicht da&#223; man das alles irgendwie &quot;hinbekommt&quot; &#8212; aber der Leser versteht nicht mehr, <i>was hier gespielt wird.</i>
</p>
</body>
</html></richcontent>
<font NAME="SansSerif" SIZE="11"/>
<icon BUILTIN="smily_bad"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#c8c0b6" COLOR="#435e98" CREATED="1789521749900" ID="ID_912513687" MODIFIED="1789525194850" TEXT="sollte aber trotzdem wesentliche Elemente demonstrieren">
<icon BUILTIN="yes"/>
<node CREATED="1789521771353" ID="ID_559262092" MODIFIED="1789522935600" TEXT="was das &#xbb;limit&#xab; bedeutet : n&#xe4;mlich im Verh&#xe4;ltnis zur Periode"/>
<node CREATED="1789521783888" ID="ID_1660128606" MODIFIED="1789521817663" TEXT="da&#xdf; das Wrapping mit einem sehr gro&#xdf;en Intervall funktioniert"/>
<node CREATED="1789521818763" ID="ID_22678032" MODIFIED="1789521843436" TEXT="da&#xdf; es nahe der Interval-Obergrenze zu einem Aliasing kommen kann"/>
</node>
<node BACKGROUND_COLOR="#a6fdce" COLOR="#338800" CREATED="1789521853775" ID="ID_1420946197" MODIFIED="1789525101636" TEXT="Test formulieren...">
<icon BUILTIN="yes"/>
<node CREATED="1789521897465" ID="ID_1330009757" MODIFIED="1789521904636" TEXT="zun&#xe4;chst alles f&#xfc;r double"/>
<node CREATED="1789521905359" ID="ID_284924920" MODIFIED="1789521995911" TEXT="aber bereits mit Konversionen und almostEqual"/>
<node CREATED="1789525027288" ID="ID_1906103710" MODIFIED="1789525036691" TEXT="dann auf generischen Float umstellen...">
<node CREATED="1789525037796" ID="ID_767684162" MODIFIED="1789525039283" TEXT="float"/>
<node CREATED="1789525040023" ID="ID_1224952753" MODIFIED="1789525041834" TEXT="double"/>
<node CREATED="1789525042606" ID="ID_815586095" MODIFIED="1789525045834" TEXT="long double"/>
</node>
</node>
</node>
<node BACKGROUND_COLOR="#174956" COLOR="#8dfd49" CREATED="1788313090668" HGAP="15" ID="ID_904924646" MODIFIED="1789525281531" STYLE="bubble" TEXT="ufff: GR&#xdc;N" VSHIFT="2">
<edge COLOR="#61fe5b" STYLE="bezier" WIDTH="thin"/>
<icon BUILTIN="ksmiletris"/>
<node COLOR="#0f3e46" CREATED="1789525285095" HGAP="36" ID="ID_409017691" MODIFIED="1789525404221" STYLE="fork" TEXT="das ist schon &#xbb;advanced stuff&#xab;...." VSHIFT="-10">
<arrowlink COLOR="#57fec0" DESTINATION="ID_452450374" ENDARROW="Default" ENDINCLINATION="-182;8;" ID="Arrow_ID_1586349868" STARTARROW="None" STARTINCLINATION="87;8;"/>
<font NAME="SansSerif" SIZE="10"/>
</node>
</node>
</node>
</node>
</node>