SpECTRE Documentation Coverage Report
Current view: top level - Utilities - FractionUtilities.hpp Coverage Total Hit
Commit: 0d1d717af84a81fb1284a20c4013708fe85cc2f0 Lines: 40.0 % 25 10
Test Date: 2026-10-22 17:29:42
Legend: Lines:     hit not hit

            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              : }
        

Generated by: LCOV version 2.0-1