![]() |
Home | Libraries | People | FAQ | More |
#include <boost/math/tools/simple_continued_fraction.hpp> namespace boost::math::tools { template<typename Real, typename Z = int64_t> class simple_continued_fraction { public: simple_continued_fraction(Real x); Real khinchin_geometric_mean() const; Real khinchin_harmonic_mean() const; const std::vector<Z>& partial_denominators() const; template<typename T, typename Z_> friend std::ostream& operator<<(std::ostream& out, const simple_continued_fraction<T, Z_>& scf); }; }
The simple_continued_fraction
class provided by Boost affords the ability to convert a floating point number
into a simple continued fraction. In addition, we can answer a few questions
about the number in question using this representation.
Here's a minimal working example:
using boost::math::constants::pi; using boost::math::tools::simple_continued_fraction; auto cfrac = simple_continued_fraction(pi<long double>()); std::cout << "π ≈ " << cfrac << "\n"; // Prints: // π ≈ [3; 7, 15, 1, 292, 1, 1, 1, 2, 1, 3, 1, 14, 2, 1, 1, 2]
The class computes partial denominators while simultaneously computing convergents with the modified Lentz's algorithm. Once a convergent is within a few ulps of the input value, the computation stops.
Finite simple continued fractions are not unique: [a0; a1, ..., an, 1]
and [a0; a1,
..., an
+ 1] represent the same value. The class uses
the shorter, canonical representation, absorbing a trailing 1 into the preceding coefficient. For example,
3.75 is represented as [3;
1, 3], rather
than [3; 1,
2, 1].
The partial_denominators() member function returns a read-only reference
to the stored coefficients, including the integer part at index zero:
const auto& a = cfrac.partial_denominators(); std::cout << a.size() << " coefficients\n"; std::cout << a[0] << "\n"; // Integer part: 3 std::cout << a.at(1) << "\n"; // First partial denominator: 7
The reference remains valid for the lifetime of cfrac.
A prefix of these coefficients defines a convergent, which can be used as
a rational approximation. For example, the first four coefficients of the
expansion of π give 355/113. With #include
<boost/rational.hpp>,
this can be computed by evaluating the prefix backwards:
using rational = boost::rational<std::int64_t>; std::size_t n = 4; // Use the first four coefficients of cfrac above. rational approximation(a.at(n - 1)); for (std::size_t i = n - 1; i > 0; --i) { approximation = rational(a[i - 1]) + rational(1) / approximation; } std::cout << approximation << "\n"; // Prints: 355/113
In general, choose n between
1 and a.size()
and an integer type wide enough for the rational arithmetic, including intermediate
results. Longer prefixes can overflow a fixed-width integer type.
Note that every floating point number is a rational number, and this exact rational can be exactly converted to a finite continued fraction. This is perfectly sensible behavior, but we do not do it here. This is because when examining known values like π, it creates a large number of incorrect partial denominators, even if every bit of the binary representation is correct.
It may be the case the a few incorrect partial convergents is harmless, but we compute continued fractions because we would like to do something with them. One sensible thing to do it to ask whether the number is in some sense "random"; a question that can be partially answered by computing the Khinchin geometric mean
and Khinchin harmonic mean
If these approach Khinchin's constant K0 and K-1 as the number of partial denominators goes to infinity, then our number is "uninteresting" with respect to the characterization. These violations are washed out if too many incorrect partial denominators are included in the expansion.
Note: The convergence of these means to the Khinchin limit is exceedingly slow; we've used 30,000 decimal digits of π and only found two digits of agreement with K0. However, clear violations of are obvious, such as the continued fraction expansion of √2, whose Khinchin geometric mean is precisely 2.