summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSven Gothel <[email protected]>2022-05-01 06:57:55 +0200
committerSven Gothel <[email protected]>2022-05-01 06:57:55 +0200
commit043ab5ce128b4e6b6e08034f0d050fb1d8308c33 (patch)
treeef51a19266a9851827416291221ed42e90357f0e
parentea2a46c821d23b02095a7a78b0342a97c30ea728 (diff)
Introduce new types: fraction, fraction_timespec; its constants & literals as well adoption in latch, ringbuffer, service_runner and simple_timer.
Also adds string -> fraction conversion via to_fraction_i64(), supported by environment. fraction provides similar properties like C++11's `std::ratio`, but is evaluated at runtime time without `constexpr` constraints using a common integral template type. std::ratio is evaluated at compile time and must use `constexpr` literal values. fraction provides similar properties like C++11's `std::chrono::duration`, but is flexible with its denominator and always reduce() its fraction to the lowest terms. `std::chrono::duration` uses a fixed `std::ratio` denominator and hence is inflexible. Further, fraction can be converted to std::chrono::duration, matching the selected duration's period, see to_duration_count() and to_duration(). ... see fraction_type.hpp
-rw-r--r--include/jau/environment.hpp41
-rw-r--r--include/jau/fraction_type.hpp1045
-rw-r--r--include/jau/int_math.hpp90
-rw-r--r--src/basic_types.cpp73
-rw-r--r--src/environment.cpp17
-rw-r--r--test/test_fractions_01.cpp734
6 files changed, 2000 insertions, 0 deletions
diff --git a/include/jau/environment.hpp b/include/jau/environment.hpp
index c2f855f..261f103 100644
--- a/include/jau/environment.hpp
+++ b/include/jau/environment.hpp
@@ -70,11 +70,37 @@ namespace jau {
public:
/**
+ * Module startup time t0 in monotonic time using high precision and range of fraction_timespec.
+ */
+ static const fraction_timespec startupTimeMonotonic;
+
+ /**
* Module startup time t0 in monotonic time in milliseconds.
*/
static const uint64_t startupTimeMilliseconds;
/**
+ * Returns elapsed monotonic time using fraction_timespec since module startup,
+ * see {@link #startupTimeMonotonic} and getMonotonicTime().
+ * <pre>
+ * return getMonotonicTime() - startupTimeMonotonic;
+ * </pre>
+ */
+ static fraction_timespec getElapsedMonotonicTime() noexcept {
+ return getMonotonicTime() - startupTimeMonotonic;
+ }
+
+ /**
+ * Returns elapsed monotonic time using fraction_timespec since module startup up to the given current_ts, see {@link #startupTimeMonotonic}.
+ * <pre>
+ * return current_ts - startupTimeMonotonic;
+ * </pre>
+ */
+ static fraction_timespec getElapsedMonotonicTime(const fraction_timespec& current_ts) noexcept {
+ return current_ts - startupTimeMonotonic;
+ }
+
+ /**
* Returns current elapsed monotonic time in milliseconds since module startup, see {@link #startupTimeMilliseconds}.
*/
static uint64_t getElapsedMillisecond() noexcept {
@@ -158,6 +184,21 @@ namespace jau {
const uint32_t min_allowed=0, const uint32_t max_allowed=UINT32_MAX) noexcept;
/**
+ * Returns the fraction_i64 value of the environment's variable 'name' in format `<num>/<denom>`,
+ * with white space allowed, if within given fraction_i64 value range.
+ *
+ * Otherwise returns the 'default_value' if the environment variable's value is null
+ * or of invalid format or not within given fraction_i64 value range.
+ * <p>
+ * Implementation uses {@link #getProperty(const std::string & name)}
+ * and hence attempts to also find a Unix conform name,
+ * e.g. 'direct_bt_debug' if ''direct_bt.debug' wasn't found.
+ * </p>
+ */
+ static fraction_i64 getFractionProperty(const std::string & name, const fraction_i64& default_value,
+ const fraction_i64& min_allowed, const fraction_i64& max_allowed) noexcept;
+
+ /**
* Fetches exploding variable-name (prefix_domain) values.
* <p>
* Implementation uses {@link #getProperty(const std::string & name)}
diff --git a/include/jau/fraction_type.hpp b/include/jau/fraction_type.hpp
new file mode 100644
index 0000000..73aa67b
--- /dev/null
+++ b/include/jau/fraction_type.hpp
@@ -0,0 +1,1045 @@
+/*
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef JAU_FRACTION_TYPE_HPP_
+#define JAU_FRACTION_TYPE_HPP_
+
+#include <ratio>
+#include <chrono>
+#include <condition_variable>
+
+#include <stdio.h>
+#include <cinttypes>
+
+#include <sstream>
+#include <cstdint>
+#include <cmath>
+
+#include <jau/int_types.hpp>
+#include <jau/int_math.hpp>
+#include <jau/ordered_atomic.hpp>
+
+namespace jau {
+
+ extern void print_backtrace(const bool skip_anon_frames, const jau::snsize_t max_frames, const jau::snsize_t skip_frames) noexcept;
+
+ // Remember: constexpr specifier used in a function or static data member (since C++17) declaration implies inline.
+
+ /**
+ * Fraction template type using integral values, evaluated at runtime.
+ *
+ * All operations reduce its fraction to the lowest terms using
+ * the greatest common divisor (gcd()) following Euclid's algorithm from Euclid's Elements ~300 BC,
+ * see reduce().
+ *
+ * fraction provides similar properties like C++11's `std::ratio`,
+ * but is evaluated at runtime time without `constexpr` constraints using a common integral template type.
+ * std::ratio is evaluated at compile time and must use `constexpr` literal values.
+ *
+ * fraction provides similar properties like C++11's `std::chrono::duration`,
+ * but is flexible with its denominator and always reduce() its fraction to the lowest terms.
+ * `std::chrono::duration` uses a fixed `std::ratio` denominator and hence is inflexible.
+ *
+ * Further, fraction can be converted to std::chrono::duration,
+ * matching the selected duration's period, see to_duration_count() and to_duration().
+ *
+ * The following properties are exposed:
+ * - Numerator carries sign and hence can be negative and can be of signed type
+ * - Denominator is always positive and is always an unsigned type
+ * - All operations incl. construction will result in a reduced fraction using the greatest common denominator, see gcd().
+ * - No exceptions are thrown, a zero denominator is undefined behavior, implementation will return zero { n=0, d=1 }.
+ *
+ * See usable fixed typedef's
+ * - fraction_i64
+ * - fraction_ui64
+ *
+ * fraction_timespec covers high precision and almost infinite range of time
+ * similar to `struct timespec_t`.
+ *
+ * Counting nanoseconds in int64_t only lasts until `2262-04-12`,
+ * since INT64_MAX is 9'223'372'036'854'775'807 for 9'223'372'036 seconds or 292 years.
+ *
+ * Hence using one may use fraction_i64 for durations up to 292 years
+ * and fraction_timespec for almost infinite range of time-points or durations beyond 292 years.
+ *
+ * Constants are provided in in namespace jau::fractions_i64,
+ * from fractions_i64::pico to fractions_i64::tera, including fractions::fractions_i64 to fractions::fractions_i64, etc.
+ *
+ * Literal operators are provided in namespace jau::fractions_i64_literals,
+ * e.g. for `3_s`, `100_ns` ... literals.
+ *
+ * @tparam int_type
+ * @tparam
+ */
+ template<typename Int_type,
+ std::enable_if_t< std::is_integral_v<Int_type>, bool> = true>
+ class fraction {
+ public:
+ /** User defined integral integer template type, used for numerator and may be signed. */
+ typedef Int_type int_type;
+
+ /** unsigned variant of template int_type, used for denominator. */
+ typedef std::make_unsigned_t<int_type> uint_type;
+
+ /** Numerator, carries the sign. */
+ int_type num;
+ /** Denominator, always positive. */
+ uint_type denom;
+ /** Overflow flag. If set, last arithmetic operation produced an overflow. Must be cleared manually. */
+ bool overflow;
+
+ private:
+ void set_overflow() noexcept {
+ overflow = true;
+ print_backtrace(true /* skip_anon_frames */, 6 /* max_frames */, 2 /* skip_frames */);
+ num = std::numeric_limits<int_type>::max();
+ denom = std::numeric_limits<uint_type>::max();
+ }
+
+ public:
+#if 0
+ template<typename _ToDur, typename _Rep, typename _Period>
+ constexpr __enable_if_is_duration<_ToDur>
+ duration_cast(const duration<_Rep, _Period>& __d)
+ {
+ typedef typename _ToDur::period __to_period;
+ typedef typename _ToDur::rep __to_rep;
+ typedef ratio_divide<_Period, __to_period> __cf;
+ typedef typename common_type<__to_rep, _Rep, intmax_t>::type
+ __cr;
+ typedef __duration_cast_impl<_ToDur, __cf, __cr,
+ __cf::num == 1, __cf::den == 1> __dc;
+ return __dc::__cast(__d);
+ }
+#endif
+
+ /**
+ * Constructs a zero fraction instance { 0, 1 }
+ */
+ constexpr fraction() noexcept
+ : num(0), denom(1), overflow(false) { }
+
+
+ /**
+ * Constructs a fraction instance with smallest numerator and denominator using gcd()
+ *
+ * Note: sign is always stored in fraction's numerator, i.e. the denominator is always positive.
+ *
+ * @param n the given numerator
+ * @param d the given denominator
+ */
+ template <typename T,
+ std::enable_if_t< std::is_same_v<int_type, T> &&
+ !std::is_unsigned_v<T>, bool> = true>
+ constexpr fraction(const int_type n, const T d) noexcept
+ : num(0), denom(1), overflow(false)
+ {
+ if( n != 0 && d != 0 ) {
+ // calculate smallest num and denom, both arguments 'n' and 'd' may be negative
+ const uint_type abs_d = abs(d);
+ const uint_type _gcd = gcd<uint_type>( (uint_type)abs(n), abs_d );
+ num = ( n * jau::sign(d) ) / (int_type)_gcd;
+ denom = abs_d / _gcd;
+ }
+ }
+
+
+ /**
+ * Constructs a fraction instance with smallest numerator and denominator using gcd()
+ *
+ * Note: sign is always stored in fraction's numerator, i.e. the denominator is always positive and hence unsigned.
+ *
+ * @param n the given numerator
+ * @param d the given denominator
+ */
+ constexpr fraction(const int_type n, const uint_type abs_d) noexcept
+ : num(0), denom(1), overflow(false)
+ {
+ if( n != 0 && abs_d != 0 ) {
+ // calculate smallest num and denom, only given argument 'n' can be negative
+ const uint_type _gcd = gcd<uint_type>( (uint_type)abs(n), abs_d );
+ num = n / (int_type)_gcd;
+ denom = abs_d / _gcd;
+ }
+ }
+
+ // We use the implicit default copy- and move constructor and assignment operations,
+ // rendering fraction TriviallyCopyable
+#if 0
+ constexpr fraction(const fraction<int_type> &o) noexcept
+ : num(o.num), denom(o.denom) { }
+
+ constexpr fraction(fraction<int_type> &&o) noexcept
+ : num(std::move(o.num)), denom(std::move(o.denom)) { }
+
+ constexpr fraction& operator=(const fraction<int_type> &o) noexcept {
+ num = o.num;
+ denom = o.denom;
+ return *this;
+ }
+ constexpr fraction& operator=(fraction<int_type> &&o) noexcept {
+ num = std::move( o.num );
+ denom = std::move( o.denom );
+ return *this;
+ }
+#endif
+
+ /**
+ * Reduce this fraction to the lowest terms using the greatest common denominator, see gcd(), i.e. normalization.
+ *
+ * Might need to be called after manual modifications on numerator or denominator.
+ *
+ * Not required after applying any provided operation as they normalize the fraction.
+ */
+ constexpr fraction<int_type>& reduce() noexcept {
+ if( num != 0 && denom != 0 ) {
+ const uint_type _gcd = gcd<uint_type>( (uint_type)abs(num), denom );
+ num /= static_cast<int_type>(_gcd);
+ denom /= _gcd;
+ }
+ return *this;
+ }
+
+ /**
+ * Converts this this fraction to a numerator for the given new base fraction.
+ *
+ * If overflow_ptr is not nullptr, true is stored if an overflow occurred otherwise false.
+ *
+ * @param new_base the new base fraction for conversion
+ * @param overflow_ptr optional pointer to overflow result, defaults to nullptr
+ * @return numerator representing this fraction on the new base, or std::numeric_limits<int_type>::max() if an overflow occurred.
+ */
+ constexpr int_type to_num_of(const fraction<int_type>& new_base, bool * overflow_ptr=nullptr) const noexcept {
+ // const uint_type _lcm = lcm<uint_type>( denom, new_base.denom );
+ // return ( num * (int_type)( _lcm / denom ) ) / new_base.num;
+ //
+ int_type r;
+ if( mul_overflow(num, (int_type)new_base.denom, r) ) {
+ if( nullptr != overflow_ptr ) {
+ *overflow_ptr = true;
+ }
+ return std::numeric_limits<int_type>::max();
+ } else {
+ if( nullptr != overflow_ptr ) {
+ *overflow_ptr = false;
+ }
+ return r / (int_type)denom / new_base.num;
+ }
+ }
+
+ /**
+ * Converts this this fraction to a numerator for the given new base fraction.
+ *
+ * If overflow_ptr is not nullptr, true is stored if an overflow occurred otherwise false.
+ *
+ * @param new_base_num the new base numerator for conversion
+ * @param new_base_denom the new base denominator for conversion
+ * @param new_base the new base fraction for conversion
+ * @param overflow_ptr optional pointer to overflow result, defaults to nullptr
+ * @return numerator representing this fraction on the new base, or std::numeric_limits<int_type>::max() if an overflow occurred.
+ */
+ constexpr int_type to_num_of(const int_type new_base_num, const uint_type new_base_denom, bool * overflow_ptr=nullptr) const noexcept {
+ int_type r;
+ if( mul_overflow(num, (int_type)new_base_denom, r) ) {
+ if( nullptr != overflow_ptr ) {
+ *overflow_ptr = true;
+ }
+ return std::numeric_limits<int_type>::max();
+ } else {
+ if( nullptr != overflow_ptr ) {
+ *overflow_ptr = false;
+ }
+ return r / (int_type)denom / new_base_num;
+ }
+ }
+
+ /**
+ * Convenient shortcut to `to_num_of(1_ms)`
+ * @return time in milliseconds
+ * @see to_num_of()
+ */
+ constexpr int_type to_ms() const noexcept { return to_num_of(1l, 1'000lu); }
+
+ /**
+ * Convenient shortcut to `to_num_of(1_ns)`
+ * @return time in nanoseconds
+ * @see to_num_of()
+ */
+ constexpr int_type to_ns() const noexcept { return to_num_of(1l, 1'000'000'000lu); }
+
+ /** Returns the converted fraction to lossy float */
+ constexpr float to_float() const noexcept { return (float)num / (float)denom; }
+
+ /** Returns the converted fraction to lossy double */
+ constexpr double to_double() const noexcept { return (double)num / (double)denom; }
+
+ /** Returns the converted fraction to lossy long double */
+ constexpr long double to_ldouble() const noexcept { return (long double)num / (long double)denom; }
+
+ /**
+ * Constructs a fraction from the given std::chrono::duration and its Rep and Period
+ * with smallest numerator and denominator using gcd()
+ *
+ * Note: sign is always stored in fraction's numerator, i.e. the denominator is always positive and hence unsigned.
+ *
+ * @tparam Rep Rep of given std::chrono::duration
+ * @tparam Period Period of given std::chrono::duration
+ * @param dur std::chrono::duration reference to convert into a fraction
+ */
+ template<typename Rep, typename Period>
+ constexpr fraction(const std::chrono::duration<Rep, Period>& dur) noexcept
+ : num(0), denom(1), overflow(false)
+ {
+ if( dur.count()*Period::num != 0 && Period::den != 0 ) {
+ // calculate smallest num and denom, both arguments 'n' and 'd' may be negative
+ const int_type n = dur.count()*Period::num;
+ const int_type d = Period::den;
+ const uint_type abs_d = abs(d);
+ const uint_type _gcd = gcd<uint_type>( (uint_type)abs(n), abs_d );
+ num = ( n * jau::sign(d) ) / (int_type)_gcd;
+ denom = abs_d / _gcd;
+ }
+ }
+
+ /**
+ * Convert this fraction into std::chrono::duration with given Rep and Period
+ *
+ * If overflow_ptr is not nullptr, true is stored if an overflow occurred otherwise false.
+ *
+ * @tparam Rep std::chrono::duration numerator type
+ * @tparam Period std::chrono::duration denominator type, i.e. a std::ratio
+ * @param dur_ref std::chrono::duration reference to please automated template type deduction and ease usage
+ * @param overflow_ptr optional pointer to overflow result, defaults to nullptr
+ * @return fraction converted into given std::chrono::duration Rep and Period, or using (Rep)std::numeric_limits<Rep>::max() if an overflow occurred
+ */
+ template<typename Rep, typename Period>
+ std::chrono::duration<Rep, Period> to_duration(const std::chrono::duration<Rep, Period>& dur_ref, bool * overflow_ptr=nullptr) const noexcept {
+ (void)dur_ref; // just to please template type deduction
+ bool overflow_ = false;
+ const int_type num_ = to_num_of( fraction<int_type>( (int_type)Period::num, (uint_type)Period::den ), &overflow_ );
+ if( overflow_ ) {
+ if( nullptr != overflow_ptr ) {
+ *overflow_ptr = true;
+ }
+ return std::chrono::duration<Rep, Period>( (Rep)std::numeric_limits<Rep>::max() );
+ } else {
+ if( nullptr != overflow_ptr ) {
+ *overflow_ptr = false;
+ }
+ return std::chrono::duration<Rep, Period>( (Rep)num_ );
+ }
+ }
+
+ /**
+ * Returns a string representation of this fraction.
+ *
+ * If the overflow flag is set, ` O! ` will be appended.
+ *
+ * @param show_double true to show the double value, otherwise false (default)
+ * @return
+ */
+ std::string to_string(const bool show_double=false) const noexcept {
+ std::string r = std::to_string(num) + "/" + std::to_string(denom);
+ if( overflow ) {
+ r.append(" O! ");
+ } else if( show_double ) {
+ std::ostringstream out;
+ out.precision( std::max<nsize_t>( 6, digits10(denom, false /* sign_is_digit */) ) );
+ out << to_double();
+ r.append(" ( " + out.str() + " )");
+ }
+ return r;
+ }
+
+ /**
+ * Returns true if numerator is zero.
+ */
+ constexpr bool is_zero() const noexcept {
+ return 0 == num;
+ }
+
+ /**
+ * Returns the value of the sign function applied to numerator.
+ * <pre>
+ * -1 for numerator < 0
+ * 0 for numerator = 0
+ * 1 for numerator > 0
+ * </pre>
+ * @return function result
+ */
+ constexpr snsize_t sign() const noexcept {
+ return sign(num);
+ }
+
+ /**
+ * Unary minus
+ *
+ * @return new instance with negated value, reduced
+ */
+ constexpr fraction<int_type> operator-() const noexcept {
+ fraction<int_type> r(*this);
+ r.num *= (int_type)-1;
+ return r;
+ }
+
+ /**
+ * Multiplication of this fraction's numerator with scalar in place.
+ *
+ * Operation may set the overflow flag if occurring.
+ *
+ * @param rhs the scalar
+ * @return reference to this instance, reduced
+ */
+ constexpr fraction<int_type>& operator*=(const int_type& rhs ) noexcept {
+ if( mul_overflow(num, rhs, num) ) {
+ set_overflow();
+ return *this;
+ } else {
+ return reduce();
+ }
+ }
+
+ /**
+ * Division of this fraction's numerator with scalar in place.
+ *
+ * @param rhs the scalar
+ * @return reference to this instance, reduced
+ */
+ constexpr fraction<int_type>& operator/=(const int_type& rhs ) noexcept {
+ return this->operator/=(fraction<int_type>(rhs, (int_type)1));
+ }
+
+ /**
+ * Compound assignment (addition)
+ *
+ * Operation may set the overflow flag if occurring.
+ *
+ * @param rhs the other fraction
+ * @return reference to this instance, reduced
+ */
+ constexpr fraction<int_type>& operator+=(const fraction<int_type>& rhs ) noexcept {
+ if( denom == rhs.denom ) {
+ num += rhs.num;
+ } else {
+ uint_type _lcm;
+ if( lcm_overflow<uint_type>(denom, rhs.denom, _lcm) ) {
+ set_overflow();
+ return *this;
+ } else {
+ const int_type num_new = ( num * (int_type)( _lcm / denom ) ) + ( rhs.num * (int_type)( _lcm / rhs.denom ) );
+ num = num_new;
+ denom = _lcm;
+ }
+ }
+ return reduce();
+ }
+
+ /**
+ * Negative compound assignment (subtraction)
+ *
+ * Operation may set the overflow flag if occurring.
+ *
+ * @param rhs the other fraction
+ * @return reference to this instance, reduced
+ */
+ constexpr fraction<int_type>& operator-=(const fraction<int_type>& rhs ) noexcept {
+ if( denom == rhs.denom ) {
+ num -= rhs.num;
+ } else {
+ uint_type _lcm;
+ if( lcm_overflow<uint_type>(denom, rhs.denom, _lcm) ) {
+ set_overflow();
+ return *this;
+ } else {
+ const int_type num_new = ( num * (int_type)( _lcm / denom ) ) - ( rhs.num * (int_type)( _lcm / rhs.denom ) );
+ num = num_new;
+ denom = _lcm;
+ }
+ }
+ return reduce();
+ }
+
+
+ /**
+ * Multiplication in place.
+ *
+ * Operation may set the overflow flag if occurring.
+ *
+ * @param rhs the other fraction
+ * @return reference to this instance, reduced
+ */
+ constexpr fraction<int_type>& operator*=(const fraction<int_type>& rhs ) noexcept {
+ const uint_type gcd1 = gcd<uint_type>( (uint_type)abs(num), rhs.denom );
+ const uint_type gcd2 = gcd<uint_type>( (uint_type)abs(rhs.num), denom );
+ const int_type n1 = num / (int_type)gcd1;
+ const int_type n2 = rhs.num / (int_type)gcd2;
+ const uint_type d1 = denom / gcd2;
+ const uint_type d2 = rhs.denom / gcd1;
+
+ if( mul_overflow(n1, n2, num) || mul_overflow(d1, d2, denom) ) {
+ set_overflow();
+ }
+ return *this;
+ }
+
+ /**
+ * Division in place.
+ *
+ * @param rhs the other fraction
+ * @return reference to this instance, reduced
+ */
+ constexpr fraction<int_type>& operator/=(const fraction<int_type>& rhs ) noexcept {
+ // flipped rhs num and denom as compared to multiply
+ const uint_type abs_num2 = abs(rhs.num);
+ const uint_type gcd1 = gcd<uint_type>( (uint_type)abs(num), abs_num2 );
+ const uint_type gcd2 = gcd<uint_type>( rhs.denom , denom );
+
+ num = ( num / (int_type)gcd1 ) * jau::sign(rhs.num) * ( rhs.denom / (int_type)gcd2 );
+ denom = ( denom / gcd2 ) * ( abs_num2 / gcd1 );
+ return *this;
+ }
+ };
+
+ template<typename int_type>
+ inline std::string to_string(const fraction<int_type>& v) noexcept { return v.to_string(); }
+
+ template<typename int_type>
+ constexpr bool operator!=(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ return lhs.denom != rhs.denom || lhs.num != rhs.num;
+ }
+
+ template<typename int_type>
+ constexpr bool operator==(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ return !( lhs != rhs );
+ }
+
+ template<typename int_type>
+ constexpr bool operator>(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ return lhs.num * (int_type)rhs.denom > (int_type)lhs.denom * rhs.num;
+ }
+
+ template<typename int_type>
+ constexpr bool operator>=(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ return lhs.num * (int_type)rhs.denom >= (int_type)lhs.denom * rhs.num;
+ }
+
+ template<typename int_type>
+ constexpr bool operator<(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ return lhs.num * (int_type)rhs.denom < (int_type)lhs.denom * rhs.num;
+ }
+
+ template<typename int_type>
+ constexpr bool operator<=(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ return lhs.num * (int_type)rhs.denom <= (int_type)lhs.denom * rhs.num;
+ }
+
+ /** Return the maximum of the two given fractions */
+ template<typename int_type>
+ constexpr const fraction<int_type>& max(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ return lhs >= rhs ? lhs : rhs;
+ }
+
+ /** Return the minimum of the two given fractions */
+ template<typename int_type>
+ constexpr const fraction<int_type>& min(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ return lhs <= rhs ? lhs : rhs;
+ }
+
+ /**
+ * Returns the value of the sign function applied to numerator.
+ * <pre>
+ * -1 for numerator < 0
+ * 0 for numerator = 0
+ * 1 for numerator > 0
+ * </pre>
+ * @return function result
+ */
+ template<typename int_type>
+ constexpr snsize_t sign(const fraction<int_type>& rhs) noexcept {
+ return sign(rhs.num);
+ }
+
+ /**
+ * Returns the absolute fraction
+ */
+ template<typename int_type>
+ constexpr fraction<int_type> abs(const fraction<int_type>& rhs) noexcept {
+ fraction<int_type> copy(rhs); // skip normalize
+ copy.num = abs(rhs.num);
+ return copy;
+ }
+
+ /**
+ * Returns multiplication of fraction with scalar.
+ *
+ * Operation may set the overflow flag in the returned instance, if occurring.
+ *
+ * @tparam int_type integral type
+ * @param lhs the fraction
+ * @param rhs the scalar
+ * @return resulting new fraction, reduced
+ */
+ template<typename int_type>
+ constexpr fraction<int_type> operator*(const fraction<int_type>& lhs, const int_type& rhs ) noexcept {
+ return fraction<int_type>( lhs.num*rhs, lhs.denom );
+ }
+
+ /**
+ * Returns multiplication of fraction with scalar.
+ *
+ * Operation may set the overflow flag in the returned instance, if occurring.
+ *
+ * @tparam int_type integral type
+ * @param lhs the scalar
+ * @param rhs the fraction
+ * @return resulting new fraction, reduced
+ */
+ template<typename int_type>
+ constexpr fraction<int_type> operator*(const int_type& lhs, const fraction<int_type>& rhs) noexcept {
+ return fraction<int_type>( rhs.num*lhs, rhs.denom );
+ }
+
+ /**
+ * Returns division of fraction with scalar.
+ * @tparam int_type integral type
+ * @param lhs the fraction
+ * @param rhs the scalar
+ * @return resulting new fraction, reduced
+ */
+ template<typename int_type>
+ constexpr fraction<int_type> operator/(const fraction<int_type>& lhs, const int_type& rhs ) noexcept {
+ fraction<int_type> r(lhs);
+ return r /= rhs;
+ }
+
+ /**
+ * Returns division of fraction with scalar.
+ * @tparam int_type integral type
+ * @param lhs the scalar
+ * @param rhs the fraction
+ * @return resulting new fraction, reduced
+ */
+ template<typename int_type>
+ constexpr fraction<int_type> operator/(const int_type& lhs, const fraction<int_type>& rhs) noexcept {
+ fraction<int_type> r( lhs, (int_type)1 );
+ return r /= rhs;
+ }
+
+ /**
+ * Returns sum of two fraction.
+ *
+ * Operation may set the overflow flag in the returned instance, if occurring.
+ *
+ * @tparam int_type integral type
+ * @param lhs a fraction
+ * @param rhs a fraction
+ * @return resulting new fraction, reduced
+ */
+ template<typename int_type>
+ constexpr fraction<int_type> operator+(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ fraction<int_type> r(lhs);
+ r += rhs; // implicit reduce
+ return r;
+ }
+
+ /**
+ * Returns difference of two fraction.
+ *
+ * Operation may set the overflow flag in the returned instance, if occurring.
+ *
+ * @tparam int_type integral type
+ * @param lhs a fraction
+ * @param rhs a fraction
+ * @return resulting new fraction, reduced
+ */
+ template<typename int_type>
+ constexpr fraction<int_type> operator-(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ fraction<int_type> r(lhs);
+ r -= rhs; // implicit reduce
+ return r;
+ }
+
+ /**
+ * Returns product of two fraction.
+ *
+ * Operation may set the overflow flag in the returned instance, if occurring.
+ *
+ * @tparam int_type integral type
+ * @param lhs a fraction
+ * @param rhs a fraction
+ * @return resulting new fraction, reduced
+ */
+ template<typename int_type>
+ constexpr fraction<int_type> operator*(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ fraction<int_type> r(lhs);
+ r *= rhs; // implicit reduce
+ return r;
+ }
+
+ /**
+ * Returns division of two fraction.
+ * @tparam int_type integral type
+ * @param lhs a fraction
+ * @param rhs a fraction
+ * @return resulting new fraction, reduced
+ */
+ template<typename int_type>
+ constexpr fraction<int_type> operator/(const fraction<int_type>& lhs, const fraction<int_type>& rhs ) noexcept {
+ fraction<int_type> r(lhs);
+ r /= rhs; // implicit reduce
+ return r;
+ }
+
+ /** fraction using int64_t as integral type */
+ typedef fraction<int64_t> fraction_i64;
+
+ /**
+ * Stores the fraction_i64 value of the given string value in format `<num>/<denom>`,
+ * which may contain whitespace.
+ *
+ * It the given string value does not conform with the format
+ * or exceeds the given value range, `false` is being returned.
+ *
+ * If the given string value has been accepted, it is stored in the result reference
+ * and `true` is being returned.
+ *
+ * @param result storage for result is value is parsed successfully and within range.
+ * @param value the string value
+ * @param min_allowed the minimum allowed value
+ * @param max_allowed the maximum allowed value
+ * @return true if value has been accepted, otherwise false
+ */
+ bool to_fraction_i64(fraction_i64& result, const std::string & value, const fraction_i64& min_allowed, const fraction_i64& max_allowed) noexcept;
+
+
+ /** fraction using uint64_t as integral type */
+ typedef fraction<uint64_t> fraction_u64;
+
+ /** fractions namespace to provide fraction constants using int64_t as underlying integral integer type. */
+ namespace fractions_i64 { // Note: int64_t == intmax_t -> 10^18 or 19 digits (for intmax_t on 64bit platforms)
+ /** tera is 10^12 */
+ inline constexpr const jau::fraction_i64 tera ( 1'000'000'000'000l, 1lu );
+ /** giga is 10^9 */
+ inline constexpr const jau::fraction_i64 giga ( 1'000'000'000l, 1lu );
+ /** mega is 10^6 */
+ inline constexpr const jau::fraction_i64 mega ( 1'000'000l, 1lu );
+ /** days is 86400/1 */
+ inline constexpr const jau::fraction_i64 days ( 86'400l, 1lu );
+ /** hours is 3660/1 */
+ inline constexpr const jau::fraction_i64 hours ( 3'600l, 1lu );
+ /** kilo is 10^3 */
+ inline constexpr const jau::fraction_i64 kilo ( 1'000l, 1lu );
+ /** minutes is 60/1 */
+ inline constexpr const jau::fraction_i64 minutes ( 60l, 1lu );
+ /** seconds is 1/1 */
+ inline constexpr const jau::fraction_i64 seconds ( 1l, 1lu );
+ /** one is 10^0 or 1/1 */
+ inline constexpr const jau::fraction_i64 one ( 1l, 1lu );
+ /** zero is 0/1 */
+ inline constexpr const jau::fraction_i64 zero ( 0l, 1lu );
+ /** milli is 10^-3 */
+ inline constexpr const jau::fraction_i64 milli( 1l, 1'000lu );
+ /** micro is 10^-6 */
+ inline constexpr const jau::fraction_i64 micro( 1l, 1'000'000lu );
+ /** nano is 10^-9 */
+ inline constexpr const jau::fraction_i64 nano ( 1l, 1'000'000'000lu );
+ /** pico is 10^-12 */
+ inline constexpr const jau::fraction_i64 pico ( 1l, 1'000'000'000'000lu );
+ } // namespace fractions_i64
+
+ namespace fractions_i64_literals {
+ /** Literal for fractions_i64::tera */
+ constexpr fraction_i64 operator ""_T(unsigned long long int __T) { return (int64_t)__T * fractions_i64::tera; }
+ /** Literal for fractions_i64::giga */
+ constexpr fraction_i64 operator ""_G(unsigned long long int __G) { return (int64_t)__G * fractions_i64::giga; }
+ /** Literal for fractions_i64::mega */
+ constexpr fraction_i64 operator ""_M(unsigned long long int __M) { return (int64_t)__M * fractions_i64::mega; }
+ /** Literal for fractions_i64::kilo */
+ constexpr fraction_i64 operator ""_k(unsigned long long int __k) { return (int64_t)__k * fractions_i64::kilo; }
+ /** Literal for fractions_i64::one */
+ constexpr fraction_i64 operator ""_one(unsigned long long int __one) { return (int64_t)__one * fractions_i64::one; }
+ /** Literal for fractions_i64::milli */
+ constexpr fraction_i64 operator ""_m(unsigned long long int __m) { return (int64_t)__m * fractions_i64::milli; }
+ /** Literal for fractions_i64::micro */
+ constexpr fraction_i64 operator ""_u(unsigned long long int __u) { return (int64_t)__u * fractions_i64::micro; }
+ /** Literal for fractions_i64::nano */
+ constexpr fraction_i64 operator ""_n(unsigned long long int __n) { return (int64_t)__n * fractions_i64::nano; }
+ /** Literal for fractions_i64::pico */
+ constexpr fraction_i64 operator ""_p(unsigned long long int __p) { return (int64_t)__p * fractions_i64::pico; }
+
+ /** Literal for fractions_i64::days */
+ constexpr fraction_i64 operator ""_d(unsigned long long int __d) { return (int64_t)__d * fractions_i64::days; }
+ /** Literal for fractions_i64::hours */
+ constexpr fraction_i64 operator ""_h(unsigned long long int __h) { return (int64_t)__h * fractions_i64::hours; }
+ /** Literal for fractions_i64::minutes */
+ constexpr fraction_i64 operator ""_min(unsigned long long int __min) { return (int64_t)__min * fractions_i64::minutes; }
+ /** Literal for fractions_i64::seconds */
+ constexpr fraction_i64 operator ""_s(unsigned long long int __s) { return (int64_t)__s * fractions_i64::seconds; }
+ /** Literal for fractions_i64::milli */
+ constexpr fraction_i64 operator ""_ms(unsigned long long int __ms) { return (int64_t)__ms * fractions_i64::milli; }
+ /** Literal for fractions_i64::micro */
+ constexpr fraction_i64 operator ""_us(unsigned long long int __us) { return (int64_t)__us * fractions_i64::micro; }
+ /** Literal for fractions_i64::nano */
+ constexpr fraction_i64 operator ""_ns(unsigned long long int __ns) { return (int64_t)__ns * fractions_i64::nano; }
+ } // namespace fractions_i64_literals
+
+ /**
+ * Timespec structure using fraction_i64 for its components
+ * in analogy to `struct timespec_t`.
+ *
+ * fraction_timespec allows to cover an almost infinite range of time
+ * while maintaining high precision like `struct timespec_t`.
+ *
+ * Note: Counting nanoseconds in int64_t only lasts until `2262-04-12`,
+ * since INT64_MAX is 9'223'372'036'854'775'807 for 9'223'372'036 seconds or 292 years.
+ *
+ * If used as time-point, zero is time since Unix Epoch `00:00:00 UTC on 1970-01-01`.
+ *
+ * @see to_fraction_i64()
+ * @see getMonotonicTime()
+ */
+ struct fraction_timespec {
+ /**
+ * Seconds component, with its absolute value in range [0..inf[ or [0..inf).
+ */
+ int64_t tv_sec;
+
+ /**
+ * Positive fraction of seconds for the nanoseconds component,
+ * where its value shall be in range [0..1'000'000'000[ or [0..1'000'000'000).
+ */
+ int64_t tv_nsec;
+
+ /**
+ * Constructs a zero fraction_timespec instance
+ */
+ constexpr fraction_timespec() noexcept
+ : tv_sec(0), tv_nsec(0) { }
+
+ /**
+ * Constructs a fraction_timespec instance with given components, normalized.
+ */
+ constexpr fraction_timespec(const int64_t& s, const int64_t ns) noexcept
+ : tv_sec(s), tv_nsec(ns) { normalize(); }
+
+ /**
+ * Construct a fraction_timespec via fraction_i64 conversion.
+ *
+ * If overflow_ptr is not nullptr, true is stored if an overflow occurred, otherwise false.
+ *
+ * In case of an overflow, tv_sec and tv_nsec will also be set to INT64_MAX
+ *
+ * @param r the conversion input
+ * @param overflow_ptr optional pointer to overflow result, defaults to nullptr
+ */
+ constexpr fraction_timespec(const fraction_i64& r, bool * overflow_ptr=nullptr) noexcept
+ : tv_sec(0), tv_nsec(0)
+ {
+ bool overflow = false;
+ tv_sec = r.to_num_of(fractions_i64::seconds, &overflow);
+ if( !overflow ) {
+ const fraction_i64 ns = r - tv_sec * fractions_i64::seconds;
+ tv_nsec = ns.to_num_of(fractions_i64::nano, &overflow);
+ }
+ if( overflow ) {
+ if( nullptr != overflow_ptr ) {
+ *overflow_ptr = true;
+ }
+ tv_sec = INT64_MAX;
+ tv_nsec = INT64_MAX;
+ }
+ }
+
+ /**
+ * Returns the sum of both components.
+ *
+ * If applied to relative duration, i.e. difference of two time points,
+ * its range is good for 292 years and exceeds that of an `int64_t nanoseconds` timepoint-difference greatly.
+ *
+ * <pre>
+ * fraction_timespec t0 = getMonotonicTime();
+ * // do something
+ *
+ * // Exact duration
+ * fraction_timespec td_1 = getMonotonicTime() - t0;
+ *
+ * // or for durations <= 292 years
+ * fraction_i64 td_2 = (getMonotonicTime() - t0).to_fraction_i64();
+ * </pre>
+ * @see getMonotonicTime()
+ */
+ constexpr fraction_i64 to_fraction_i64() const noexcept {
+ return ( tv_sec * fractions_i64::seconds ) + ( tv_nsec * fractions_i64::nano );
+ }
+
+ /**
+ * Normalize tv_nsec to be in range [0..1'000'000'000[ or [0..1'000'000'000),
+ * used after an arithmetic operation.
+ *
+ * @returns reference to this instance
+ */
+ constexpr fraction_timespec& normalize() noexcept {
+ using namespace jau::int_literals;
+ const int64_t ns_per_sec = 1'000'000'000_i64;
+ if( tv_nsec < 0 ) {
+ tv_nsec += ns_per_sec;
+ tv_sec -= 1;
+ } else if( tv_nsec >= ns_per_sec ) {
+ const int64_t c = tv_nsec / ns_per_sec;
+ tv_nsec -= c * ns_per_sec;
+ tv_sec += c;
+ }
+ return *this;
+ }
+
+ /**
+ * Compound assignment (addition)
+ *
+ * @param rhs the other fraction_timespec
+ * @return reference to this instance, normalized
+ */
+ constexpr fraction_timespec& operator+=(const fraction_timespec& rhs ) noexcept {
+ tv_sec += rhs.tv_sec;
+ tv_nsec += rhs.tv_nsec;
+ return normalize();
+ }
+
+ /**
+ * Negative compound assignment (subtraction)
+ *
+ * @param rhs the other fraction_timespec
+ * @return reference to this instance, normalized
+ */
+ constexpr fraction_timespec& operator-=(const fraction_timespec& rhs ) noexcept {
+ tv_sec -= rhs.tv_sec;
+ tv_nsec -= rhs.tv_nsec;
+ return normalize();
+ }
+
+ std::string to_string() const noexcept {
+ return std::to_string(tv_sec) + "s + " + std::to_string(tv_nsec) + "ns";
+ }
+ };
+
+ inline std::string to_string(const fraction_timespec& v) noexcept { return v.to_string(); }
+
+
+ constexpr bool operator!=(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ return lhs.tv_sec != rhs.tv_sec || lhs.tv_nsec != rhs.tv_nsec;
+ }
+
+ constexpr bool operator==(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ return !( lhs != rhs );
+ }
+
+ constexpr bool operator>(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ return ( lhs.tv_sec > rhs.tv_sec ) || ( lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec > rhs.tv_nsec );
+ }
+
+ constexpr bool operator>=(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ return ( lhs.tv_sec > rhs.tv_sec ) || ( lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec >= rhs.tv_nsec );
+ }
+
+ constexpr bool operator<(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ return ( lhs.tv_sec < rhs.tv_sec ) || ( lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec < rhs.tv_nsec );
+ }
+
+ constexpr bool operator<=(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ return ( lhs.tv_sec < rhs.tv_sec ) || ( lhs.tv_sec == rhs.tv_sec && lhs.tv_nsec <= rhs.tv_nsec );
+ }
+
+ /** Return the maximum of the two given fraction_timespec */
+ constexpr const fraction_timespec& max(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ return lhs >= rhs ? lhs : rhs;
+ }
+
+ /** Return the minimum of the two given fraction_timespec */
+ constexpr const fraction_timespec& min(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ return lhs <= rhs ? lhs : rhs;
+ }
+
+ /**
+ * Returns sum of two fraction_timespec.
+ *
+ * @param lhs a fraction_timespec
+ * @param rhs a fraction_timespec
+ * @return resulting new fraction_timespec, each component reduced and both fraction_timespec::normalize() 'ed
+ */
+ constexpr fraction_timespec operator+(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ fraction_timespec r(lhs);
+ r += rhs; // implicit normalize
+ return r;
+ }
+
+ /**
+ * Returns difference of two fraction_timespec.
+ *
+ * See fraction_timespec::to_fraction_i64().
+ *
+ * @param lhs a fraction_timespec
+ * @param rhs a fraction_timespec
+ * @return resulting new fraction_timespec, each component reduced and both fraction_timespec::normalize() 'ed
+ */
+ constexpr fraction_timespec operator-(const fraction_timespec& lhs, const fraction_timespec& rhs ) noexcept {
+ fraction_timespec r(lhs);
+ r -= rhs; // implicit normalize
+ return r;
+ }
+
+ namespace fraction_tv {
+
+ /** jau::fraction_timespec zero is { 0, 0 } */
+ inline constexpr const jau::fraction_timespec zero(0, 0);
+
+ } // namespace fraction_tv
+
+ /** SC atomic integral scalar jau::fraction_i64. Memory-Model (MM) guaranteed sequential consistency (SC) between acquire (read) and release (write). Requires libatomic with libstdc++10. */
+ typedef ordered_atomic<jau::fraction_i64, std::memory_order_seq_cst> sc_atomic_fraction_i64;
+
+ /** Relaxed non-SC atomic integral scalar jau::fraction_i64. Memory-Model (MM) only guarantees the atomic value, _no_ sequential consistency (SC) between acquire (read) and release (write). Requires libatomic with libstdc++10. */
+ typedef ordered_atomic<jau::fraction_i64, std::memory_order_relaxed> relaxed_atomic_fraction_i64;
+
+ /** SC atomic integral scalar jau::fraction_u64. Memory-Model (MM) guaranteed sequential consistency (SC) between acquire (read) and release (write). Requires libatomic with libstdc++10. */
+ typedef ordered_atomic<jau::fraction_u64, std::memory_order_seq_cst> sc_atomic_fraction_u64;
+
+ /** Relaxed non-SC atomic integral scalar jau::fraction_u64. Memory-Model (MM) only guarantees the atomic value, _no_ sequential consistency (SC) between acquire (read) and release (write). Requires libatomic with libstdc++10. */
+ typedef ordered_atomic<jau::fraction_u64, std::memory_order_relaxed> relaxed_atomic_fraction_u64;
+
+} // namespace jau
+
+namespace std {
+
+ inline std::ostream& operator<<(std::ostream& os, const jau::fraction_timespec& v) noexcept {
+ os << v.to_string();
+ return os;
+ }
+
+ template<typename int_type>
+ inline std::ostream& operator<<(std::ostream& os, const jau::fraction<int_type>& v) noexcept {
+ os << v.to_string();
+ return os;
+ }
+} // namespace std
+
+#endif /* JAU_FRACTION_TYPE_HPP_ */
diff --git a/include/jau/int_math.hpp b/include/jau/int_math.hpp
index a6d5abe..5259569 100644
--- a/include/jau/int_math.hpp
+++ b/include/jau/int_math.hpp
@@ -231,6 +231,96 @@ namespace jau {
}
/**
+ * Returns the greatest common divisor (GCD) of the two given integer values following Euclid's algorithm from Euclid's Elements ~300 BC,
+ * using the absolute positive value of given integers.
+ *
+ * Returns zero if a and b is zero.
+ *
+ * Note implementation uses modulo operator `(a/b)*b + a%b = a`,
+ * i.e. remainder of the integer division - hence implementation uses abs(a)%abs(b) to avoid negative numbers.
+ *
+ * Implementation is similar to std::gcd(), however, it uses a fixed common type T
+ * and a while loop instead of recursion.
+ *
+ * @tparam T integral type
+ * @tparam
+ * @param a integral value a
+ * @param b integral value b
+ * @return zero if a and b are zero, otherwise the greatest common divisor (GCD) of a and b,
+ */
+ template <typename T,
+ std::enable_if_t< std::is_integral_v<T> &&
+ !std::is_unsigned_v<T>, bool> = true>
+ constexpr T gcd(T a, T b) noexcept
+ {
+ T a_ = abs(a);
+ T b_ = abs(b);
+ while( b_ != 0 ) {
+ const T t = b_;
+ b_ = a_ % b_;
+ a_ = t;
+ }
+ return a_;
+ }
+
+ /**
+ * Returns the greatest common divisor (GCD) of the two given positive integer values following Euclid's algorithm from Euclid's Elements ~300 BC.
+ *
+ * Returns zero if a and b is zero.
+ *
+ * Since both operands are of type unsigned, no negative numbers can be produced by the modulo operator.
+ *
+ * Implementation is similar to std::gcd(), however, it uses a fixed common type T
+ * and a while loop instead of recursion.
+ *
+ * @tparam T integral type
+ * @tparam
+ * @param a positive integral value a
+ * @param b positive integral value b
+ * @return zero if a and b are zero, otherwise the greatest common divisor (GCD) of a and b,
+ */
+ template <typename T,
+ std::enable_if_t< std::is_integral_v<T> &&
+ std::is_unsigned_v<T>, bool> = true>
+ constexpr T gcd(T a, T b) noexcept
+ {
+ while( b != 0 ) {
+ const T t = b;
+ b = a % b;
+ a = t;
+ }
+ return a;
+ }
+
+ /**
+ * Integer overflow aware calculation of least common multiple (LCM) following Euclid's algorithm from Euclid's Elements ~300 BC.
+ * @tparam T integral type
+ * @tparam
+ * @param result storage for lcm result: zero if a and b are zero, otherwise lcm of a and b
+ * @param a integral value a
+ * @param b integral value b
+ * @return true if overflow, otherwise false for success
+ */
+ template <typename T,
+ std::enable_if_t< std::is_integral_v<T>, bool> = true>
+ constexpr bool lcm_overflow(const T a, const T b, T& result) noexcept
+ {
+ const T _gcd = gcd<T>( a, b );
+ if( 0 < _gcd ) {
+ T r;
+ if( mul_overflow(a, b, r) ) {
+ return true;
+ } else {
+ result = r / _gcd;
+ return false;
+ }
+ } else {
+ result = 0;
+ return false;
+ }
+ }
+
+ /**
* Returns the number of decimal digits of the given integral value number using std::log10<T>().<br>
* If sign_is_digit == true (default), treats a potential negative sign as a digit.
* <pre>
diff --git a/src/basic_types.cpp b/src/basic_types.cpp
index e761bbc..6e14c61 100644
--- a/src/basic_types.cpp
+++ b/src/basic_types.cpp
@@ -277,3 +277,76 @@ std::string jau::to_string(const endian& v) noexcept {
return "unlisted";
}
+static bool to_integer(long long & result, const char * str, size_t str_len, const char limiter, const char *limiter_pos) {
+ static constexpr const bool _debug = false;
+ char *endptr = NULL;
+ if( nullptr == limiter_pos ) {
+ limiter_pos = str + str_len;
+ }
+ const long long num = std::strtoll(str, &endptr, 10);
+ if( 0 != errno ) {
+ // value under- or overflow occured
+ if constexpr ( _debug ) {
+ INFO_PRINT("Value under- or overflow occurred, value %lld in: '%s', errno %d %s", num, str, errno, strerror(errno));
+ }
+ return false;
+ }
+ if( nullptr == endptr || endptr == str ) {
+ // no digits consumed
+ if constexpr ( _debug ) {
+ INFO_PRINT("Value no digits consumed @ idx %d, %p == start, in: '%s'", endptr-str, endptr, str);
+ }
+ return false;
+ }
+ if( endptr < limiter_pos ) {
+ while( endptr < limiter_pos && ::isspace(*endptr) ) { // only accept whitespace
+ ++endptr;
+ }
+ }
+ if( *endptr != limiter || endptr != limiter_pos ) {
+ // numerator value not completely valid
+ if constexpr ( _debug ) {
+ INFO_PRINT("Value end not '%c' @ idx %d, %p != %p, in: %p '%s' len %zd", limiter, endptr-str, endptr, limiter_pos, str, str, str_len);
+ }
+ return false;
+ }
+ result = num;
+ return true;
+}
+
+bool jau::to_fraction_i64(fraction_i64& result, const std::string & value, const fraction_i64& min_allowed, const fraction_i64& max_allowed) noexcept {
+ static constexpr const bool _debug = false;
+ const char * str = const_cast<const char*>(value.c_str());
+ const size_t str_len = value.length();
+ const char *divptr = NULL;
+
+ divptr = std::strstr(str, "/");
+ if( nullptr == divptr ) {
+ if constexpr ( _debug ) {
+ INFO_PRINT("Missing '/' in: '%s'", str);
+ }
+ return false;
+ }
+
+ long long num;
+ if( !to_integer(num, str, str_len, '/', divptr) ) {
+ return false;
+ }
+
+ long long denom; // 0x7ffc7090d904 != 0x7ffc7090d907 " 10 / 1000000 "
+ if( !to_integer(denom, divptr+1, str_len-(divptr-str)-1, '\0', str + str_len) ) {
+ return false;
+ }
+
+ fraction_i64 temp((int64_t)num, (uint64_t)denom);
+ if( ! ( min_allowed <= temp && temp <= max_allowed ) ) {
+ // invalid user value range
+ if constexpr ( _debug ) {
+ INFO_PRINT("Numerator out of range, not %s <= %s <= %s, in: '%s'", min_allowed.to_string().c_str(), temp.to_string().c_str(), max_allowed.to_string().c_str(), str);
+ }
+ return false;
+ }
+ result = std::move(temp);
+ return true;
+}
+
diff --git a/src/environment.cpp b/src/environment.cpp
index 84e4b1d..3e3699b 100644
--- a/src/environment.cpp
+++ b/src/environment.cpp
@@ -37,6 +37,7 @@
using namespace jau;
const uint64_t environment::startupTimeMilliseconds = jau::getCurrentMilliseconds();
+const fraction_timespec environment::startupTimeMonotonic = jau::getMonotonicTime();
bool environment::local_debug = false;
@@ -171,6 +172,22 @@ uint32_t environment::getUint32Property(const std::string & name, const uint32_t
}
}
+fraction_i64 environment::getFractionProperty(const std::string & name, const fraction_i64& default_value,
+ const fraction_i64& min_allowed, const fraction_i64& max_allowed) noexcept {
+ const std::string value = getProperty(name);
+ if( 0 == value.length() ) {
+ COND_PRINT(local_debug, "env::getFractionProperty %s: null -> %s (default)", name.c_str(), default_value.to_string().c_str());
+ return default_value;
+ } else {
+ fraction_i64 result = default_value;
+ if( !to_fraction_i64(result, value, min_allowed, max_allowed) ) {
+ ERR_PRINT("env::getFractionProperty %s: value %s not valid or in range[%s .. %s] -> %s (default)",
+ name.c_str(), value.c_str(), min_allowed.to_string().c_str(), max_allowed.to_string().c_str(), default_value.to_string().c_str());
+ }
+ return result;
+ }
+}
+
void environment::envSet(std::string prefix_domain, std::string basepair) noexcept {
trimInPlace(basepair);
if( basepair.length() > 0 ) {
diff --git a/test/test_fractions_01.cpp b/test/test_fractions_01.cpp
new file mode 100644
index 0000000..b297f8f
--- /dev/null
+++ b/test/test_fractions_01.cpp
@@ -0,0 +1,734 @@
+/*
+ * Author: Sven Gothel <[email protected]>
+ * Copyright (c) 2022 Gothel Software e.K.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining
+ * a copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sublicense, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be
+ * included in all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+ * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+ * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+#include <iostream>
+#include <thread>
+#include <cassert>
+#include <cinttypes>
+#include <cstring>
+
+#define CATCH_CONFIG_RUNNER
+// #define CATCH_CONFIG_MAIN
+#include <catch2/catch_amalgamated.hpp>
+#include <jau/test/catch2_ext.hpp>
+
+#include <jau/basic_types.hpp>
+
+using namespace jau;
+using namespace jau::fractions_i64_literals;
+using namespace jau::int_literals;
+
+#if 0
+static fraction<int64_t> ratio_multiply(const fraction<int64_t>& r1, const fraction<int64_t>& r2) {
+ const uint64_t gcd1 = gcd<uint64_t>((uint64_t)abs(r1.num), r2.denom);
+ const uint64_t gcd2 = gcd<uint64_t>((uint64_t)abs(r2.num), r1.denom);
+
+ return fraction<int64_t>( ( r1.num / gcd1 ) * ( r2.num / gcd2),
+ ( r1.denom / gcd2 ) * ( r2.denom / gcd1) );
+}
+
+static fraction<int64_t> ratio_divide(const fraction<int64_t>& r1, const fraction<int64_t>& r2) {
+ return ratio_multiply(r1, fraction<int64_t>(r2.denom, r2.num));
+}
+
+static fraction<int64_t> duration_common_type(const fraction<int64_t>& p1, const fraction<int64_t>& p2) {
+ const uint64_t gcd_num = gcd<uint64_t>((uint64_t)abs(p1.num), (uint64_t)abs(p2.num));
+ const uint64_t gcd_den = gcd<uint64_t>(p1.denom, p2.denom);
+ return fraction<int64_t>( gcd_num * jau::sign(p1.num) * jau::sign(p2.num), ( p1.denom / gcd_den ) * p2.denom );
+}
+#endif
+
+
+template<typename int_type>
+static void test_gcd_fract(const int_type n, const std::make_unsigned_t<int_type> d,
+ const int_type exp_gcd, const int_type exp_num, const std::make_unsigned_t<int_type> exp_denom) {
+ {
+ const int_type n1 = sign(n) * abs(n);
+ const std::make_unsigned_t<int_type> d1 = sign(d) * abs(d);
+ REQUIRE( n == n1 );
+ REQUIRE( d == d1 );
+ }
+
+ const int_type _gcd = gcd( n, static_cast<int_type>(d) );
+ REQUIRE( exp_gcd == _gcd );
+
+ fraction<int_type> f1(n, d);
+ REQUIRE( exp_num == f1.num );
+ REQUIRE( exp_denom == f1.denom);
+}
+
+template<typename int_type>
+static void test_gcd_fract_pm(const int_type n, const std::make_unsigned_t<int_type> d,
+ const int_type exp_gcd, const int_type exp_num, const std::make_unsigned_t<int_type> exp_denom) {
+ test_gcd_fract( n, d, exp_gcd, exp_num, exp_denom);
+ test_gcd_fract(-n, d, exp_gcd, -exp_num, exp_denom);
+}
+
+template<typename int_type>
+static void test_comp_fract(const fraction<int_type>& a, const fraction<int_type>& b,
+ const fraction<int_type>& exp_max, const fraction<int_type>& exp_min,
+ const fraction<int_type>& exp_sum, const fraction<int_type>& exp_diff,
+ const fraction<int_type>& exp_mul, const fraction<int_type>& exp_div)
+{
+ const bool show_double = true;
+ INFO_STR( "max(a "+a.to_string(show_double)+", b "+b.to_string(show_double)+") = "+max(a,b).to_string(show_double));
+ INFO_STR( "min(a "+a.to_string(show_double)+", b "+b.to_string(show_double)+") = "+min(a,b).to_string(show_double));
+ INFO_STR( "a "+a.to_string(show_double)+" + b "+b.to_string(show_double)+" = "+(a+b).to_string(show_double));
+ INFO_STR( "a "+a.to_string(show_double)+" - b "+b.to_string(show_double)+" = "+(a-b).to_string(show_double));
+ INFO_STR( "a "+a.to_string(show_double)+" * b "+b.to_string(show_double)+" = "+(a*b).to_string(show_double));
+ INFO_STR( "a "+a.to_string(show_double)+" / b "+b.to_string(show_double)+" = "+(a/b).to_string(show_double));
+ {
+ const double epsilon = std::numeric_limits<double>::epsilon();
+ const double ad = a.to_double();
+ const double bd = b.to_double();
+ if( std::abs( ad - bd ) <= epsilon ) {
+ REQUIRE( a == b );
+ REQUIRE( !(a != b) );
+ REQUIRE( a <= b );
+ REQUIRE( a >= b );
+ } else if( std::abs( ad - bd ) > epsilon ) {
+ REQUIRE( a != b );
+ REQUIRE( !(a == b) );
+ if( ad - bd < -epsilon ) {
+ REQUIRE( a < b );
+ REQUIRE( a <= b );
+ REQUIRE( b > a );
+ REQUIRE( b >= a );
+ } else {
+ REQUIRE( a > b );
+ REQUIRE( a >= b );
+ REQUIRE( b < a );
+ REQUIRE( b <= a );
+ }
+ }
+ }
+ {
+ fraction<int_type> has_max = max(a, b);
+ fraction<int_type> has_min = min(a, b);
+ REQUIRE_MSG( "exp "+to_string(exp_max)+" == has "+to_string(has_max), exp_max == has_max );
+ REQUIRE_MSG( "exp "+to_string(exp_min)+" == has "+to_string(has_min), exp_min == has_min );
+ REQUIRE( has_max >= has_min );
+ REQUIRE( has_min <= has_max );
+ }
+ {
+ const fraction<int_type> has_sum = a + b;
+ const double exp_double = a.to_double() + b.to_double();
+ const double has_double = has_sum.to_double();
+ REQUIRE_MSG( "exp "+std::to_string(exp_double)+" == has "+std::to_string(has_double), abs( exp_double - has_double ) <= std::numeric_limits<double>::epsilon() );
+ REQUIRE_MSG( "exp "+to_string(exp_sum)+" == has "+to_string(has_sum), exp_sum == has_sum );
+ }
+ {
+ fraction<int_type> has_diff = a - b;
+ const double exp_double = a.to_double() - b.to_double();
+ const double has_double = has_diff.to_double();
+ REQUIRE_MSG( "exp "+std::to_string(exp_double)+" == has "+std::to_string(has_double), abs( exp_double - has_double ) <= std::numeric_limits<double>::epsilon() );
+ REQUIRE_MSG( "exp "+to_string(exp_diff)+" == has "+to_string(has_diff), exp_diff == has_diff );
+ }
+ {
+ fraction<int_type> has_mul = a * b;
+ const double exp_double = a.to_double() * b.to_double();
+ const double has_double = has_mul.to_double();
+ REQUIRE_MSG( "exp "+std::to_string(exp_double)+" == has "+std::to_string(has_double), abs( exp_double - has_double ) <= std::numeric_limits<double>::epsilon() );
+ REQUIRE_MSG( "exp "+to_string(exp_mul)+" == has "+to_string(has_mul), exp_mul == has_mul);
+ }
+ {
+ fraction<int_type> has_div = a / b;
+ const double exp_double = a.to_double() / b.to_double();
+ const double has_double = has_div.to_double();
+ REQUIRE_MSG( "exp "+std::to_string(exp_double)+" == has "+std::to_string(has_double), abs( exp_double - has_double ) <= std::numeric_limits<double>::epsilon() );
+ REQUIRE_MSG( "exp "+to_string(exp_div)+" == has "+to_string(has_div), exp_div == has_div);
+ }
+ {
+ const fraction<int_type> step(1, a.denom);
+ const fraction<int_type> lim(a + ( step * (int_type)10 ));
+ fraction<int_type> i(a);
+ int count;
+ for(count = 0; i < lim; i+=step, ++count) { }
+ REQUIRE( i == lim );
+ REQUIRE( i > a );
+ REQUIRE( 10 == count );
+
+ i+=step;
+ REQUIRE( i > lim );
+ REQUIRE( i == lim + step );
+ }
+ if( !std::is_unsigned_v<int_type> ) {
+ const fraction<int_type> step(1, a.denom);
+ const fraction<int_type> lim(a - ( step * (int_type)10 ));
+ fraction<int_type> i(a);
+ int count;
+ for(count = 0; i > lim; i-=step, ++count) { }
+ REQUIRE( i == lim );
+ REQUIRE( i < a );
+ REQUIRE( 10 == count );
+
+ i-=step;
+ REQUIRE( i < lim );
+ REQUIRE( i == lim - step );
+ }
+}
+
+template<typename Rep, typename Period, typename int_type>
+static void test_duration(const fraction<int_type>& a, const std::chrono::duration<Rep, Period>& dur_ref,
+ const Rep exp_count)
+{
+ INFO_STR( " given duration: ( " + std::to_string(dur_ref.count()) + " * " + std::to_string(Period::num) + " = " + std::to_string(dur_ref.count() * Period::num) + " ) / " + std::to_string(Period::den) );
+ {
+ const int64_t d_num = a.to_num_of( fraction_i64(Period::num, Period::den) );
+ std::chrono::duration<Rep, Period> d = a.to_duration( dur_ref );
+ INFO_STR( " fraction-1 " + a.to_string(true) + " -> duration_count " + std::to_string( d_num ) + ", duration " + std::to_string( d.count() ) );
+ INFO_STR( " resulting duration-1: ( " + std::to_string(d.count()) + " * " + std::to_string(Period::num) + " = " + std::to_string(d.count() * Period::num) + " ) / " + std::to_string(Period::den) );
+
+ // fully functional conversion check
+ fraction<int_type> b(d);
+ INFO_STR( " reconverted fraction-2 " + b.to_string(true));
+ REQUIRE( exp_count == d_num );
+ REQUIRE( exp_count == d.count() );
+ REQUIRE( a == b );
+ }
+#if 0
+ {
+ typename std::chrono::duration<Rep, Period>::rep d_count = a.to_duration_count( dur_ref );
+ auto d = a.to_auto_duration();
+ INFO_STR( " fraction-2 " + a.to_string(true) + " -> duration_count " + std::to_string( d_count ) + ", duration " + std::to_string( d.count() ) );
+ INFO_STR( " resulting duration-2: ( " + std::to_string(d.count()) + " * " + std::to_string(Period::num) + " = " + std::to_string(d.count() * Period::num) + " ) / " + std::to_string(Period::den) );
+ INFO_STR( " resulting duration-2: ( " + std::to_string(d.count()) + " * " + std::to_string(decltype(d)::num) + " = " + std::to_string(d.count() * decltype(d)::num) + " ) / " + std::to_string(decltype(d)::den) );
+
+ // fully functional conversion check
+ fraction<int_type> b(d);
+ INFO_STR( " reconverted fraction-2 " + b.to_string(true));
+ REQUIRE( exp_count == d_count );
+ REQUIRE( exp_count == d.count() );
+ REQUIRE( a == b );
+ }
+#endif
+}
+
+
+TEST_CASE( "Fraction Types Test 00", "[fraction][type]" ) {
+ {
+ INFO_STR(" is_trivially_copyable_v<fraction_i64>: " + std::to_string(std::is_trivially_copyable_v<fraction_i64>));
+ INFO_STR(" is_trivially_copyable<fraction_timespec>: " + std::to_string(std::is_trivially_copyable_v<fraction_timespec>));
+ REQUIRE( true == std::is_trivially_copyable_v<fraction_i64> );
+ REQUIRE( true == std::is_trivially_copyable_v<fraction_timespec> );
+ sc_atomic_fraction_i64 check_type_01 = fractions_i64::seconds;
+ (void)check_type_01;
+ }
+ {
+ INFO_STR(" is_trivially_copyable_v<fraction_i64>: " + std::to_string(std::is_trivially_copyable_v<fraction_i64>));
+ REQUIRE( true == std::is_trivially_copyable_v<fraction_i64> );
+ sc_atomic_fraction_i64 check_type_01 = fractions_i64::seconds;
+ (void)check_type_01;
+ }
+ {
+ REQUIRE( INT64_MAX == std::numeric_limits<int64_t>::max() );
+ REQUIRE( INT64_MIN == std::numeric_limits<int64_t>::min() );
+
+ REQUIRE( UINT64_MAX == std::numeric_limits<uint64_t>::max() );
+ }
+ {
+ // copy-ctor
+ fraction<int> a(1, 6);
+ fraction<int> b(a);
+ REQUIRE( a == b );
+ }
+ {
+ // move-ctor
+ fraction<int> a(1, 6);
+ fraction<int> b( std::move(a) );
+ REQUIRE( a == b );
+ }
+ {
+ // assignment
+ fraction<int> a(1, 6);
+ fraction<int> b(6, 1);
+ b = a;
+ REQUIRE( a == b );
+ }
+ {
+ // move-assignment
+ fraction<int> a(1, 6);
+ fraction<int> a2(a);
+ fraction<int> b(6, 1);
+ b = std::move(a2);
+ REQUIRE( a == b );
+ }
+ {
+ REQUIRE( fractions_i64::zero == 0_s );
+ REQUIRE( fractions_i64::zero == 0_one );
+
+ REQUIRE( fractions_i64::one == 1_one );
+ REQUIRE( fractions_i64::one == 1_s );
+
+ REQUIRE( 3_i64*fractions_i64::tera == 3_T );
+ REQUIRE( 3_i64*fractions_i64::giga == 3_G );
+ REQUIRE( 3_i64*fractions_i64::mega == 3_M );
+ REQUIRE( 3_i64*fractions_i64::kilo == 3_k );
+ REQUIRE( 3_i64*fractions_i64::one == 3_one );
+ REQUIRE( 3_i64*fractions_i64::milli == 3_m );
+ REQUIRE( 3_i64*fractions_i64::micro == 3_u );
+ REQUIRE( 3_i64*fractions_i64::nano == 3_n );
+ REQUIRE( 3_i64*fractions_i64::pico == 3_p );
+
+ REQUIRE( 3_i64*fractions_i64::days == 3_d );
+ REQUIRE( 3_i64*fractions_i64::hours == 3_h );
+ REQUIRE( 3_i64*fractions_i64::minutes == 3_min );
+ REQUIRE( 3_i64*fractions_i64::seconds == 3_s );
+ REQUIRE( 3_i64*fractions_i64::milli == 3_ms );
+ REQUIRE( 3_i64*fractions_i64::micro == 3_us );
+ REQUIRE( 3_i64*fractions_i64::nano == 3_ns );
+ }
+}
+
+TEST_CASE( "Fraction GCD and Modulo Test 00", "[integer][fraction][gcd]" ) {
+ test_gcd_fract<int>(0, 0, 0, 0, 1);
+ test_gcd_fract<uint32_t>(0, 0, 0, 0, 1);
+
+ test_gcd_fract_pm<int>(15, 5, 5, 3, 1);
+ test_gcd_fract_pm<int>(17, 5, 1, 17, 5);
+
+ test_gcd_fract<uint32_t>(15, 5, 5, 3, 1);
+ test_gcd_fract<uint32_t>(17, 5, 1, 17, 5);
+}
+
+static void test_to_num_of(const int64_t exp, const fraction<int64_t>& v, const fraction<int64_t>& new_base, bool exp_overflow=false) noexcept {
+ bool overflow = false;
+ int64_t rr = v.to_num_of(new_base, &overflow);
+ INFO_STR(" value " + v.to_string() );
+ INFO_STR(" new_base " + new_base.to_string() );
+ std::string rr_s = exp == rr ? " - OK " : " - ********* ERROR ";
+ INFO_STR(" rr " + std::to_string(rr) + " =?= " + std::to_string(exp) + rr_s + ", overflow[exp " + std::to_string(exp_overflow) + ", has " + std::to_string(overflow) + "]");
+ if constexpr ( false ) {
+ // leaving elaboration code in .. for future testing overflows
+ const uint64_t _gcd = gcd<uint64_t>( v.denom, new_base.denom );
+ const uint64_t _lcm = ( v.denom * new_base.denom ) / _gcd;
+ int64_t r0 = ( v.num * (int64_t)( _lcm / v.denom ) ) / new_base.num;
+ int64_t r2 = ( v.num * (int64_t)new_base.denom ) / ( (int64_t) v.denom ) / new_base.num;
+ INFO_STR(" gcd " + std::to_string(_gcd) );
+ INFO_STR(" lcm " + std::to_string(_lcm) );
+ INFO_STR(" lcm / v_denom " + std::to_string( _lcm / v.denom) );
+ INFO_STR(" v_num * nb_denum " + std::to_string( v.num * (int64_t)new_base.denom ) );
+ std::string r0_s = exp == r0 ? " - OK " : " - ********* ERROR ";
+ INFO_STR(" r0 " + std::to_string(r0) + " =?= " + std::to_string(exp) + r0_s);
+ std::string r2_s = exp == r2 ? " - OK " : " - ********* ERROR ";
+ INFO_STR(" r2 " + std::to_string(r2) + " =?= " + std::to_string(exp) + r2_s);
+ }
+ REQUIRE( exp_overflow == overflow );
+ if( !exp_overflow ) {
+ REQUIRE( exp == rr );
+ }
+}
+
+TEST_CASE( "Fraction Cast Test 01.1", "[integer][fraction][type][to_num_of]" ) {
+ {
+ test_to_num_of( 2_i64, fractions_i64::one, fraction_i64(1_i64, 2_u64) ); // one -> halves
+ test_to_num_of( 1000_i64, fractions_i64::milli, fractions_i64::micro );
+ test_to_num_of( 60_i64, fractions_i64::minutes, fractions_i64::seconds );
+ test_to_num_of( 120_i64, fractions_i64::hours * 2_i64, fractions_i64::minutes );
+ test_to_num_of( 48_i64, 2_i64 * fractions_i64::days, fractions_i64::hours );
+ }
+ {
+ fraction_i64 a ( 10_s + 400_ms );
+ fraction_i64 b ( 0_s + 400_ms );
+ fraction_i64 exp_sum ( 10_s + 800_ms );
+
+ test_to_num_of( 10_i64, a, fractions_i64::seconds );
+ test_to_num_of( 10'400'000'000_i64, a, fractions_i64::nano );
+ test_to_num_of( 0_i64, b, fractions_i64::seconds );
+ test_to_num_of( 400'000'000_i64, b, fractions_i64::nano );
+ test_to_num_of( 10_i64, exp_sum, fractions_i64::seconds );
+ test_to_num_of( 10'800'000'000_i64, exp_sum, fractions_i64::nano );
+ }
+ {
+ fraction_i64 n1 ( 999'999'999_ns );
+ fraction_i64 n2 ( 999'999'999_ns );
+ fraction_i64 exp_nsum ( 1'999'999'998_ns );
+ REQUIRE( exp_nsum == n1 + n2 );
+ test_to_num_of( 999'999'999_i64, n1, fractions_i64::nano );
+ test_to_num_of( 1'999'999'998_i64, exp_nsum, fractions_i64::nano );
+ test_to_num_of( 999'999_i64, n1, fractions_i64::micro );
+ test_to_num_of( 1'999'999_i64, exp_nsum, fractions_i64::micro );
+ // OVERFLOW
+ test_to_num_of( 999'999'999'000_i64, n1, fractions_i64::pico, true /* overflow */ );
+ test_to_num_of( 1'999'999'998'000_i64, exp_nsum, fractions_i64::pico, true /* overflow */ );
+ }
+ { // OVERFLOW
+ // 1'000'000'000'000
+ // 999'999'999'999
+ // 1'999'999'999'998
+ fraction_i64 p1 ( 999'999'999'999_p );
+ // fraction_i64 p2 ( 999'999'999'999_p );
+ fraction_i64 exp_sum ( 1'999'999'999'998_p );
+ test_to_num_of( 999'999'999_i64, p1, fractions_i64::pico, true /* overflow */ );
+ test_to_num_of( 1'999'999'999'998_i64, exp_sum, fractions_i64::pico, true /* overflow */);
+ // REQUIRE( exp_sum == p1 + p2 );
+ }
+}
+
+TEST_CASE( "Fraction String Test 01.2", "[integer][fraction][type][string]" ) {
+ {
+ fraction_i64 a;
+ fraction_i64 exp = 10_s;
+
+ REQUIRE( true == to_fraction_i64(a, "10/1", 0_s, 365_d) );
+ REQUIRE( exp == a );
+ {
+ fraction_i64 b;
+ REQUIRE( true == to_fraction_i64(b, a.to_string(), a, a) );
+ REQUIRE( exp == b );
+ }
+
+ REQUIRE( true == to_fraction_i64(a, "10/1", 10_s, 10_s) );
+ REQUIRE( exp == a );
+ {
+ fraction_i64 b;
+ REQUIRE( true == to_fraction_i64(b, a.to_string(), a, a) );
+ REQUIRE( exp == b );
+ }
+
+ REQUIRE( false == to_fraction_i64(a, "10/1", 100_ns, 9_s) );
+ REQUIRE( false == to_fraction_i64(a, "10/1", 11_s, 365_d) );
+ }
+ {
+ fraction_i64 a;
+ REQUIRE( true == to_fraction_i64(a, " 10 / 1000000 ", 0_s, 365_d) );
+ REQUIRE( 10_us == a );
+ {
+ fraction_i64 b;
+ REQUIRE( true == to_fraction_i64(b, a.to_string(), a, a) );
+ REQUIRE( 10_us == b );
+ }
+
+ REQUIRE( false == to_fraction_i64(a, " 10x / 1000000 ", 0_s, 365_d) );
+ REQUIRE( false == to_fraction_i64(a, " 10 / 1000000x ", 0_s, 365_d) );
+ REQUIRE( false == to_fraction_i64(a, " 10 % 1000000x ", 0_s, 365_d) );
+ REQUIRE( false == to_fraction_i64(a, " 10 ", 0_s, 365_d) );
+ }
+}
+
+TEST_CASE( "Fraction Arithmetic Test 02", "[integer][fraction]" ) {
+ {
+ fraction<int> b(1, 6);
+ REQUIRE( b == fraction<int>(2, 12U));
+ }
+ {
+ fraction<int> b(6, 1);
+ REQUIRE( b == fraction<int>(12, 2U));
+ }
+ {
+ fraction<int> a(1, 4), b(1, 6);
+ fraction<int> exp_sum(5, 12);
+ fraction<int> exp_diff(1, 12);
+ fraction<int> exp_mul(1, 24);
+ fraction<int> exp_div(3, 2);
+ test_comp_fract(a, b, a, b, exp_sum, exp_diff, exp_mul, exp_div);
+ }
+ {
+ fraction<int> a(1, 4), b(6, 1);
+ fraction<int> exp_sum(25, 4);
+ fraction<int> exp_diff(-23, 4);
+ fraction<int> exp_mul(3, 2);
+ fraction<int> exp_div(1, 24);
+ test_comp_fract(a, b, b, a, exp_sum, exp_diff, exp_mul, exp_div);
+ }
+ {
+ fraction<int64_t> a(-1, 4), b(-1, 6);
+ fraction<int64_t> exp_sum(-5, 12);
+ fraction<int64_t> exp_diff(-1, 12);
+ fraction<int64_t> exp_mul(1, 24);
+ fraction<int64_t> exp_div(3, 2);
+ test_comp_fract(a, b, b, a, exp_sum, exp_diff, exp_mul, exp_div);
+ }
+ {
+ fraction<int> a(-1, 4), b(-1, 6);
+ fraction<int> exp_sum(-5, 12);
+ fraction<int> exp_diff(-1, 12);
+ fraction<int> exp_mul(1, 24);
+ fraction<int> exp_div(3, 2);
+ test_comp_fract(a, b, b, a, exp_sum, exp_diff, exp_mul, exp_div);
+ }
+ {
+ fraction<int> a(-1, 4), b( 1, 6);
+ fraction<int> exp_sum(-1, 12);
+ fraction<int> exp_diff(-5, 12);
+ fraction<int> exp_mul(-1, 24);
+ fraction<int> exp_div(-3, 2);
+ test_comp_fract(a, b, b, a, exp_sum, exp_diff, exp_mul, exp_div);
+ }
+ {
+ fraction<int> a(1, 4), b(-1, 6);
+ fraction<int> exp_sum(1, 12);
+ fraction<int> exp_diff(5, 12);
+ fraction<int> exp_mul(-1, 24);
+ fraction<int> exp_div(-3, 2);
+ test_comp_fract(a, b, a, b, exp_sum, exp_diff, exp_mul, exp_div);
+ }
+ {
+ // unsigned: micro + nano
+ fraction_u64 a(1, 1'000_u64), b(1, 1'000'000'000_u64);
+ fraction_u64 exp_sum( 1000001_u64, 100'0000'000_u64);
+ fraction_u64 exp_diff( 999999_u64, 1'000'000'000_u64);
+ fraction_u64 exp_mul( 1_u64, 1'000'000'000'000_u64);
+ fraction_u64 exp_div( 1000000_u64, 1_u64);
+ test_comp_fract(a, b, a, b, exp_sum, exp_diff, exp_mul, exp_div);
+ }
+ {
+ // signed: micro + nano
+ fraction_i64 a(1_m), b(1_n);
+ fraction_i64 exp_sum( 1000001_n);
+ fraction_i64 exp_diff( 999999_n);
+ fraction_i64 exp_mul( 1_p);
+ fraction_i64 exp_div( 1000000_one);
+ test_comp_fract(a, b, a, b, exp_sum, exp_diff, exp_mul, exp_div);
+ }
+ {
+ // signed: 100 micro + 1'000'000 nano
+ fraction_i64 a(100_i64*fractions_i64::milli), b(1'000'000_i64*fractions_i64::nano);
+ fraction_i64 exp_sum( 101_i64, 1'000_u64);
+ fraction_i64 exp_diff( 99_i64, 1'000_u64);
+ fraction_i64 exp_mul( 1_i64, 10'000_u64);
+ fraction_i64 exp_div( 100_i64, 1_u64);
+ test_comp_fract(a, b, a, b, exp_sum, exp_diff, exp_mul, exp_div);
+ }
+ {
+ const std::chrono::milliseconds::rep exp_count = 100;
+ const fraction<int64_t> a(exp_count * fractions_i64::milli);
+ test_duration(a, std::chrono::milliseconds::zero(), exp_count);
+ }
+ {
+ const std::chrono::nanoseconds::rep exp_count = -50;
+ const fraction_i64 a(exp_count * fractions_i64::nano);
+ test_duration(a, std::chrono::nanoseconds::zero(), exp_count);
+ }
+ {
+ const std::chrono::hours::rep exp_count = 24;
+ const fraction_i64 a(exp_count * fractions_i64::hours);
+ test_duration(a, std::chrono::hours::zero(), exp_count);
+ }
+ {
+ const fraction_i64 refresh_rate = 60_i64/1_s;
+ const fraction_i64 fps = 1_i64 / refresh_rate;
+ REQUIRE( 1_i64 / fps == refresh_rate );
+ REQUIRE( fps == 1_i64 / refresh_rate );
+ REQUIRE( 2_i64 * fps == 1_i64 / ( refresh_rate / 2_i64 ) );
+
+ REQUIRE( fractions_i64::milli / 2_i64 == 500_i64 * fractions_i64::micro );
+ REQUIRE( 1_m / 2_one == 500_one * 1_u );
+ REQUIRE( 1_i64 / fractions_i64::milli == fractions_i64::kilo );
+ REQUIRE( fractions_i64::milli/-1000_i64 == -fractions_i64::micro );
+ REQUIRE( 500_i64 * fractions_i64::milli == fractions_i64::seconds/2_i64 );
+ REQUIRE( 1000_ms == fractions_i64::seconds );
+ REQUIRE( 1_i64 * fractions_i64::seconds == 60_i64/fractions_i64::minutes );
+ REQUIRE( 60_s == fractions_i64::minutes );
+ REQUIRE( 60_s == 1_min );
+ REQUIRE( 60_i64 * fractions_i64::minutes == fractions_i64::hours );
+ REQUIRE( 24_i64 * fractions_i64::hours == fractions_i64::days );
+ REQUIRE( 24_h == 1_d );
+
+ const fraction_i64 m(720_i64 * fractions_i64::minutes); // 12 hours
+ const fraction_i64 h( 48_i64 * fractions_i64::hours);
+ const fraction_i64 d( 2_i64 * fractions_i64::days);
+ const fraction_i64 t = m + h + d;
+ REQUIRE( m == h/4_i64 );
+ REQUIRE( h == d );
+ REQUIRE( t > 4_i64 * fractions_i64::days );
+ }
+ {
+ fraction_i64 a ( 1'000l, 1lu ); // 1'000s
+ fraction_i64 b ( 1'000l, 1'000'000'000lu ); // 1'000ns
+ REQUIRE( 1000_s == a );
+ REQUIRE( 1000_ns == b );
+ fraction_i64 c = a + b;
+ fraction_i64 exp_c ( 1'000'000'000lu + 1lu, 1'000'000lu ); // 1'000ns
+ REQUIRE( exp_c == c );
+ }
+}
+
+TEST_CASE( "Fraction Time Arithmetic Add Test 03.1", "[fraction][fraction_timespec][add]" ) {
+ // 10.4 + 0.4 = 10.8
+ {
+ fraction_timespec a ( 10_i64, 400000000_i64 );
+ fraction_timespec b ( 0_i64, 400000000_i64 );
+ fraction_timespec exp_sum ( 10_i64, 800000000_i64 );
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+ // 10.4 + 0.4 = 10.8
+ {
+ fraction_i64 a ( 10_s + 400_ms );
+ fraction_i64 b ( 0_s + 400_ms );
+ fraction_i64 exp_sum ( 10_s + 800_ms );
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a+b " + (a+b).to_string() );
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+ // 10.4 + 0.4 = 10.8
+ {
+ fraction_timespec a ( 10_s + 400_ms );
+ fraction_timespec b ( 0_s + 400_ms );
+ fraction_timespec exp_sum ( 10_s + 800_ms );
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a+b " + (a+b).to_string() );
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+ // 10.4 + 0.7 = 11.1
+ {
+ fraction_timespec a { 10_s + 400_ms };
+ fraction_timespec b { 0_s + 700_ms };
+ fraction_timespec exp_sum { 11_s + 100_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a+b " + (a+b).to_string() );
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+ // 10.4 + 2.7 (in denominator) = 13.1
+ {
+ fraction_timespec a { 10_s + 400_ms };
+ fraction_timespec b { 0_s + 2700_ms };
+ fraction_timespec exp_sum { 13_s + 100_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a+b " + (a+b).to_string() );
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+ // 10.4 + -0.3 = 10.1
+ {
+ fraction_timespec a { 10_s + 400_ms };
+ fraction_timespec b { 0_s + -300_ms };
+ fraction_timespec exp_sum { 10_s + 100_ms };
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+ // 10.-3 + 0.4 = 10.1
+ {
+ fraction_timespec a { 10_s + -300_ms };
+ fraction_timespec b { 0_s + 400_ms };
+ fraction_timespec exp_sum { 10_s + 100_ms };
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+ // 10.4 + -0.9 = 9.5
+ {
+ fraction_timespec a { 10_s + 400_ms };
+ fraction_timespec b { 0_s + -900_ms };
+ fraction_timespec exp_sum { 9_s + 500_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a+b " + (a+b).to_string() );
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+ // 10.4 + -2.7 = 7.7
+ {
+ fraction_timespec a { 10_s + 400_ms };
+ fraction_timespec b { 0_s + -2700_ms };
+ fraction_timespec exp_sum { 7_s + 700_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a+b " + (a+b).to_string() );
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+ // 10.-9 + 0.4 = 9.5
+ {
+ fraction_timespec a { 10_s + -900_ms };
+ fraction_timespec b { 0_s + 400_ms };
+ fraction_timespec exp_sum { 9_s + 500_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a+b " + (a+b).to_string() );
+ REQUIRE( ( a + b ) == exp_sum );
+ }
+}
+
+TEST_CASE( "Fraction Time Arithmetic Sub Test 03.2", "[fraction][fraction_timespec][sub]" ) {
+ // 10.4 - 0.3 = 10.1
+ {
+ fraction_timespec a ( 10_i64, 400000000_i64 );
+ fraction_timespec b ( 0_i64, 300000000_i64 );
+ fraction_timespec exp_sum ( 10_i64, 100000000_i64 );
+ REQUIRE( ( a - b ) == exp_sum );
+ }
+ // 10.4 - 0.3 = 10.1
+ {
+ fraction_timespec a ( 10_s + 400_ms );
+ fraction_timespec b ( 0_s + 300_ms );
+ fraction_timespec exp_sum ( 10_s + 100_ms );
+ REQUIRE( ( a - b ) == exp_sum );
+ }
+ // 10.4 - 0.7 = 9.7
+ {
+ fraction_timespec a { 10_s + 400_ms };
+ fraction_timespec b { 0_s + 700_ms };
+ fraction_timespec exp_sum { 9_s + 700_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a-b " + (a-b).to_string() );
+ REQUIRE( ( a - b ) == exp_sum );
+ }
+ // 10.4 - 2.7 (in denominator) = 7.7
+ {
+ fraction_timespec a { 10_s + 400_ms };
+ fraction_timespec b { 0_s + 2700_ms };
+ fraction_timespec exp_sum { 7_s + 700_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a-b " + (a-b).to_string() );
+ REQUIRE( ( a - b ) == exp_sum );
+ }
+ // 10.4 - -0.3 = 10.7
+ {
+ fraction_timespec a { 10_s + 400_ms };
+ fraction_timespec b { 0_s + -300_ms };
+ fraction_timespec exp_sum { 10_s + 700_ms };
+ REQUIRE( ( a - b ) == exp_sum );
+ }
+ // 10.-2 - 0.4 = 9.4
+ {
+ fraction_timespec a { 10_s + -200_ms };
+ fraction_timespec b { 0_s + 400_ms };
+ fraction_timespec exp_sum { 9_s + 400_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a-b " + (a-b).to_string() );
+ REQUIRE( ( a - b ) == exp_sum );
+ }
+ // 10.4 - -0.9 = 11.3
+ {
+ fraction_timespec a { 10_s + 400_ms };
+ fraction_timespec b { 0_s + -900_ms };
+ fraction_timespec exp_sum { 11_s + 300_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a-b " + (a-b).to_string() );
+ REQUIRE( ( a - b ) == exp_sum );
+ }
+ // 10.-9 - 0.4 = 8.7
+ {
+ fraction_timespec a { 10_s + -900_ms };
+ fraction_timespec b { 0_s + 400_ms };
+ fraction_timespec exp_sum { 8_s + 700_ms };
+ INFO_STR(" a " + a.to_string() );
+ INFO_STR(" b " + b.to_string() );
+ INFO_STR(" a-b " + (a-b).to_string() );
+ REQUIRE( ( a - b ) == exp_sum );
+ }
+}
+