/boost/math/tools/engel_expansion.hpp

https://bitbucket.org/bosp/external-boost · C++ Header · 116 lines · 92 code · 15 blank · 9 comment · 11 complexity · 83321aa77aaf7a7cd500c5dbe9aa66ef MD5 · raw file

  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. namespace boost::math::tools {
  15. template<typename Real, typename Z = int64_t>
  16. class engel_expansion {
  17. public:
  18. engel_expansion(Real x) : x_{x}
  19. {
  20. using std::floor;
  21. using std::abs;
  22. using std::sqrt;
  23. using std::isfinite;
  24. if (!isfinite(x))
  25. {
  26. throw std::domain_error("Cannot convert non-finites into an Engel expansion.");
  27. }
  28. if(x==0)
  29. {
  30. throw std::domain_error("Zero does not have an Engel expansion.");
  31. }
  32. a_.reserve(64);
  33. // Let the error bound grow by 1 ULP/iteration.
  34. // I haven't done the error analysis to show that this is an expected rate of error growth,
  35. // but if you don't do this, you can easily get into an infinite loop.
  36. Real i = 1;
  37. Real computed = 0;
  38. Real term = 1;
  39. Real scale = std::numeric_limits<Real>::epsilon()*abs(x_)/2;
  40. Real u = x;
  41. while (abs(x_ - computed) > (i++)*scale)
  42. {
  43. Real recip = 1/u;
  44. Real ak = ceil(recip);
  45. a_.push_back(static_cast<Z>(ak));
  46. u = u*ak - 1;
  47. if (u==0)
  48. {
  49. break;
  50. }
  51. term /= ak;
  52. computed += term;
  53. }
  54. for (size_t j = 1; j < a_.size(); ++j)
  55. {
  56. // Sanity check: This should only happen when wraparound occurs:
  57. if (a_[j] < a_[j-1])
  58. {
  59. throw std::domain_error("The digits of an Engel expansion must form a non-decreasing sequence; consider increasing the wide of the integer type.");
  60. }
  61. // Watch out for saturating behavior:
  62. if (a_[j] == (std::numeric_limits<Z>::max)())
  63. {
  64. 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.");
  65. }
  66. }
  67. a_.shrink_to_fit();
  68. }
  69. const std::vector<Z>& digits() const
  70. {
  71. return a_;
  72. }
  73. template<typename T, typename Z2>
  74. friend std::ostream& operator<<(std::ostream& out, engel_expansion<T, Z2>& eng);
  75. private:
  76. Real x_;
  77. std::vector<Z> a_;
  78. };
  79. template<typename Real, typename Z2>
  80. std::ostream& operator<<(std::ostream& out, engel_expansion<Real, Z2>& engel)
  81. {
  82. constexpr const int p = std::numeric_limits<Real>::max_digits10;
  83. if constexpr (p == 2147483647)
  84. {
  85. out << std::setprecision(engel.x_.backend().precision());
  86. }
  87. else
  88. {
  89. out << std::setprecision(p);
  90. }
  91. out << "{";
  92. for (size_t i = 0; i < engel.a_.size() - 1; ++i)
  93. {
  94. out << engel.a_[i] << ", ";
  95. }
  96. out << engel.a_.back();
  97. out << "}";
  98. return out;
  99. }
  100. }
  101. #endif