from math import gcd
from numbers import Real

class RAN:
    """
    Rational-Addition Number: a/b + c.
    
    Parameters a, b, c may be ints or floats. The value is not collapsed
    to a single float until .collapse() is called.
    """

    def __init__(self, a, b, c):
        if b == 0:
            raise ValueError("b cannot be zero")
        self.a = a
        self.b = b
        self.c = c

    def collapse(self):
        """Force evaluation to a single number."""
        return self.a / self.b + self.c

    def __repr__(self):
        return f"RAN({self.a!r}, {self.b!r}, {self.c!r})"

    def __str__(self):
        return f"({self.a}/{self.b} + {self.c})"

    def __float__(self):
        return self.collapse()

    def __add__(self, other):
        if isinstance(other, Real):
            other = RAN(0, 1, other)
        if not isinstance(other, RAN):
            return NotImplemented
        a, b, c = self.a, self.b, self.c
        d, e, f = other.a, other.b, other.c
        # (a/b + c) + (d/e + f) = (ae + bd)/(be) + (c + f)
        return RAN(a * e + b * d, b * e, c + f)

    __radd__ = __add__

    def __sub__(self, other):
        if isinstance(other, Real):
            other = RAN(0, 1, other)
        if not isinstance(other, RAN):
            return NotImplemented
        return self + RAN(-other.a, other.b, -other.c)

    def __rsub__(self, other):
        if isinstance(other, Real):
            other = RAN(0, 1, other)
        return other - self

    def __neg__(self):
        return RAN(-self.a, self.b, -self.c)

    def __mul__(self, other):
        if isinstance(other, Real):
            other = RAN(0, 1, other)
        if not isinstance(other, RAN):
            return NotImplemented
        a, b, c = self.a, self.b, self.c
        d, e, f = other.a, other.b, other.c
        # (a/b + c)(d/e + f) = (ad + afe + cbd)/(be) + cf
        return RAN(a * d + a * f * e + c * b * d, b * e, c * f)

    __rmul__ = __mul__

    def __truediv__(self, other):
        if isinstance(other, Real):
            other = RAN(0, 1, other)
        if not isinstance(other, RAN):
            return NotImplemented
        a, b, c = self.a, self.b, self.c
        d, e, f = other.a, other.b, other.c
        # (a/b + c) / (d/e + f) = e(a + bc) / (b(d + ef))
        return RAN(e * (a + b * c), b * (d + e * f), 0)

    def __rtruediv__(self, other):
        if isinstance(other, Real):
            other = RAN(0, 1, other)
        return other / self

    def __eq__(self, other):
        if isinstance(other, RAN):
            return self.collapse() == other.collapse()
        if isinstance(other, Real):
            return self.collapse() == other
        return NotImplemented

    def simplify(self):
        """
        Partial collapse: if a and b are integers, reduce the fraction
        and move any whole part into c. This is still exact for integer
        parameters.
        """
        if isinstance(self.a, int) and isinstance(self.b, int):
            g = gcd(self.a, self.b)
            a = self.a // g
            b = self.b // g
            if b < 0:
                a, b = -a, -b
            q = a // b
            r = a - q * b
            return RAN(r, b, self.c + q)
        return self


# Demo: delayed collapse preserves accuracy
if __name__ == "__main__":
    # Example 1: large offset cancellation
    x = RAN(1, 1_000_000, 1_000_000)   # 1000000 + 1/1000000
    y = RAN(0, 1, 1_000_000)           # 1000000
    diff = x - y
    print("RAN subtraction:", diff)
    print("Collapsed:      ", diff.collapse())
    print("Direct float:   ", (1_000_000 + 1 / 1_000_000) - 1_000_000)

    # Example 2: three tenths
    t = RAN(1, 10, 0)
    sum_t = t + t + t
    print("\nRAN sum:        ", sum_t)
    print("Collapsed:      ", sum_t.collapse())
    print("Direct float:   ", 0.1 + 0.1 + 0.1)

    # Example 3: three thirds with integer parameters
    third = RAN(1, 3, 0)
    whole = third + third + third
    print("\nRAN 1/3 * 3:    ", whole)
    print("Collapsed:      ", whole.collapse())

    # Example 4: mixed float/integer parameters
    x = RAN(1.0, 3.0, 1_000_000.0)
    y = RAN(1.0, 3.0, 1_000_000.0)
    z = x - y
    print("\nMixed RAN:      ", z)
    print("Collapsed:      ", z.collapse())