luroth_expansion.hpp 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147
  1. // (C) Copyright Nick Thompson 2020.
  2. // Use, modification and distribution are subject to the
  3. // Boost Software License, Version 1.0. (See accompanying file
  4. // LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
  5. #ifndef BOOST_MATH_TOOLS_LUROTH_EXPANSION_HPP
  6. #define BOOST_MATH_TOOLS_LUROTH_EXPANSION_HPP
  7. #include <vector>
  8. #include <ostream>
  9. #include <iomanip>
  10. #include <cmath>
  11. #include <limits>
  12. #include <cstdint>
  13. #include <stdexcept>
  14. #include <boost/math/tools/is_standalone.hpp>
  15. #ifndef BOOST_MATH_STANDALONE
  16. #include <boost/config.hpp>
  17. #ifdef BOOST_MATH_NO_CXX17_IF_CONSTEXPR
  18. #error "The header <boost/math/norms.hpp> can only be used in C++17 and later."
  19. #endif
  20. #endif
  21. namespace boost::math::tools {
  22. template<typename Real, typename Z = int64_t>
  23. class luroth_expansion {
  24. public:
  25. luroth_expansion(Real x) : x_{x}
  26. {
  27. using std::floor;
  28. using std::abs;
  29. using std::sqrt;
  30. using std::isfinite;
  31. if (!isfinite(x))
  32. {
  33. throw std::domain_error("Cannot convert non-finites into a Luroth representation.");
  34. }
  35. d_.reserve(50);
  36. Real dn1 = floor(x);
  37. d_.push_back(static_cast<Z>(dn1));
  38. if (dn1 == x)
  39. {
  40. d_.shrink_to_fit();
  41. return;
  42. }
  43. // This attempts to follow the notation of:
  44. // "Khinchine's constant for Luroth Representation", by Sophia Kalpazidou.
  45. x = x - dn1;
  46. Real computed = dn1;
  47. Real prod = 1;
  48. // Let the error bound grow by 1 ULP/iteration.
  49. // I haven't done the error analysis to show that this is an expected rate of error growth,
  50. // but if you don't do this, you can easily get into an infinite loop.
  51. Real i = 1;
  52. Real scale = std::numeric_limits<Real>::epsilon()*abs(x_)/2;
  53. while (abs(x_ - computed) > (i++)*scale)
  54. {
  55. Real recip = 1/x;
  56. Real dn = floor(recip);
  57. // x = n + 1/k => lur(x) = ((n; k - 1))
  58. // Note that this is a bit different than Kalpazidou (examine the half-open interval of definition carefully).
  59. // One way to examine this definition is better for rationals (it never happens for irrationals)
  60. // is to consider i + 1/3. If you follow Kalpazidou, then you get ((i, 3, 0)); a zero digit!
  61. // That's bad since it destroys uniqueness and also breaks the computation of the geometric mean.
  62. if (recip == dn) {
  63. d_.push_back(static_cast<Z>(dn - 1));
  64. break;
  65. }
  66. d_.push_back(static_cast<Z>(dn));
  67. Real tmp = 1/(dn+1);
  68. computed += prod*tmp;
  69. prod *= tmp/dn;
  70. x = dn*(dn+1)*(x - tmp);
  71. }
  72. for (size_t i = 1; i < d_.size(); ++i)
  73. {
  74. // Sanity check:
  75. if (d_[i] <= 0)
  76. {
  77. throw std::domain_error("Found a digit <= 0; this is an error.");
  78. }
  79. }
  80. d_.shrink_to_fit();
  81. }
  82. const std::vector<Z>& digits() const {
  83. return d_;
  84. }
  85. // Under the assumption of 'randomness', this mean converges to 2.2001610580.
  86. // See Finch, Mathematical Constants, section 1.8.1.
  87. Real digit_geometric_mean() const {
  88. if (d_.size() == 1) {
  89. return std::numeric_limits<Real>::quiet_NaN();
  90. }
  91. using std::log;
  92. using std::exp;
  93. Real g = 0;
  94. for (size_t i = 1; i < d_.size(); ++i) {
  95. g += log(static_cast<Real>(d_[i]));
  96. }
  97. return exp(g/(d_.size() - 1));
  98. }
  99. template<typename T, typename Z2>
  100. friend std::ostream& operator<<(std::ostream& out, luroth_expansion<T, Z2>& scf);
  101. private:
  102. const Real x_;
  103. std::vector<Z> d_;
  104. };
  105. template<typename Real, typename Z2>
  106. std::ostream& operator<<(std::ostream& out, luroth_expansion<Real, Z2>& luroth)
  107. {
  108. constexpr const int p = std::numeric_limits<Real>::max_digits10;
  109. if constexpr (p == 2147483647)
  110. {
  111. out << std::setprecision(luroth.x_.backend().precision());
  112. }
  113. else
  114. {
  115. out << std::setprecision(p);
  116. }
  117. out << "((" << luroth.d_.front();
  118. if (luroth.d_.size() > 1)
  119. {
  120. out << "; ";
  121. for (size_t i = 1; i < luroth.d_.size() -1; ++i)
  122. {
  123. out << luroth.d_[i] << ", ";
  124. }
  125. out << luroth.d_.back();
  126. }
  127. out << "))";
  128. return out;
  129. }
  130. }
  131. #endif