Line data Source code
1 0 : // Distributed under the MIT License. 2 : // See LICENSE.txt for details. 3 : 4 : #pragma once 5 : 6 : #include <algorithm> 7 : #include <cmath> 8 : #include <cstdint> 9 : #include <limits> 10 : #include <type_traits> 11 : 12 : #include "Utilities/ConstantExpressions.hpp" 13 : #include "Utilities/Gsl.hpp" 14 : 15 : /// Type trait to check if a type looks like a fraction (specifically, 16 : /// if it has numerator and denominator methods) 17 : /// @{ 18 : template <typename T, typename = std::void_t<>> 19 1 : struct is_fraction : std::false_type {}; 20 : template <typename T> 21 : struct is_fraction<T, std::void_t<decltype(std::declval<T>().numerator()), 22 : decltype(std::declval<T>().denominator())>> 23 : : std::true_type {}; 24 : 25 : template <typename T> 26 0 : constexpr bool is_fraction_v = is_fraction<T>::value; 27 : /// @} 28 : 29 : /// \ingroup UtilitiesGroup 30 : /// \brief Compute the continued fraction representation of a number 31 : /// 32 : /// The template argument may be a fraction type or a floating point 33 : /// type. If the expansion is computed exactly then it will be the 34 : /// shorter of the expansions for the given value. 35 : template <class T> 36 1 : class ContinuedFraction { 37 : template <typename U, typename = typename is_fraction<U>::type> 38 0 : struct value_type_helper; 39 : 40 : template <typename U> 41 : struct value_type_helper<U, std::false_type> { 42 : using type = int64_t; 43 : }; 44 : 45 : template <typename U> 46 : struct value_type_helper<U, std::true_type> { 47 : using type = std::decay_t<decltype(std::declval<U>().numerator())>; 48 : }; 49 : 50 : public: 51 : /// The decayed return type of T::numerator() for a fraction, 52 : /// int64_t for other types. 53 1 : using value_type = typename value_type_helper<T>::type; 54 : 55 0 : explicit ContinuedFraction(const T& value) 56 : : term_(ifloor(value)), 57 : remainder_(value - term_), 58 : error_([this, &value]() { 59 : using std::abs; 60 : using std::max; 61 : // For any non-fundamental type, epsilon() returns a 62 : // default-constructed value, which should be zero for 63 : // fractions. 64 : return max(abs(value), abs(remainder_)) * 65 : std::numeric_limits<T>::epsilon(); 66 : }()) {} 67 : 68 : /// Obtain the current element in the expansion 69 1 : value_type operator*() const { return term_; } 70 : 71 : /// Check if the expansion is incomplete. If T is a fraction type 72 : /// this will be true until the full exact representation of the 73 : /// fraction is produced. If T is a floating point type this will 74 : /// be true until the estimated numerical error is larger than the 75 : /// remaining error in the representation. 76 1 : explicit operator bool() const { return not done_; } 77 : 78 : /// Advance to the next element in the expansion 79 1 : ContinuedFraction& operator++() { 80 : // Terminate when remainder_ is consistent with zero. 81 : if (remainder_ == 0 or error_ > remainder_) { 82 : done_ = true; 83 : return *this; 84 : } 85 : remainder_ = 1 / remainder_; 86 : error_ *= square(remainder_); 87 : term_ = ifloor(remainder_); 88 : remainder_ -= term_; 89 : return *this; 90 : } 91 : 92 : private: 93 : template <typename U> 94 0 : static value_type ifloor(const U& x) { 95 : if constexpr (is_fraction_v<U>) { 96 : return static_cast<value_type>(x.numerator() / x.denominator()); 97 : } else { 98 : return static_cast<value_type>(std::floor(x)); 99 : } 100 : } 101 : 102 0 : value_type term_; 103 0 : T remainder_; 104 : // Estimate of error in the term. 105 0 : T error_; 106 0 : bool done_{false}; 107 : }; 108 : 109 : /// \ingroup UtilitiesGroup 110 : /// \brief Sum a continued fraction 111 : /// 112 : /// \tparam Fraction the result type, which must be a fraction type 113 : template <class Fraction> 114 1 : class ContinuedFractionSummer { 115 : public: 116 0 : using Term_t = std::decay_t<decltype(std::declval<Fraction>().numerator())>; 117 : 118 : /// Sum of the supplied continued fraction terms. If the exact sum 119 : /// cannot be represented by the `Fraction` type, a semiconvergent 120 : /// may be returned instead. 121 1 : Fraction value() const { return Fraction(numerator_, denominator_); } 122 : 123 : /// Insert a new term. Terms should be supplied from most to least 124 : /// significant. 125 1 : void insert(Term_t term) { 126 : if (overflowed_) { 127 : return; 128 : } 129 : 130 : Term_t new_numerator{}; 131 : Term_t new_denominator{}; 132 : if (__builtin_mul_overflow(term, numerator_, &new_numerator) or 133 : __builtin_add_overflow(new_numerator, prev_numerator_, 134 : &new_numerator) or 135 : __builtin_mul_overflow(term, denominator_, &new_denominator) or 136 : __builtin_add_overflow(new_denominator, prev_denominator_, 137 : &new_denominator)) { 138 : overflowed_ = true; 139 : 140 : // We can't add this term exactly, but we can try to return a 141 : // semiconvergent. 142 : using std::abs; 143 : const Term_t representable_semiconvergent_index = 144 : std::min((std::numeric_limits<Term_t>::max() - abs(prev_numerator_)) / 145 : abs(numerator_), 146 : (std::numeric_limits<Term_t>::max() - prev_denominator_) / 147 : denominator_); 148 : // There is an edge case where the two sides of this inequality 149 : // are equal, where we don't have enough information to 150 : // determine whether the semiconvergent approximation is better 151 : // than the current convergent. We assume it's not. 152 : if (2 * representable_semiconvergent_index <= term) { 153 : return; 154 : } 155 : 156 : new_numerator = 157 : representable_semiconvergent_index * numerator_ + prev_numerator_; 158 : new_denominator = 159 : representable_semiconvergent_index * denominator_ + prev_denominator_; 160 : } 161 : 162 : prev_numerator_ = numerator_; 163 : numerator_ = new_numerator; 164 : prev_denominator_ = denominator_; 165 : denominator_ = new_denominator; 166 : } 167 : 168 : private: 169 0 : Term_t numerator_{1}; 170 0 : Term_t denominator_{0}; 171 0 : Term_t prev_numerator_{0}; 172 0 : Term_t prev_denominator_{1}; 173 0 : bool overflowed_{false}; 174 : }; 175 : 176 : /// \ingroup UtilitiesGroup 177 : /// \brief Find the fraction in the supplied interval with the 178 : /// smallest denominator 179 : /// 180 : /// The endpoints are considered to be in the interval. The order of 181 : /// the arguments is not significant. The answer is unique as long as 182 : /// the interval has length less than 1; for longer intervals, an 183 : /// integer in the range will be returned. 184 : template <typename Fraction, typename T1, typename T2> 185 1 : Fraction simplest_fraction_in_interval(const T1& end1, const T2& end2) { 186 : ContinuedFractionSummer<Fraction> result; 187 : using Term_t = typename decltype(result)::Term_t; 188 : ContinuedFraction<T1> cf1(end1); 189 : ContinuedFraction<T2> cf2(end2); 190 : using InputTerm_t = std::common_type_t<typename decltype(cf1)::value_type, 191 : typename decltype(cf2)::value_type>; 192 : InputTerm_t term1 = *cf1; 193 : InputTerm_t term2 = *cf2; 194 : for (; cf1 and cf2; term1 = *++cf1, term2 = *++cf2) { 195 : if (term1 != term2) { 196 : if (++cf1) { 197 : ++term1; 198 : } 199 : if (++cf2) { 200 : ++term2; 201 : } 202 : using std::min; 203 : result.insert(gsl::narrow<Term_t>(min(term1, term2))); 204 : return result.value(); 205 : } 206 : result.insert(gsl::narrow<Term_t>(term1)); 207 : } 208 : return result.value(); 209 : }