diff options
author | Sven Gothel <[email protected]> | 2022-05-01 06:57:55 +0200 |
---|---|---|
committer | Sven Gothel <[email protected]> | 2022-05-01 06:57:55 +0200 |
commit | 043ab5ce128b4e6b6e08034f0d050fb1d8308c33 (patch) | |
tree | ef51a19266a9851827416291221ed42e90357f0e | |
parent | ea2a46c821d23b02095a7a78b0342a97c30ea728 (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.hpp | 41 | ||||
-rw-r--r-- | include/jau/fraction_type.hpp | 1045 | ||||
-rw-r--r-- | include/jau/int_math.hpp | 90 | ||||
-rw-r--r-- | src/basic_types.cpp | 73 | ||||
-rw-r--r-- | src/environment.cpp | 17 | ||||
-rw-r--r-- | test/test_fractions_01.cpp | 734 |
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 ); + } +} + |