Skip to content

rational_approximation.hpp

SECTIONMath INCLUDEnoya/math/rational_approximation.hpp

在分子、分母均有上界时,求给定有理数两侧最接近的最简分数。

\[ \displaystyle \frac{L}{R}\le\frac{x}{y}\le\frac{U}{V} \]

Complexity: Time: \(O(\log \max (x , y))\). Space: \(O(1)\).

Source: Library Checker

AC 记录:rational_approximation。

跳到代码 · GitHub ↗

Implementation

当前头文件,省略 include guard;依赖见 #include。

// SPDX-License-Identifier: Apache-2.0
// Adapted from Library Checker, with changes for libra.
// Sources and license: third_party/README.md.

/// @complexity Time: O(log max(x,y)). Space: O(1).

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <utility>

namespace noya {

struct bounded_rational {
  std::int64_t num = 0;
  std::int64_t den = 1;
};

/// @brief Find the closest reduced fractions on both sides of x/y whose
/// numerators and denominators are at most lim. Maximal Stern--Brocot jumps
/// follow Euclidean remainders, capped by both coordinate bounds. Missing
/// lower and upper answers are represented by 0/1 and 1/0.
inline std::pair<bounded_rational, bounded_rational>
rational_approximation(std::int64_t lim, std::int64_t x, std::int64_t y) {
  assert(lim >= 1 && x >= 1 && y >= 1);
  bounded_rational lo{0, 1}, hi{1, 0};
  auto advance = [&](bounded_rational &a, bounded_rational b, std::int64_t k) {
    if (b.num) k = std::min(k, (lim - a.num) / b.num);
    if (b.den) k = std::min(k, (lim - a.den) / b.den);
    // The bounds are applied before multiplying, so both coordinates fit.
    a.num += k * b.num;
    a.den += k * b.den;
    return k;
  };
  // x = lo.den * target.num - lo.num * target.den,
  // y = hi.num * target.den - hi.den * target.num.
  while (true) {
    std::int64_t ls = advance(lo, hi, x / y);
    x -= ls * y;
    if (x == 0) {
      hi = lo;
      break;
    }
    std::int64_t rs = advance(hi, lo, y / x);
    y -= rs * x;
    if (y == 0) {
      lo = hi;
      break;
    }
    if (ls == 0 && rs == 0) break;
  }
  return {lo, hi};
}

} // namespace noya

 // NOYA_RATIONAL_APPROXIMATION_HPP
#ifndef NOYA_RATIONAL_APPROXIMATION_HPP
#define NOYA_RATIONAL_APPROXIMATION_HPP 1

// SPDX-License-Identifier: Apache-2.0
// Adapted from Library Checker, with changes for libra.
// Sources and license: third_party/README.md.

/// @complexity Time: O(log max(x,y)). Space: O(1).

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <utility>

namespace noya {

struct bounded_rational {
  std::int64_t num = 0;
  std::int64_t den = 1;
};

/// @brief Find the closest reduced fractions on both sides of x/y whose
/// numerators and denominators are at most lim. Maximal Stern--Brocot jumps
/// follow Euclidean remainders, capped by both coordinate bounds. Missing
/// lower and upper answers are represented by 0/1 and 1/0.
inline std::pair<bounded_rational, bounded_rational>
rational_approximation(std::int64_t lim, std::int64_t x, std::int64_t y) {
  assert(lim >= 1 && x >= 1 && y >= 1);
  bounded_rational lo{0, 1}, hi{1, 0};
  auto advance = [&](bounded_rational &a, bounded_rational b, std::int64_t k) {
    if (b.num) k = std::min(k, (lim - a.num) / b.num);
    if (b.den) k = std::min(k, (lim - a.den) / b.den);
    // The bounds are applied before multiplying, so both coordinates fit.
    a.num += k * b.num;
    a.den += k * b.den;
    return k;
  };
  // x = lo.den * target.num - lo.num * target.den,
  // y = hi.num * target.den - hi.den * target.num.
  while (true) {
    std::int64_t ls = advance(lo, hi, x / y);
    x -= ls * y;
    if (x == 0) {
      hi = lo;
      break;
    }
    std::int64_t rs = advance(hi, lo, y / x);
    y -= rs * x;
    if (y == 0) {
      lo = hi;
      break;
    }
    if (ls == 0 && rs == 0) break;
  }
  return {lo, hi};
}

} // namespace noya

#endif // NOYA_RATIONAL_APPROXIMATION_HPP
// SPDX-License-Identifier: Apache-2.0
// Adapted from Library Checker, with changes for libra.
// Sources and license: third_party/README.md.

/// @complexity Time: O(log max(x,y)). Space: O(1).

#include <algorithm>
#include <cassert>
#include <cstdint>
#include <utility>

namespace noya {

struct bounded_rational {
  std::int64_t num = 0;
  std::int64_t den = 1;
};

/// @brief Find the closest reduced fractions on both sides of x/y whose
/// numerators and denominators are at most lim. Maximal Stern--Brocot jumps
/// follow Euclidean remainders, capped by both coordinate bounds. Missing
/// lower and upper answers are represented by 0/1 and 1/0.
inline std::pair<bounded_rational, bounded_rational>
rational_approximation(std::int64_t lim, std::int64_t x, std::int64_t y) {
  assert(lim >= 1 && x >= 1 && y >= 1);
  bounded_rational lo{0, 1}, hi{1, 0};
  auto advance = [&](bounded_rational &a, bounded_rational b, std::int64_t k) {
    if (b.num) k = std::min(k, (lim - a.num) / b.num);
    if (b.den) k = std::min(k, (lim - a.den) / b.den);
    // The bounds are applied before multiplying, so both coordinates fit.
    a.num += k * b.num;
    a.den += k * b.den;
    return k;
  };
  // x = lo.den * target.num - lo.num * target.den,
  // y = hi.num * target.den - hi.den * target.num.
  while (true) {
    std::int64_t ls = advance(lo, hi, x / y);
    x -= ls * y;
    if (x == 0) {
      hi = lo;
      break;
    }
    std::int64_t rs = advance(hi, lo, y / x);
    y -= rs * x;
    if (y == 0) {
      lo = hi;
      break;
    }
    if (ls == 0 && rs == 0) break;
  }
  return {lo, hi};
}

} // namespace noya

 // NOYA_RATIONAL_APPROXIMATION_HPP