// Minimal rational number type, supporting the needs of `fractional` and
// (later) `precisedelta`. Mirrors Python's `fractions.Fraction`: a normalized
// numerator/denominator pair (denominator > 0, sign carried by numerator)
// with `limit_denominator` approximation.
//
// See docs/specs/port-to-moonbit.md (porting gap G4).

/// A normalized fraction `numer / denom` with `denom > 0`.
/// The sign (if any) is carried by `numer`.
pub struct Rational {
  numer : Int
  denom : Int
}

/// Greatest common divisor (Euclidean), always non-negative.
fn gcd(a : Int, b : Int) -> Int {
  let mut x = if a < 0 { -a } else { a }
  let mut y = if b < 0 { -b } else { b }
  while y != 0 {
    let t = y
    y = x % y
    x = t
  }
  x
}

/// Construct a normalized Rational from integer numerator/denominator.
pub fn Rational::new(numer : Int, denom : Int) -> Rational {
  if denom == 0 {
    abort("Rational: denominator is zero")
  }
  let d = gcd(numer, denom)
  if denom < 0 {
    { numer: -numer / d, denom: -denom / d }
  } else {
    { numer: numer / d, denom: denom / d }
  }
}

/// Best rational approximation of `x` with denominator <= `max_den`, using a
/// brute-force search over denominators (same result as Python's
/// `Fraction.limit_denominator` because we minimize |p/q - x| and prefer
/// smaller q on ties).
pub fn Rational::from_float_approx(x : Double, max_den : Int) -> Rational {
  if x.is_nan() || x.is_inf() {
    abort("Rational::from_float_approx: not finite")
  }
  if x == 0.0 {
    return { numer: 0, denom: 1 }
  }
  let max_q = if max_den < 1 { 1 } else { max_den }
  let neg = x < 0.0
  let ax = if neg { -x } else { x }
  let mut best_n : Int = 0
  let mut best_d : Int = 1
  let mut best_err = ax // |0/1 - x|
  for q = 1; q <= max_q; q = q + 1 {
    let p = @math.round(ax * q.to_double()).to_int()
    let approx = p.to_double() / q.to_double()
    let err = (ax - approx).abs()
    // prefer strictly smaller error; on equal error keep smaller denominator
    if err < best_err {
      best_err = err
      best_n = p
      best_d = q
    }
  }
  if neg {
    best_n = -best_n
  }
  Rational::new(best_n, best_d)
}

/// The numerator (sign carried here).
pub fn Rational::numerator(self : Rational) -> Int {
  self.numer
}

/// The denominator (always > 0).
pub fn Rational::denominator(self : Rational) -> Int {
  self.denom
}