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()
|