engel_expansion.hpp 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  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_ENGEL_EXPANSION_HPP
  6. #define BOOST_MATH_TOOLS_ENGEL_EXPANSION_HPP
  7. #include <cmath>
  8. #include <cstdint>
  9. #include <vector>
  10. #include <ostream>
  11. #include <iomanip>
  12. #include <limits>
  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 engel_expansion {
  24. public:
  25. engel_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 an Engel expansion.");
  34. }
  35. if(x==0)
  36. {
  37. throw std::domain_error("Zero does not have an Engel expansion.");
  38. }
  39. a_.reserve(64);
  40. // Let the error bound grow by 1 ULP/iteration.
  41. // I haven't done the error analysis to show that this is an expected rate of error growth,
  42. // but if you don't do this, you can easily get into an infinite loop.
  43. Real i = 1;
  44. Real computed = 0;
  45. Real term = 1;
  46. Real scale = std::numeric_limits<Real>::epsilon()*abs(x_)/2;
  47. Real u = x;
  48. while (abs(x_ - computed) > (i++)*scale)
  49. {
  50. Real recip = 1/u;
  51. Real ak = ceil(recip);
  52. a_.push_back(static_cast<Z>(ak));
  53. u = u*ak - 1;
  54. if (u==0)
  55. {
  56. break;
  57. }
  58. term /= ak;
  59. computed += term;
  60. }
  61. for (size_t j = 1; j < a_.size(); ++j)
  62. {
  63. // Sanity check: This should only happen when wraparound occurs:
  64. if (a_[j] < a_[j-1])
  65. {
  66. throw std::domain_error("The digits of an Engel expansion must form a non-decreasing sequence; consider increasing the wide of the integer type.");
  67. }
  68. // Watch out for saturating behavior:
  69. if (a_[j] == (std::numeric_limits<Z>::max)())
  70. {
  71. throw std::domain_error("The integer type Z does not have enough width to hold the terms of the Engel expansion; please widen the type.");
  72. }
  73. }
  74. a_.shrink_to_fit();
  75. }
  76. const std::vector<Z>& digits() const
  77. {
  78. return a_;
  79. }
  80. template<typename T, typename Z2>
  81. friend std::ostream& operator<<(std::ostream& out, engel_expansion<T, Z2>& eng);
  82. private:
  83. Real x_;
  84. std::vector<Z> a_;
  85. };
  86. template<typename Real, typename Z2>
  87. std::ostream& operator<<(std::ostream& out, engel_expansion<Real, Z2>& engel)
  88. {
  89. constexpr const int p = std::numeric_limits<Real>::max_digits10;
  90. if constexpr (p == 2147483647)
  91. {
  92. out << std::setprecision(engel.x_.backend().precision());
  93. }
  94. else
  95. {
  96. out << std::setprecision(p);
  97. }
  98. out << "{";
  99. for (size_t i = 0; i < engel.a_.size() - 1; ++i)
  100. {
  101. out << engel.a_[i] << ", ";
  102. }
  103. out << engel.a_.back();
  104. out << "}";
  105. return out;
  106. }
  107. }
  108. #endif