Compare commits

...

3 commits

Author SHA1 Message Date
59b44ed3e8 Parameters: 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
32fbfa7909 Library: extend text coverage for util::floorwrap
These two helpers ''just happen to work'' in the full domain, and also for unsigned types.
I can not recall that I actually was aware of that, but it's nice.

Thus add some test cases to cover the bounds of the domain
2026-09-14 16:34:15 +02:00
00941095c3 Parameters: numeric-types deep dive
Turns out that implementing a cyclically wrapping domain properly is much more tricky than initially anticipated.

After initial analysis I decided where to draw the line and what ''esoteric features'' aspects to leave out.
Then I used Knowledge-Technology (Claude) to scrutinise and improve my implementation,
and to learn a lot more about integral arithmetics in general.
2026-09-14 01:50:26 +02:00
12 changed files with 1337 additions and 486 deletions

View file

@ -1,6 +1,7 @@
/* try.cpp - to try out and experiment with new features....
* scons will create the binary bin/try
*/
// 09/26 - ways to build a cyclically wrapping number domain
// 03/26 - runtime index access and iteration over tuple-likes
// 06/25 - provide a concept to accept _tuple-like_ objects
// 06/25 - investigate function type detection of std::bind Binders
@ -11,425 +12,146 @@
/** @file try.cpp
* Work out a construction scheme to build an efficient element-accessor to data
* in a tuple-like with run-time indexing. As an extension, run-time pulled iteration
* can be established, using the IterExplorer framework.
* @see lib::meta::FunTrampoline
* @see lib::meta::TupleIdxAdaptor
* Investigate techniques for creating a cyclically wrapping numeric domain.
* Actually this could be achieved with my `floordiv()` utility, yet the ramifications
* were not clear -- so I settled to build a stand-alone solution, based on the naive modulus
* technique, attempting to fix the negative offsets. It became clear that this approach would
* have to consider the domain boundaries and identify cases that could not be handlet. At that
* point, I used Claude to scrutinise and improve my draft, leading to a generic tool that is
* now relocated into the \ref util-quant.hpp header
* @see lib::par::Scale
*/
#include "lib/format-cout.hpp"
#include "lib/format-util.hpp"
#include "test/test-helper.hpp"
#include "test/diagnostic-output.hpp"
#include "lib/meta/common-result.hpp"
#include "lib/meta/tuple-concept.hpp"
#include "lib/meta/tuple-helper.hpp"
#include "lib/meta/function.hpp"
#include "lib/iter-adapter.hpp"
#include "lib/hetero-data.hpp"
#include "lib/meta/trait.hpp"
#include "lib/util-quant.hpp"
#include "lib/util.hpp"
#include <array>
using std::string;
template<typename X>
#include <cmath>
template<typename NUM>
constexpr NUM _MAX = std::numeric_limits<NUM>::max();
template<typename NUM>
constexpr NUM _MIN = std::numeric_limits<NUM>::lowest();
template<typename N>
void
show()
{
SHOW_TYPE(X)
}
showCase (N val, N minVal, N maxVal)
{
cout << "val:"<<val<<" (min:"<<minVal<<" , max:"<<maxVal<<") ->"<< util::cyclicWrap (val,minVal,maxVal)<<endl;
}
template<lib::meta::tuple_like X>
template<typename N>
void
show()
{
cout << "Tulik!! "<< ::test::showType<X>() <<endl;
lib::meta::forEachIDX<X> ([](auto i)
{
using Elm = std::tuple_element_t<i, X>;
cout <<" "<<uint(i)<<": "<< ::test::showType<Elm>() <<endl;
});
}
showFloorDiv (N num, N den)
{
cout << "FloorDiv:"<<num<<" / "<<den<<" ->"<< util::floordiv(num,den)<<endl;
}
namespace lib {
namespace meta{
#if false ///////////////////////////////////////////////////////////////////////////////////////////////////TODO : new generic library functions
/**
* Concept to require a generic type sequence `Types<TYPES...>`
*/
template<class TSEQ>
concept typeseq = requires (TSEQ seq)
{
// TSEQ can invoke a function with a Types<TYPES> subclass
[]<typename...TYPES>(Types<TYPES...>&){/*placeholder*/}(seq);
};
#endif ///////////////////////////////////////////////////////////////////////////////////////////////////(End) : new generic library functions
template<template<class> class BUILD, typeseq TSEQ>
class FunTrampoline
{
using AllElm = ElmTypes<TSEQ>;
using Indices = AllElm::Idx;
using Builder = BUILD<TSEQ>;
using Functor = Builder::Functor;
using Table = std::array<Functor, AllElm::SIZ>;
template<size_t...idx>
static constexpr Table
buildFunctors (std::index_sequence<idx...>)
{
return {Builder().template build<idx>()...};
}
static constexpr Table functors = buildFunctors (Indices{});
public:
static constexpr size_t size() { return functors.size(); }
static constexpr size_t memSiz() { return sizeof(functors); }
static Functor const&
dispatch (size_t idx)
{
ENSURE (idx < functors.size());
return functors[idx];
}
using ResType = _Fun<Functor>::Ret;
};
#if false ///////////////////////////////////////////////////////////////////////////////////////////////////TODO : new generic library functions
template<typename T>
using isConst = std::is_const<remove_reference_t<T>>;
template<typename T>
using isLRef = std::is_lvalue_reference<T>;
template<class SEQ>
using Any_Const = ElmTypes<SEQ>::template OrAll<isConst>;
template<class SEQ>
using All_LRef = ElmTypes<SEQ>::template AndAll<isLRef>;
template<class SEQ>
static constexpr bool Any_Const_v = Any_Const<SEQ>::value;
template<class SEQ>
static constexpr bool All_LRef_v = All_LRef<SEQ>::value;
template<typename X, typename U, typename...TYPES>
struct ProbeCommonType
{
template<typename T1, typename T2>
using Reconciled = decltype(true ? std::declval<T1>() : std::declval<T2>());
template<typename RES>
struct FoundResult
: std::true_type
{
using Type = RES;
};
struct NoResultFound
: std::false_type
{ };
template<class T1
,class T2
,typename = Reconciled<T1,T2>>
static FoundResult<Reconciled<T1,T2>>
check(int);
template<class,class>
static NoResultFound
check(...);
using FirstResult = decltype(check<X,U>(0));
template<bool success, typename...TS>
struct Join
{
using Result = FirstResult;
};
template<typename T1, typename...TS>
struct Join<true, T1,TS...>
{
//wenn RES erfolgreich
using CT = FirstResult::Type;
using Result = ProbeCommonType<CT,T1,TS...>::Result;
};
using Result = Join<FirstResult::value, TYPES...>::Result;
};
template<typename...TYPES>
struct CommonResult
: ProbeCommonType<TYPES...>::Result
{ };
template<typename T1>
struct CommonResult<T1>
: CommonResult<T1,T1>
{ };
template<>
struct CommonResult<>
: std::false_type
{ };
template<typename...TYPES>
struct CommonResult<Types<TYPES...>>
: CommonResult<TYPES...>
{ };
template<typename...TYPES>
using CommonResult_t = CommonResult<TYPES...>::Type;
template<typename...TYPES>
static constexpr bool has_CommonResult_v = CommonResult<TYPES...>::value;
template<typename...TYPES>
concept reconcilable = requires
{
typename CommonResult<TYPES...>::Type;
};
template<typename...TYPES>
concept reconcilable_ref = reconcilable<TYPES...>
and isLRef_v<CommonResult_t<TYPES...>>;
template<typename...TYPES>
struct CommonResultTraits
{
static_assert (reconcilable<TYPES...>, "unable to reconcile these types");
using CommonVal = std::common_type_t<TYPES...>;
using ResType = CommonResult_t<TYPES...>;
//NOTE: std::common_type decays (strips CV and ref)
static constexpr bool makeConst = isConst_v<ResType>;
static constexpr bool exposeRef = isLRef_v<ResType>;
};
#endif ///////////////////////////////////////////////////////////////////////////////////////////////////(End) : new generic library functions
template<class TUP>
class TupleIndexAdaptor
: public TUP
{
template<typeseq SEQ>
struct SubscriptBuilder
{
using Tuple = RefTraits<TUP>::value_type;
using AddRefs = ElmTypes<SEQ>::template Apply<std::add_lvalue_reference_t>;
using ResType = CommonResult_t<AddRefs>;
using Functor = ResType(*)(Tuple&);
template<size_t idx>
constexpr auto
build()
{
return [](Tuple& tuple) -> ResType
{
return getElm<idx> (tuple);
};
}
};
using TypeSeq = ElmTypes<TUP>::Seq;
using Accessor = FunTrampoline<SubscriptBuilder, TypeSeq>;
public:
using ResType = Accessor::ResType;
using reference = ResType;
using value_Type = RefTraits<ResType>::value_type;
template<typename T>
TupleIndexAdaptor(T&& dataSrc)
: TUP{forward<T> (dataSrc)}
{ }
using TUP::TUP;
static constexpr size_t size() { return Accessor::size(); }
ResType
operator[] (size_t idx)
{
return Accessor::dispatch(idx)(*this);
}
};
template<class TUP>
TupleIndexAdaptor(TUP) -> TupleIndexAdaptor<std::decay_t<TUP>>;
}}
using namespace lib::meta;
template<typeseq SEQ>
struct Stringify
{
using Tuple = ElmTypes<SEQ>::Tup;
using Functor = string(*)(Tuple const&);
template<size_t idx>
constexpr auto
build()
{
using ElmT = std::tuple_element_t<idx, Tuple>;
return [](Tuple const& tuple)
{
return util::toString<ElmT> (getElm<idx> (tuple));
};
}
};
struct Uber { };
struct Untr : Uber { };
struct Unta : Uber { };
template<typename N>
void
showFloorWrap (N num, N den)
{
auto fwrap = util::floorwrap (num,den);
cout << "FloorWrap:"<<num<<" / "<<den<<" -> q="<<fwrap.quot<<" r="<<fwrap.rem<<endl;
}
int
main (int, char**)
{
using Tup = std::tuple<long,short>;
using Arr = std::array<int,3>;
using Hetero = lib::HeteroData<int,string>::Chain<short>::ChainExtent<bool,lib::meta::Nil>::ChainType;
const int PER = 3;
int i = -5;
int d = i / PER;
cout << "d="<<d<<endl;
cout << "fd="<<util::floordiv(i,PER)<<endl;
SHOW_EXPR((lib::meta::tuple_like<Tup> ))
SHOW_EXPR((lib::meta::tuple_like<Arr> ))
SHOW_EXPR((lib::meta::tuple_like<Hetero> ))
SHOW_EXPR((lib::meta::tuple_like<string> ))
SHOW_EXPR((lib::meta::tuple_like<int> ))
uint u = _MAX<uint>;
show<Tup>();
show<Arr>();
show<Hetero>();
show<int>();
cout << u << " fd="<<util::floordiv(u,uint(PER))<<" ...re="<<PER*util::floordiv(u,uint(PER))<<endl;
using TupStringy = FunTrampoline<Stringify, ElmTypes<Tup>::Seq>;
SHOW_EXPR(TupStringy::size())
showFloorDiv (7,5);
showFloorDiv (-7,5);
showFloorDiv (7,-5);
showFloorDiv (-7,-5);
Tup tup23{2,3};
Tup tup42{4,2};
SHOW_TYPE(decltype(std::get<1>(tup42)))
SHOW_EXPR(std::get<1>(tup42))
SHOW_EXPR(TupStringy::dispatch(0)(tup23))
SHOW_EXPR(TupStringy::dispatch(0)(tup42))
SHOW_EXPR(TupStringy::dispatch(1)(tup23))
SHOW_EXPR(TupStringy::dispatch(1)(tup42))
showFloorWrap (7,5);
showFloorWrap (-7,5);
showFloorWrap (7,-5);
showFloorWrap (-7,-5);
using T1 = Types<int const&,long&, char&>;
using T2 = Types<int*,void*>;
using T3 = Types<int,string>;
SHOW_EXPR((has_CommonResult_v<T1>))
SHOW_EXPR((has_CommonResult_v<T2>))
SHOW_EXPR((has_CommonResult_v<T3>))
SHOW_EXPR((has_CommonResult_v<>))
SHOW_EXPR((has_CommonResult_v<float*>))
SHOW_EXPR((has_CommonResult_v<float*,void* const>))
SHOW_TYPE(CommonResult_t<T1>)
SHOW_TYPE(CommonResult_t<T2>)
SHOW_TYPE(CommonResult_t<float*>)
using CoTx = CommonResult_t<float*,float const*>;
SHOW_TYPE(CoTx)
showFloorWrap (_MAX<int>,1);
showFloorWrap (_MAX<int>,2);
showFloorWrap (_MAX<int>,5);
showFloorWrap (_MIN<int>,1);
showFloorWrap (_MIN<int>,2);
showFloorWrap (_MIN<int>,5);
showFloorWrap (_MAX<int>,-1);
showFloorWrap (_MAX<int>,-2);
showFloorWrap (_MAX<int>,-5);
showFloorDiv (_MAX<int>,1);
showFloorDiv (_MAX<int>,2);
showFloorDiv (_MAX<int>,5);
showFloorDiv (_MIN<int>,1);
showFloorDiv (_MIN<int>,2);
showFloorDiv (_MIN<int>,5);
showFloorDiv (_MAX<int>,-1);
showFloorDiv (_MAX<int>,-2);
showFloorDiv (_MAX<int>,-5);
showFloorWrap (_MAX<uint>,1u);
showFloorWrap (_MAX<uint>,2u);
showFloorWrap (_MAX<uint>,5u);
showFloorDiv (_MAX<uint>,1u);
showFloorDiv (_MAX<uint>,2u);
showFloorDiv (_MAX<uint>,5u);
showCase<int>(-7,-5,5);
showCase<int>(-6,-5,5);
showCase<int>(-5,-5,5);
showCase<int>(-4,-5,5);
showCase<int>(+5,-5,5);
showCase<int>(+6,-5,5);
showCase<int>(+7,-5,5);
showCase<int>(+8,-5,5);
showCase<int>(+9,-5,5);
showCase<int>(+10,-5,5);
showCase<int>(+11,-5,5);
showCase<int>(+12,-5,5);
showCase<int>(+14,-5,5);
showCase<int>(+15,-5,5);
showCase<int>( -7,-10,-7);
showCase<int>( -8,-10,-7);
showCase<int>( -9,-10,-7);
showCase<int>(-10,-10,-7);
showCase<int>(-11,-10,-7);
showCase<int>(-12,-10,-7);
showCase<int>(-13,-10,-7);
showCase<int>(-14,-10,-7);
showCase<int>(-15,-10,-7);
showCase<uint>(5,1000,1003);
TupleIndexAdaptor<Tup> idxAdapt{tup42};
SHOW_EXPR(sizeof(tup42))
SHOW_EXPR(sizeof(idxAdapt))
SHOW_TYPE(TupleIndexAdaptor<Tup>::ResType)
SHOW_EXPR(idxAdapt.size())
SHOW_EXPR(idxAdapt[0])
SHOW_EXPR(idxAdapt[1])
SHOW_EXPR(getElm<0>(static_cast<Tup&> (idxAdapt)))
SHOW_EXPR(getElm<1>(static_cast<Tup&> (idxAdapt)))
SHOW_TYPE(decltype(idxAdapt[1]))
using Tup2 = std::tuple<int,int&, int>;
int ii{23};
Tup2 tup21{-1,ii,55};
TupleIndexAdaptor idxApt2{tup21};
SHOW_TYPE(decltype(tup21))
using IdxApt2 = decltype(idxApt2);
SHOW_TYPE(IdxApt2)
SHOW_TYPE(IdxApt2::ResType)
SHOW_EXPR(idxApt2[0])
SHOW_EXPR(idxApt2[1])
SHOW_EXPR(idxApt2[2])
SHOW_EXPR(++idxApt2[0])
SHOW_EXPR(idxApt2[1]++)
SHOW_EXPR(++++idxApt2[2])
SHOW_EXPR(tup21)
SHOW_EXPR(Tup2(idxApt2))
SHOW_EXPR(ii)
using TupIt = lib::IterableDecorator<lib::IdxStoreCore<IdxApt2>>;
TupIt tupIt{move(idxApt2)};
SHOW_EXPR( bool(tupIt) )
SHOW_EXPR( *tupIt )
SHOW_TYPE(decltype(*tupIt))
++*tupIt;
--ii;
SHOW_EXPR( *tupIt )
SHOW_EXPR(util::join(tupIt, "▶▶"))
SHOW_EXPR(( reconcilable<long,string> ))
SHOW_EXPR(( reconcilable<long&,string&> ))
SHOW_EXPR(( reconcilable<long,short> ))
SHOW_EXPR(( reconcilable<long&,short&> ))
SHOW_EXPR(( reconcilable_ref<long,string> ))
SHOW_EXPR(( reconcilable_ref<long&,string&> ))
SHOW_EXPR(( reconcilable_ref<long,short> ))
SHOW_EXPR(( reconcilable_ref<long&,const long&> ))
using TX = CommonResult_t<long&,const long&>;
SHOW_TYPE(TX)
SHOW_EXPR(( reconcilable<float*,void*> ))
SHOW_EXPR(( reconcilable<void,void*> ))
SHOW_EXPR(( reconcilable<float*&,void*&> ))
using TX2 = CommonResult_t<float*&,float*&>;
SHOW_TYPE(TX2)
SHOW_EXPR(( reconcilable<Uber,Unta> ))
SHOW_EXPR(( reconcilable<Untr,Unta> ))
SHOW_EXPR(( reconcilable<Untr&,Unta&> ))
SHOW_EXPR(( reconcilable<Uber&,Unta&> ))
SHOW_EXPR(( reconcilable_ref<Uber&,Unta&> ))
SHOW_EXPR(( reconcilable_ref<Uber&,Unta> ))
SHOW_EXPR(( reconcilable<Uber&,Unta> ))
SHOW_EXPR(( reconcilable<Uber,Untr&> ))
using TX3 = CommonResult_t<Uber&,Unta>;
SHOW_TYPE(TX3)
using TX4 = CommonResult_t<Unta&, const Uber&>;
SHOW_TYPE(TX4)
SHOW_EXPR(( reconcilable<Uber&,Unta&,Untr&> ))
using TX5 = CommonResult_t<const Uber&,Unta&,Untr&>;
SHOW_TYPE(TX5)
using TX6 = CommonResult_t<const int&,uint16_t,int64_t>;
SHOW_TYPE(TX6)
SHOW_EXPR(( CommonResult<int,string>::value ))
SHOW_EXPR(( CommonResult<int,string,long>::value ))
SHOW_EXPR(( CommonResult<const int,int&>::value ))
SHOW_EXPR(( reconcilable<const int&,uint16_t,int64_t> ))
SHOW_EXPR(( reconcilable<const Uber&,Unta&,Untr&> ))
SHOW_EXPR(( reconcilable<Uber&,const Unta&,Untr&> ))
showCase<float>(0.2 ,-2,-1.8);
showCase<float>(0 ,-2,-1.8);
showCase<float>(-0.8,-2,-1.8);
showCase<float>(-1.79,-2,-1.8);
showCase<float>(-1.8,-2,-1.8);
showCase<float>(-1.81,-2,-1.8);
showCase<float>(-1.99,-2,-1.8);
showCase<float>(-2 ,-2,-1.8);
showCase<float>(-2.01,-2,-1.8);
cout << "\n.gulp." <<endl;
return 0;

View file

@ -30,8 +30,11 @@
#ifndef LIB_HASH_VALUE_H
#define LIB_HASH_VALUE_H
#include <stdlib.h>
#ifdef __cplusplus
#include <cstdlib>
#else
#include <stdlib.h>
#endif
/**
* storage for a Lumiera unique ID,
@ -44,8 +47,6 @@ typedef lumiera_uid* LumieraUid;
#ifdef __cplusplus /* =========== C++ definitions ====================== */
#include <climits>
namespace lib {
/** a STL compatible hash value */

View file

@ -21,7 +21,6 @@
#ifndef LIB_INTEGRAL_H
#define LIB_INTEGRAL_H
#include <stdlib.h>
#include <cstddef>
#include <cstdint>
@ -38,5 +37,5 @@ static_assert(10 <= sizeof(f128));
const uint LIFE_AND_UNIVERSE_4EVER = 42;
#endif /*LIB_INTEGRAL_H*/

View file

@ -26,6 +26,7 @@
#include <unistd.h>
#include <string.h>
#include <stdint.h>
#include <stdlib.h>

View file

@ -46,7 +46,6 @@
#include "lib/hash-value.h" /* Types lumiera_uid and LumieraUid */
#include <stdlib.h>

View file

@ -22,12 +22,15 @@
#include "lib/integral.hpp"
#include <type_traits>
#include <concepts>
#include <cstdlib>
#include <climits>
#include <cfloat>
#include <limits>
#include <cmath>
#include <nobug.h>
namespace util {
@ -136,7 +139,7 @@ namespace util {
}
/** quantise towards ceiling.
/** quantise towards ceiling.
* This function models to fit something into a tiled structure.
*/
template<typename U> requires std::unsigned_integral<U>
@ -154,6 +157,103 @@ namespace util {
/**
* Transform into a cyclically wrapping domain.
* @tparam N any integral type, signed or unsigned
* @return mapping into the range [minVal...maxVal(
* @remark This function exploits the equivalence between signed and corresponding unsigned type
* to perform the modulus computation with positive numbers, because the modulus operator of C++
* works flipped for negative numbers, and oriented towards zero, while we want here always
* the same cyclic wrap-around, irrespective of the position relative to zero.
* - mathematically we want: `minVal + (rawVal-minVal) modulus PERIOD`
* - the unsigned representation of a negative number wraps cleanly, and thus maxVal - minVal,
* when converted into unsigned, is off by multiples of the whole number domain length, which
* is again absorbed by wrapping. Thus surprisingly `PERIOD = U(U(maxVal) - U(minVal))` is exact
* and can always be represented in the unsigned type.
* - for rawVal < minVal a flipped definition is used that can also be represented as unsigned:
* ** define dist ≔ minVal - rawval, then dist ≥ 1 for rawVal < minVal
* ** mathematically we need offset ≡ (-dist) modulus PERIOD
* ** we could compute PERIOD - (dist % PERIOD) -- but that would flip for dist ≡ PERIOD
* ** thus both arguments are shifted by -1, which keeps them in-range and computable as unsigned
* @note the repeated casts `U(signed)` prevent C++ integer promotion to kick in (esp. by short and char)
* @remark Claude was used (9/26) to improve and then formally verify this solution.
*/
template<std::integral N>
inline constexpr N
cyclicWrap (N rawVal, N minVal, N maxVal)
{
REQUIRE (minVal < maxVal);
using U = std::make_unsigned_t<N>;
const U PERIOD = U(U(maxVal) - U(minVal)); // always representable
U offset;
if (rawVal >= minVal)
offset = U(U(rawVal) - U(minVal)) % PERIOD;
else
{
const U dist = U(U(minVal) - U(rawVal)); // dist ≥ 1
offset = PERIOD-1 - (dist-1) % PERIOD; // ≡ (-dist) modulus PERIOD
}
return N(U(U(minVal) + offset));
}
/**
* Transform into a cyclically wrapping domain: floating point implementation.
* @return mapping into the range [minVal...maxVal(
* @warning this implementation can not cover extreme numeric values beyond #limit_cyclicWrap()
* @remark within the usable numeric range, the flipped modulus for values below minVal can simply
* be fixed by shifting once by PERIOD. But there is a caveat: extremely small negative offsets
* could be absorbed, so that the result lands on +PERIOD
*/
template<std::floating_point F>
inline constexpr F
cyclicWrap (F rawVal, F minVal, F maxVal)
{
REQUIRE (minVal < maxVal);
const F PERIOD = maxVal - minVal;
F offset = std::fmod (rawVal - minVal, PERIOD);
if (offset < 0)
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
return res < maxVal? res : minVal;
}
/**
* Validation helper for the floating-point implementation of #cyclicWrap().
* Values beyond the computed limit can not be handled by the implementation,
* due to the limited internal resolution of floating-point computations.
* @param period length of the cyclic wrap period (≔ maxVal - minVal)
* @return a limit for each parameter values, taken absolutely
* @remark at the core of the #cyclicWrap() computation sits a numeric difference,
* taken modulo the period length. Each floating point number has a minimum resolution ("ULP").
* When the ULP of the difference computation surpasses the periodic cycle length, the cyclicWrap
* becomes meaningless (albeit computed correct without glitch). And since that offset is then
* added on top of meanValue, the same argument holds, so `std::abs (minVal) <= limit` is required,
* and by transitive argument also for `maxVal` and `rawVal`. Furthermore, the difference and the
* period length must also be finite numbers by themselves.
* @note for performance reasons, that limit should be computed once, checked against `minVal` and `maxVal`,
* and then before each computation of #cyclicWrap(), `std::abs (rawVal) <= limit` should be checked.
* It is recommended to fail with an hard error, since resolving theses settings typically requires
* an intervention by the user (parameters need to be adjusted).
* @remark Claude was used (9/26) to analyse the computation and derive a boundary criterion.
*/
template<std::floating_point F>
inline constexpr F
limit_cyclicWrap (F period)
{
REQUIRE (std::isfinite (period));
F limit = period / std::numeric_limits<F>::epsilon();
if (not std::isfinite (limit))
limit = std::numeric_limits<F>::max();
return limit / 2; // exact
}
/**
* epsilon comparison of doubles.
* @remarks Floating point calculations are only accurate up to a certain degree,
@ -170,12 +270,13 @@ namespace util {
* @see https://en.wikipedia.org/wiki/Unit_in_the_last_place
* @todo 3/2024 seems we have solved this problem several times meanwhile /////////////////////////////////TICKET #1360 sort out floating-point rounding and precision
*/
template<std::floating_point F>
inline constexpr bool
almostEqual (double d1, double d2, uint ulp =2) noexcept
almostEqual (F d1, F d2, uint ulp =2) noexcept
{
using std::fabs;
return fabs (d1-d2) < DBL_EPSILON * fabs (d1+d2) * ulp
or fabs (d1-d2) < DBL_MIN; // special treatment for subnormal results
return fabs (d1-d2) < std::numeric_limits<F>::epsilon() * fabs (d1+d2) * ulp
or fabs (d1-d2) < std::numeric_limits<F>::min(); // special treatment for subnormal results
}

View file

@ -32,6 +32,7 @@
#include <unistd.h>
#include <stdint.h>
#include <stdlib.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>

View file

@ -53,6 +53,11 @@ return: 0
END
TEST "Cyclic wrapping number domain" CyclicWrap_test <<END
return: 0
END
TEST "Deleter function collection" DelStash_test <<END
return: 0
END

View file

@ -0,0 +1,144 @@
/*
CyclicWrap(Test) - verify cyclically wrapping number domain
Copyright (C)
2026, Hermann Vosseler <Ichthyostega@web.de>
  **Lumiera** is free software; you can redistribute it and/or modify it
  under the terms of the GNU General Public License as published by the
  Free Software Foundation; either version 2 of the License, or (at your
  option) any later version. See the file COPYING for further details.
* *****************************************************************/
/** @file util-floordiv-test.cpp
** unit test \ref CyclicWrap_test
*/
#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;
namespace util {
namespace test {
namespace{ // Test helpers...
template<typename NUM>
constexpr NUM _MAX = std::numeric_limits<NUM>::max();
template<typename NUM>
constexpr NUM _MIN = std::numeric_limits<NUM>::lowest();
} // (End) Test helpers
/******************************************************************//**
* @test Demonstrate and verify a cyclic wrapping numeric domain.
* - show the wrapping behaviour with simple numbers
* - verify the behaviour pattern for all integer types (signed / unsigned)
* - 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
* @see util::cyclicWrap
* @see parameter-scale-test.cpp
*/
class CyclicWrap_test : public Test
{
virtual void
run (Arg)
{
simpleDemonstration();
verifyIntegerTypes<int>();
verifyIntegerTypes<uint>();
verifyIntegerTypes<int8_t>();
verifyIntegerTypes<uint8_t>();
verifyIntegerTypes<int16_t> ();
verifyIntegerTypes<uint16_t>();
verifyIntegerTypes<int32_t> ();
verifyIntegerTypes<uint32_t>();
verifyIntegerTypes<int64_t> ();
verifyIntegerTypes<uint64_t>();
}
void
simpleDemonstration ()
{
CHECK (10 == cyclicWrap (10,10,15));
CHECK (11 == cyclicWrap (11,10,15));
CHECK (12 == cyclicWrap (12,10,15));
CHECK (13 == cyclicWrap (13,10,15));
CHECK (14 == cyclicWrap (14,10,15));
CHECK (10 == cyclicWrap (15,10,15));
CHECK (11 == cyclicWrap (16,10,15));
CHECK (12 == cyclicWrap (17,10,15));
CHECK (13 == cyclicWrap (18,10,15));
CHECK (14 == cyclicWrap (19,10,15));
CHECK (10 == cyclicWrap (20,10,15));
CHECK (10 == cyclicWrap ( 0,10,15));
CHECK (14 == cyclicWrap (-1,10,15));
}
template<typename I>
void
verifyIntegerTypes ()
{
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); };
CHECK (LOWER == cyc(LOWER+0) );
CHECK (LOWER+1 == cyc(LOWER+1) );
CHECK (LOWER+2 == cyc(LOWER+2) );
CHECK (LOWER+3 == cyc(LOWER+3) );
CHECK (LOWER+4 == cyc(LOWER+4) );
CHECK (LOWER == cyc(LOWER+5) );
CHECK (LOWER == cyc(LOWER-5) );
CHECK (LOWER+1 == cyc(LOWER-4) );
CHECK (LOWER+2 == cyc(LOWER-3) );
CHECK (LOWER+3 == cyc(LOWER-2) );
CHECK (LOWER+4 == cyc(LOWER-1) );
CHECK (isLimited (LOWER, cyc(_MAX<I>), UPPER));
CHECK (isLimited (LOWER, cyc(_MIN<I>), UPPER));
CHECK (isLimited (LOWER, cyc(_MAX<I>-1), UPPER));
CHECK (isLimited (LOWER, cyc(_MIN<I>+1), UPPER));
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;
I offset = 5 - excess; // flip orientation (since the input val lies below LOWER)
CHECK (LOWER + offset == cyc(_MIN<I>) );
}
};
LAUNCHER (CyclicWrap_test, "unit common");
}} // namespace util::test

View file

@ -30,7 +30,7 @@
using ::Test;
using util::isnil;
using util::_Fmt;
using std::integral;
namespace util {
namespace test {
@ -42,6 +42,13 @@ namespace test {
const uint NUM_ELMS_PERFORMANCE_TEST = 50000000;
const uint NUMBER_LIMIT = 1 << 30;
template<typename NUM>
constexpr NUM _MAX = std::numeric_limits<NUM>::max();
template<typename NUM>
constexpr NUM _MIN = std::numeric_limits<NUM>::lowest();
typedef std::vector<int> VecI;
VecI
@ -108,7 +115,7 @@ namespace test {
{
seedRand();
verifyBehaviour ();
verifyBehaviour();
verifyIntegerTypes<int>();
verifyIntegerTypes<long>();
@ -116,6 +123,8 @@ namespace test {
verifyIntegerTypes<int64_t>();
verifyIntegerTypes<llong>();
verifyBounds();
if (not isnil (arg))
runPerformanceTest();
}
@ -154,7 +163,7 @@ namespace test {
template<typename I>
void
verifyIntegerTypes ()
verifyIntegerTypes()
{
I n,d,expectedRes;
@ -168,11 +177,51 @@ namespace test {
}
void
verifyBounds()
{
auto verify = []<integral I>(I num, I den, I expect_quot, I expect_rem)
{
auto res = floorwrap (num,den);
CHECK (res.quot == expect_quot, "quotient mismatch");
CHECK (res.rem == expect_rem , "remainder mismatch");
CHECK (res.quot == floordiv (num,den), "floordiv diverges");
};
//----- num | den | quotient | remainder
verify (_MAX<int>, 1, _MAX<int> , 0);
verify (_MAX<int>, 2, _MAX<int>/2 , 1);
verify (_MAX<int>, 5, _MAX<int>/5 , 2);
verify (_MIN<int>, 1, _MIN<int> , 0);
verify (_MIN<int>, 2, _MIN<int>/2 , 0);
verify (_MIN<int>, 5, _MIN<int>/5 -1, 2); // _MIN<int> ≡ -2147483648 /5 ≡ -429496729 -1 step ≡ -429496730 · 5 ≡ -2147483650 ⟹ Δ = +2
verify (_MAX<int>,-1, -_MAX<int> , 0);
verify (_MAX<int>,-2, -_MAX<int>/2 -1,-1);
verify (_MAX<int>,-5, -_MAX<int>/5 -1,-3); // Δ ≡ -3 here since -_MAX<int> ≡ _MIN<int> + 1
verify (_MAX<uint>, 1u, _MAX<uint> ,0u);
verify (_MAX<uint>, 2u, _MAX<uint>/2 ,1u);
verify (_MAX<uint>, 5u, _MAX<uint>/5 ,0u);
verify (_MIN<int64_t>, int64_t(1), _MIN<int64_t> , int64_t(0));
verify (_MIN<int64_t>, int64_t(2), _MIN<int64_t>/2 , int64_t(0));
verify (_MIN<int64_t>, int64_t(5), _MIN<int64_t>/5 -1, int64_t(2)); // 2^63 / 5 ≡ 1844674407370955161.6 ; 1844674407370955162 · 5 ⟹ Δ = +2
verify (_MIN<int8_t>, int8_t(1), int8_t(-128) , int8_t(0));
verify (_MIN<int8_t>, int8_t(2), int8_t(-64 ) , int8_t(0));
verify (_MIN<int8_t>, int8_t(5), int8_t(-26 ) , int8_t(2)); // 2^7 / 5 ≡ 25.6 ; 26 · 5 ≡ 130 ⟹ Δ = +2
verify (_MAX<int8_t>, int8_t(5), int8_t( 25 ) , int8_t(2)); // (2^7-1) / 5 ≡ 25.4 ; 25 · 5 ≡ 125 ⟹ Δ = +2
}
/** @test timing measurements to compare implementation details.
* This test uses a sequence of random integers, where the values
* used as denominator are ensured not to be zero.
*
*
* \par measurement results
* My experiments (AMD Athlon-64 4200 X2) gave me
* the following timing measurements in nanoseconds:

View file

@ -36,7 +36,7 @@
#include "lib/format-string.hpp"
#include "lib/format-cout.hpp"
#include "lib/time/timevalue.hpp"
#include "lib/luid.h"
#include "lib/hash-indexed.hpp"
#include "lib/util.hpp"
#include <boost/lexical_cast.hpp>

File diff suppressed because it is too large Load diff