File size: 4,224 Bytes
767a051
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
"""Exact canonical rational numbers used by interval-event certificates."""
from __future__ import annotations

from dataclasses import dataclass
from fractions import Fraction
from math import gcd
from typing import Any


@dataclass(frozen=True, slots=True)
class Q:
    numerator: int
    denominator: int = 1

    def __post_init__(self) -> None:
        n = int(self.numerator)
        d = int(self.denominator)
        if d == 0:
            raise ValueError("rational denominator must be nonzero")
        if d < 0:
            n, d = -n, -d
        g = gcd(abs(n), d)
        object.__setattr__(self, "numerator", n // g)
        object.__setattr__(self, "denominator", d // g)

    @classmethod
    def from_value(cls, value: Any) -> "Q":
        if isinstance(value, cls):
            return value
        if isinstance(value, Fraction):
            return cls(value.numerator, value.denominator)
        if isinstance(value, bool):
            return cls(int(value), 1)
        if isinstance(value, int):
            return cls(value, 1)
        if isinstance(value, str):
            text = value.strip()
            if "/" in text:
                left, right = text.split("/", 1)
                return cls(int(left, 0), int(right, 0))
            return cls(int(text, 0), 1)
        if isinstance(value, dict):
            if set(value) >= {"num", "den"}:
                return cls(int(value["num"]), int(value["den"]))
            if set(value) >= {"numerator", "denominator"}:
                return cls(int(value["numerator"]), int(value["denominator"]))
        raise TypeError(f"cannot convert {type(value).__name__} to Q")

    def as_fraction(self) -> Fraction:
        return Fraction(self.numerator, self.denominator)

    def to_record(self) -> dict[str, int]:
        return {"num": self.numerator, "den": self.denominator}

    def to_text(self) -> str:
        if self.denominator == 1:
            return str(self.numerator)
        return f"{self.numerator}/{self.denominator}"

    def sign(self) -> int:
        return (self.numerator > 0) - (self.numerator < 0)

    def is_integer(self) -> bool:
        return self.denominator == 1

    def as_integer(self) -> int:
        if self.denominator != 1:
            raise ValueError(f"{self.to_text()} is not an integer")
        return self.numerator

    def floor(self) -> int:
        return self.numerator // self.denominator

    def ceil(self) -> int:
        return -((-self.numerator) // self.denominator)

    def __neg__(self) -> "Q":
        return Q(-self.numerator, self.denominator)

    def __abs__(self) -> "Q":
        return Q(abs(self.numerator), self.denominator)

    def __add__(self, other: Any) -> "Q":
        b = Q.from_value(other)
        return Q(self.numerator * b.denominator + b.numerator * self.denominator,
                 self.denominator * b.denominator)

    def __radd__(self, other: Any) -> "Q":
        return self + other

    def __sub__(self, other: Any) -> "Q":
        return self + (-Q.from_value(other))

    def __rsub__(self, other: Any) -> "Q":
        return Q.from_value(other) - self

    def __mul__(self, other: Any) -> "Q":
        b = Q.from_value(other)
        return Q(self.numerator * b.numerator, self.denominator * b.denominator)

    def __rmul__(self, other: Any) -> "Q":
        return self * other

    def __truediv__(self, other: Any) -> "Q":
        b = Q.from_value(other)
        if b.numerator == 0:
            raise ZeroDivisionError("rational division by zero")
        return Q(self.numerator * b.denominator, self.denominator * b.numerator)

    def __rtruediv__(self, other: Any) -> "Q":
        return Q.from_value(other) / self

    def __lt__(self, other: Any) -> bool:
        b = Q.from_value(other)
        return self.numerator * b.denominator < b.numerator * self.denominator

    def __le__(self, other: Any) -> bool:
        return self == Q.from_value(other) or self < other

    def __gt__(self, other: Any) -> bool:
        return not self <= other

    def __ge__(self, other: Any) -> bool:
        return not self < other

    def __int__(self) -> int:
        return self.as_integer()

    def __str__(self) -> str:
        return self.to_text()