Skip to content

Commit cd9961d

Browse files
author
Tomáš Kolárik
committed
simple_continued_fraction: added protected non-const accessor to partial denominators (#970)
1 parent 5376689 commit cd9961d

File tree

1 file changed

+42
-34
lines changed

1 file changed

+42
-34
lines changed

include/boost/math/tools/simple_continued_fraction.hpp

+42-34
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,20 @@ namespace boost::math::tools {
3232
template<typename Real, typename Z = int64_t>
3333
class simple_continued_fraction {
3434
public:
35-
simple_continued_fraction(Real x) : x_{x} {
35+
typedef Z int_type;
36+
37+
simple_continued_fraction(Real x) {
3638
using std::floor;
3739
using std::abs;
3840
using std::sqrt;
3941
using std::isfinite;
42+
const Real orig_x = x;
4043
if (!isfinite(x)) {
41-
throw std::domain_error("Cannot convert non-finites into continued fractions.");
44+
throw std::domain_error("Cannot convert non-finites into continued fractions.");
4245
}
4346
b_.reserve(50);
4447
Real bj = floor(x);
45-
b_.push_back(static_cast<Z>(bj));
48+
b_.push_back(static_cast<int_type>(bj));
4649
if (bj == x) {
4750
b_.shrink_to_fit();
4851
return;
@@ -54,14 +57,13 @@ class simple_continued_fraction {
5457
}
5558
Real C = f;
5659
Real D = 0;
57-
int i = 0;
58-
// the "1 + i++" lets the error bound grow slowly with the number of convergents.
60+
// the "1 + i" lets the error bound grow slowly with the number of convergents.
5961
// I have not worked out the error propagation of the Modified Lentz's method to see if it does indeed grow at this rate.
6062
// Numerical Recipes claims that no one has worked out the error analysis of the modified Lentz's method.
61-
while (abs(f - x_) >= (1 + i++)*std::numeric_limits<Real>::epsilon()*abs(x_))
62-
{
63+
const Real eps_abs_orig_x = std::numeric_limits<Real>::epsilon()*abs(orig_x);
64+
for (int i = 0; abs(f - orig_x) >= (1 + i)*eps_abs_orig_x; ++i) {
6365
bj = floor(x);
64-
b_.push_back(static_cast<Z>(bj));
66+
b_.push_back(static_cast<int_type>(bj));
6567
x = 1/(x-bj);
6668
D += bj;
6769
if (D == 0) {
@@ -78,29 +80,31 @@ class simple_continued_fraction {
7880
// The shorter representation is considered the canonical representation,
7981
// so if we compute a non-canonical representation, change it to canonical:
8082
if (b_.size() > 2 && b_.back() == 1) {
81-
b_[b_.size() - 2] += 1;
82-
b_.resize(b_.size() - 1);
83+
b_.pop_back();
84+
b_.back() += 1;
8385
}
8486
b_.shrink_to_fit();
85-
86-
for (size_t i = 1; i < b_.size(); ++i) {
87+
88+
const size_t size_ = b_.size();
89+
for (size_t i = 1; i < size_; ++i) {
8790
if (b_[i] <= 0) {
8891
std::ostringstream oss;
8992
oss << "Found a negative partial denominator: b[" << i << "] = " << b_[i] << "."
9093
#ifndef BOOST_MATH_STANDALONE
91-
<< " This means the integer type '" << boost::core::demangle(typeid(Z).name())
94+
<< " This means the integer type '" << boost::core::demangle(typeid(int_type).name())
9295
#else
93-
<< " This means the integer type '" << typeid(Z).name()
96+
<< " This means the integer type '" << typeid(int_type).name()
9497
#endif
9598
<< "' has overflowed and you need to use a wider type,"
9699
<< " or there is a bug.";
97100
throw std::overflow_error(oss.str());
98101
}
99102
}
100103
}
101-
104+
102105
Real khinchin_geometric_mean() const {
103-
if (b_.size() == 1) {
106+
const size_t size_ = b_.size();
107+
if (size_ == 1) {
104108
return std::numeric_limits<Real>::quiet_NaN();
105109
}
106110
using std::log;
@@ -110,53 +114,57 @@ class simple_continued_fraction {
110114
// A random partial denominator has ~80% chance of being in this table:
111115
const std::array<Real, 7> logs{std::numeric_limits<Real>::quiet_NaN(), Real(0), log(static_cast<Real>(2)), log(static_cast<Real>(3)), log(static_cast<Real>(4)), log(static_cast<Real>(5)), log(static_cast<Real>(6))};
112116
Real log_prod = 0;
113-
for (size_t i = 1; i < b_.size(); ++i) {
114-
if (b_[i] < static_cast<Z>(logs.size())) {
117+
for (size_t i = 1; i < size_; ++i) {
118+
if (b_[i] < static_cast<int_type>(logs.size())) {
115119
log_prod += logs[b_[i]];
116120
}
117121
else
118122
{
119123
log_prod += log(static_cast<Real>(b_[i]));
120124
}
121125
}
122-
log_prod /= (b_.size()-1);
126+
log_prod /= (size_-1);
123127
return exp(log_prod);
124128
}
125-
129+
126130
Real khinchin_harmonic_mean() const {
127-
if (b_.size() == 1) {
131+
const size_t size_ = b_.size();
132+
if (size_ == 1) {
128133
return std::numeric_limits<Real>::quiet_NaN();
129134
}
130-
Real n = b_.size() - 1;
135+
Real n = size_ - 1;
131136
Real denom = 0;
132-
for (size_t i = 1; i < b_.size(); ++i) {
137+
for (size_t i = 1; i < size_; ++i) {
133138
denom += 1/static_cast<Real>(b_[i]);
134139
}
135140
return n/denom;
136141
}
137-
138-
const std::vector<Z>& partial_denominators() const {
142+
143+
// Note that this also includes the integer part (i.e. all the coefficients)
144+
const std::vector<int_type>& partial_denominators() const {
139145
return b_;
140146
}
141-
147+
142148
template<typename T, typename Z2>
143149
friend std::ostream& operator<<(std::ostream& out, simple_continued_fraction<T, Z2>& scf);
150+
protected:
151+
simple_continued_fraction() = default;
144152

153+
// Note that non-const operations may result in invalid simple continued fraction
154+
std::vector<int_type>& partial_denominators() {
155+
return b_;
156+
}
145157
private:
146-
const Real x_;
147-
std::vector<Z> b_;
158+
std::vector<int_type> b_{};
148159
};
149160

150161

151162
template<typename Real, typename Z2>
152163
std::ostream& operator<<(std::ostream& out, simple_continued_fraction<Real, Z2>& scf) {
153164
constexpr const int p = std::numeric_limits<Real>::max_digits10;
154-
if constexpr (p == 2147483647) {
155-
out << std::setprecision(scf.x_.backend().precision());
156-
} else {
157-
out << std::setprecision(p);
158-
}
159-
165+
static_assert(p != 2147483647, "numeric_limits<Real>::max_digits10 == 2147483647");
166+
out << std::setprecision(p);
167+
160168
out << "[" << scf.b_.front();
161169
if (scf.b_.size() > 1)
162170
{

0 commit comments

Comments
 (0)