From d0e6998b9c6ed6e0a98b67cbc326ef4abd6374e8 Mon Sep 17 00:00:00 2001 From: Ichthyostega Date: Tue, 15 Sep 2026 17:32:05 +0200 Subject: [PATCH] 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. --- src/lib/util-quant.hpp | 3 +- tests/lib/cyclic-wrap-test.cpp | 101 ++++++++++++++++++++++++++++++++- 2 files changed, 101 insertions(+), 3 deletions(-) diff --git a/src/lib/util-quant.hpp b/src/lib/util-quant.hpp index 5c913afdd..bd5bb68f0 100644 --- a/src/lib/util-quant.hpp +++ b/src/lib/util-quant.hpp @@ -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::epsilon(); if (not std::isfinite (limit)) limit = std::numeric_limits::max(); diff --git a/tests/lib/cyclic-wrap-test.cpp b/tests/lib/cyclic-wrap-test.cpp index 1d96c6a38..b52849128 100644 --- a/tests/lib/cyclic-wrap-test.cpp +++ b/tests/lib/cyclic-wrap-test.cpp @@ -27,7 +27,7 @@ using ::Test; //using util::isnil; //using util::_Fmt; using util::isLimited; - +using std::floating_point; namespace util { namespace test { @@ -42,6 +42,9 @@ namespace test { template constexpr NUM _MIN = std::numeric_limits::lowest(); + template + constexpr FLO _EPS = std::numeric_limits::epsilon(); + } // (End) Test helpers @@ -75,6 +78,8 @@ namespace test { verifyIntegerTypes(); verifyIntegerTypes (); verifyIntegerTypes(); + + verifyFloatTypes(); } @@ -105,7 +110,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) ); @@ -134,6 +139,98 @@ namespace test { CHECK (LOWER + offset == cyc(_MIN) ); } + + void + verifyFloatTypes() + { + double maxDom = _MAX; + double super = _MAX - _MIN; +SHOW_EXPR(maxDom) +SHOW_EXPR(super) +SHOW_EXPR(_EPS) +SHOW_EXPR(limit_cyclicWrap(1)) +SHOW_EXPR(limit_cyclicWrap(1024)) +SHOW_EXPR(limit_cyclicWrap(1.0/1024)) +SHOW_EXPR(limit_cyclicWrap(_EPS)) +SHOW_EXPR(limit_cyclicWrap(maxDom)) + + double limit5 = limit_cyclicWrap(5); + double atLimit = limit5 * (1-_EPS); + double stepLim = std::floor (limit5 / 5) * 5; +SHOW_EXPR(limit5*_EPS) +SHOW_EXPR(limit5) +SHOW_EXPR(stepLim) +SHOW_EXPR(atLimit) + +SHOW_EXPR(cyclicWrap (10.0 ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (limit5 ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (atLimit ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim-5 ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+1 ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+2 ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+3 ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+4 ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+5 ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+6 ,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+10,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+20,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+30,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+40,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+50,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+100,0.0, 5.0)) +SHOW_EXPR(cyclicWrap (stepLim+101,0.0, 5.0)) + +SHOW_EXPL(cyclicWrap (limit5 ,10.0, 15.0)) +SHOW_EXPL(cyclicWrap (limit5+1 ,10.0, 15.0)) + double epsStep = _MAX*_EPS; + double toxicMin = _MIN + 2*epsStep; + double toxicMax = epsStep; + double toxicPeriod = toxicMax - toxicMin; +SHOW_EXPR(epsStep) +SHOW_EXPR(toxicMin) +SHOW_EXPR(toxicMax) +SHOW_EXPR(toxicPeriod) +SHOW_EXPR(cyclicWrap(0, toxicMin,toxicMax) ) +SHOW_EXPR(cyclicWrap(1, toxicMin,toxicMax) ) +SHOW_EXPR(cyclicWrap(1*epsStep, toxicMin,toxicMax) ) +SHOW_EXPR(cyclicWrap(2*epsStep, toxicMin,toxicMax) ) +SHOW_EXPR(cyclicWrap(3*epsStep, toxicMin,toxicMax) ) +SHOW_EXPR(cyclicWrap(0, 1, _MAX) ) +SHOW_EXPR(cyclicWrap(1, 1, _MAX) ) +SHOW_EXPR(cyclicWrap(2, 1, _MAX) ) +SHOW_EXPR(cyclicWrap(-1,1, _MAX) ) +SHOW_EXPR(cyclicWrap(-epsStep,1, _MAX) ) +SHOW_EXPR(1 == cyclicWrap(1, 1, _MAX)) +SHOW_EXPR(2 == cyclicWrap(2, 1, _MAX)) +SHOW_EXPR(1 == cyclicWrap(-1,1, _MAX) ) + double wrapped = cyclicWrap (-epsStep,double(1), _MAX); +SHOW_EXPR(1 < wrapped) +SHOW_EXPR(wrapped < _MAX) + + auto cyc = [&](double val){ return cyclicWrap (val, double(-10),double(-5)); }; + + CHECK (almostEqual (double(-10), cyc(0))); + CHECK (almostEqual (double( -9), cyc(1))); + CHECK (almostEqual (double( -8), cyc(2))); + CHECK (almostEqual (double( -7), cyc(3))); + CHECK (almostEqual (double( -6), cyc(4))); + CHECK (almostEqual (double(-10), cyc(5))); + CHECK (almostEqual (double( -9), cyc(6))); + CHECK (almostEqual (double( -8), cyc(7))); + CHECK (almostEqual (double( -7), cyc(8))); + CHECK (almostEqual (double( -6), cyc(9))); + CHECK (almostEqual (double(-10), cyc(105))); + + CHECK (almostEqual (double(5)/2, limit5 * _EPS)); + CHECK (limit5 + 1 == limit5); + + CHECK (1 == cyclicWrap( 1, 1,_MAX) ); + CHECK (2 == cyclicWrap( 2, 1,_MAX) ); + CHECK (1 == cyclicWrap(-1, 1,_MAX) ); + CHECK ( 1 < wrapped ); + CHECK (wrapped < _MAX); + } };