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.
This commit is contained in:
Fischlurch 2026-09-13 18:53:22 +02:00
parent 426d7c610e
commit 77257c5653
9 changed files with 1071 additions and 483 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

@ -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