pythonic-peano-arithmetic 0.3.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- peano/__init__.py +66 -0
- peano/algebraic_root.py +180 -0
- peano/integer.py +345 -0
- peano/natural_number.py +340 -0
- peano/polynomial.py +431 -0
- peano/py.typed +1 -0
- peano/rational.py +295 -0
- peano/utils.py +189 -0
- pythonic_peano_arithmetic-0.3.0.dist-info/METADATA +240 -0
- pythonic_peano_arithmetic-0.3.0.dist-info/RECORD +12 -0
- pythonic_peano_arithmetic-0.3.0.dist-info/WHEEL +4 -0
- pythonic_peano_arithmetic-0.3.0.dist-info/licenses/LICENSE +21 -0
peano/__init__.py
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Public API from Peano arithmetic through algebraic-root approximation."""
|
|
2
|
+
|
|
3
|
+
from .algebraic_root import (
|
|
4
|
+
AlgebraicRoot,
|
|
5
|
+
RationalInterval,
|
|
6
|
+
algebraic_root,
|
|
7
|
+
)
|
|
8
|
+
from .integer import Z_MINUS_ONE, Z_ONE, Z_ZERO, Integer, integer, n2z
|
|
9
|
+
from .natural_number import N_ONE, N_ZERO, NaturalNumber, natural_number, successor
|
|
10
|
+
from .polynomial import (
|
|
11
|
+
P_ONE,
|
|
12
|
+
P_ZERO,
|
|
13
|
+
Polynomial,
|
|
14
|
+
count_real_roots,
|
|
15
|
+
n2p,
|
|
16
|
+
polynomial,
|
|
17
|
+
r2p,
|
|
18
|
+
sign_variations,
|
|
19
|
+
sturm_sequence,
|
|
20
|
+
z2p,
|
|
21
|
+
)
|
|
22
|
+
from .rational import (
|
|
23
|
+
Q_MINUS_ONE,
|
|
24
|
+
Q_ONE,
|
|
25
|
+
Q_ZERO,
|
|
26
|
+
Rational,
|
|
27
|
+
n2r,
|
|
28
|
+
rational,
|
|
29
|
+
z2r,
|
|
30
|
+
)
|
|
31
|
+
from .utils import config_log
|
|
32
|
+
|
|
33
|
+
__all__ = [
|
|
34
|
+
"NaturalNumber",
|
|
35
|
+
"natural_number",
|
|
36
|
+
"successor",
|
|
37
|
+
"N_ZERO",
|
|
38
|
+
"N_ONE",
|
|
39
|
+
"Integer",
|
|
40
|
+
"integer",
|
|
41
|
+
"n2z",
|
|
42
|
+
"Z_ZERO",
|
|
43
|
+
"Z_ONE",
|
|
44
|
+
"Z_MINUS_ONE",
|
|
45
|
+
"Rational",
|
|
46
|
+
"rational",
|
|
47
|
+
"n2r",
|
|
48
|
+
"z2r",
|
|
49
|
+
"Q_ZERO",
|
|
50
|
+
"Q_ONE",
|
|
51
|
+
"Q_MINUS_ONE",
|
|
52
|
+
"Polynomial",
|
|
53
|
+
"polynomial",
|
|
54
|
+
"n2p",
|
|
55
|
+
"z2p",
|
|
56
|
+
"r2p",
|
|
57
|
+
"P_ZERO",
|
|
58
|
+
"P_ONE",
|
|
59
|
+
"sturm_sequence",
|
|
60
|
+
"sign_variations",
|
|
61
|
+
"count_real_roots",
|
|
62
|
+
"RationalInterval",
|
|
63
|
+
"AlgebraicRoot",
|
|
64
|
+
"algebraic_root",
|
|
65
|
+
"config_log",
|
|
66
|
+
]
|
peano/algebraic_root.py
ADDED
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
"""Observe non-rational roots through shrinking rational intervals."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from fractions import Fraction
|
|
7
|
+
|
|
8
|
+
from .natural_number import NaturalNumber
|
|
9
|
+
from .polynomial import Polynomial, count_real_roots
|
|
10
|
+
from .rational import Rational, rational
|
|
11
|
+
from .utils import LogMessage, localized, log
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@dataclass(frozen=True, slots=True)
|
|
15
|
+
class RationalInterval:
|
|
16
|
+
"""A closed interval with rational endpoints.
|
|
17
|
+
|
|
18
|
+
Equal endpoints are allowed and represent one exact rational point.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
lower: Rational
|
|
22
|
+
upper: Rational
|
|
23
|
+
|
|
24
|
+
def __post_init__(self) -> None:
|
|
25
|
+
if not isinstance(self.lower, Rational) or not isinstance(self.upper, Rational):
|
|
26
|
+
raise TypeError("interval endpoints must be Rational values")
|
|
27
|
+
lower_fraction = _as_fraction(self.lower)
|
|
28
|
+
upper_fraction = _as_fraction(self.upper)
|
|
29
|
+
if lower_fraction > upper_fraction:
|
|
30
|
+
raise ValueError("lower must be less than or equal to upper")
|
|
31
|
+
object.__setattr__(self, "lower", _from_fraction(lower_fraction))
|
|
32
|
+
object.__setattr__(self, "upper", _from_fraction(upper_fraction))
|
|
33
|
+
|
|
34
|
+
@property
|
|
35
|
+
def width(self) -> Rational:
|
|
36
|
+
return _from_fraction(_as_fraction(self.upper) - _as_fraction(self.lower))
|
|
37
|
+
|
|
38
|
+
@property
|
|
39
|
+
def midpoint(self) -> Rational:
|
|
40
|
+
return _from_fraction((_as_fraction(self.lower) + _as_fraction(self.upper)) / 2)
|
|
41
|
+
|
|
42
|
+
@property
|
|
43
|
+
def is_point(self) -> bool:
|
|
44
|
+
return _as_fraction(self.lower) == _as_fraction(self.upper)
|
|
45
|
+
|
|
46
|
+
def contains(self, value: Rational) -> bool:
|
|
47
|
+
point = _as_fraction(value)
|
|
48
|
+
return _as_fraction(self.lower) <= point <= _as_fraction(self.upper)
|
|
49
|
+
|
|
50
|
+
def __str__(self) -> str:
|
|
51
|
+
return f"[{self.lower}, {self.upper}]"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True, slots=True, eq=False)
|
|
55
|
+
class AlgebraicRoot:
|
|
56
|
+
"""Identify one real polynomial root by an isolating interval.
|
|
57
|
+
|
|
58
|
+
This educational object is not a complete real-number type. It deliberately
|
|
59
|
+
omits arithmetic and general equality and focuses on shrinking rational
|
|
60
|
+
intervals while preserving one root.
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
polynomial: Polynomial
|
|
64
|
+
interval: RationalInterval
|
|
65
|
+
|
|
66
|
+
def __post_init__(self) -> None:
|
|
67
|
+
if not isinstance(self.polynomial, Polynomial):
|
|
68
|
+
raise TypeError("polynomial must be a Polynomial")
|
|
69
|
+
if not isinstance(self.interval, RationalInterval):
|
|
70
|
+
raise TypeError("interval must be a RationalInterval")
|
|
71
|
+
if self.polynomial.degree <= 0:
|
|
72
|
+
raise ValueError("a root-defining polynomial must have positive degree")
|
|
73
|
+
if self.interval.is_point:
|
|
74
|
+
raise ValueError("the initial interval must have positive width")
|
|
75
|
+
|
|
76
|
+
lower_sign = self.polynomial.sign_at(self.interval.lower)
|
|
77
|
+
upper_sign = self.polynomial.sign_at(self.interval.upper)
|
|
78
|
+
if lower_sign == 0 or upper_sign == 0:
|
|
79
|
+
raise ValueError("initial interval endpoints cannot be roots")
|
|
80
|
+
if lower_sign == upper_sign:
|
|
81
|
+
raise ValueError("the polynomial must change sign across the endpoints")
|
|
82
|
+
|
|
83
|
+
number_of_roots = count_real_roots(
|
|
84
|
+
self.polynomial,
|
|
85
|
+
self.interval.lower,
|
|
86
|
+
self.interval.upper,
|
|
87
|
+
)
|
|
88
|
+
if number_of_roots != 1:
|
|
89
|
+
raise ValueError(
|
|
90
|
+
"the initial interval must contain exactly one distinct real root "
|
|
91
|
+
f"(found {number_of_roots})"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def approximate(self, steps: int | NaturalNumber) -> RationalInterval:
|
|
95
|
+
"""Bisect ``steps`` times and return a closed interval containing the root."""
|
|
96
|
+
|
|
97
|
+
count = _step_count(steps)
|
|
98
|
+
interval = self.interval
|
|
99
|
+
for _ in range(count):
|
|
100
|
+
if interval.is_point:
|
|
101
|
+
break
|
|
102
|
+
interval = _bisect(self.polynomial, interval)
|
|
103
|
+
return interval
|
|
104
|
+
|
|
105
|
+
def trace(self, steps: int | NaturalNumber) -> tuple[RationalInterval, ...]:
|
|
106
|
+
"""Return every bisection interval, including the initial interval."""
|
|
107
|
+
|
|
108
|
+
count = _step_count(steps)
|
|
109
|
+
intervals = [self.interval]
|
|
110
|
+
for _ in range(count):
|
|
111
|
+
if intervals[-1].is_point:
|
|
112
|
+
break
|
|
113
|
+
intervals.append(_bisect(self.polynomial, intervals[-1]))
|
|
114
|
+
return tuple(intervals)
|
|
115
|
+
|
|
116
|
+
def __repr__(self) -> str:
|
|
117
|
+
return f"<AlgebraicRoot({self.polynomial!r}, {self.interval})>"
|
|
118
|
+
|
|
119
|
+
def __str__(self) -> str:
|
|
120
|
+
return f"root of {self.polynomial} in {self.interval}"
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
@log(log_level=41)
|
|
124
|
+
def _bisect(
|
|
125
|
+
polynomial_value: Polynomial,
|
|
126
|
+
interval: RationalInterval,
|
|
127
|
+
) -> tuple[RationalInterval, LogMessage]:
|
|
128
|
+
midpoint = interval.midpoint
|
|
129
|
+
midpoint_sign = polynomial_value.sign_at(midpoint)
|
|
130
|
+
|
|
131
|
+
if midpoint_sign == 0:
|
|
132
|
+
result = RationalInterval(midpoint, midpoint)
|
|
133
|
+
return (
|
|
134
|
+
result,
|
|
135
|
+
lambda: localized(
|
|
136
|
+
f"{polynomial_value!r}: midpoint {midpoint!r} is a root",
|
|
137
|
+
f"{polynomial_value!r}: 中点 {midpoint!r} は根",
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
|
|
141
|
+
lower_sign = polynomial_value.sign_at(interval.lower)
|
|
142
|
+
if lower_sign != midpoint_sign:
|
|
143
|
+
result = RationalInterval(interval.lower, midpoint)
|
|
144
|
+
else:
|
|
145
|
+
result = RationalInterval(midpoint, interval.upper)
|
|
146
|
+
return (
|
|
147
|
+
result,
|
|
148
|
+
lambda: f"{polynomial_value!r}: {interval} -> {result}",
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def algebraic_root(
|
|
153
|
+
polynomial_value: Polynomial,
|
|
154
|
+
lower: tuple[int, int],
|
|
155
|
+
upper: tuple[int, int],
|
|
156
|
+
) -> AlgebraicRoot:
|
|
157
|
+
"""Construct an algebraic root from Python integer endpoint pairs."""
|
|
158
|
+
|
|
159
|
+
return AlgebraicRoot(
|
|
160
|
+
polynomial_value,
|
|
161
|
+
RationalInterval(rational(*lower), rational(*upper)),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def _step_count(value: int | NaturalNumber) -> int:
|
|
166
|
+
if isinstance(value, NaturalNumber):
|
|
167
|
+
return int(value)
|
|
168
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
169
|
+
raise TypeError("steps must be an int or NaturalNumber")
|
|
170
|
+
if value < 0:
|
|
171
|
+
raise ValueError("steps must be non-negative")
|
|
172
|
+
return value
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def _as_fraction(value: Rational) -> Fraction:
|
|
176
|
+
return Fraction(int(value.p), int(value.q))
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def _from_fraction(value: Fraction) -> Rational:
|
|
180
|
+
return rational(value.numerator, value.denominator)
|
peano/integer.py
ADDED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
"""Construct integers as equivalence classes of natural-number differences."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from functools import total_ordering
|
|
7
|
+
from typing import TYPE_CHECKING, cast
|
|
8
|
+
|
|
9
|
+
from .natural_number import N_ONE, N_ZERO, NaturalNumber, natural_number
|
|
10
|
+
from .utils import LogMessage, log
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from .rational import Rational
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@total_ordering
|
|
17
|
+
@dataclass(frozen=True, slots=True, eq=False, repr=False)
|
|
18
|
+
class Integer:
|
|
19
|
+
"""Represent the difference ``a - b`` by a pair of natural numbers.
|
|
20
|
+
|
|
21
|
+
``(a, b) ~ (c, d)`` is defined by ``a + d = b + c``. Representations are
|
|
22
|
+
deliberately not normalized automatically, so equivalent representatives
|
|
23
|
+
remain observable.
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
a: NaturalNumber
|
|
27
|
+
b: NaturalNumber
|
|
28
|
+
|
|
29
|
+
def __post_init__(self) -> None:
|
|
30
|
+
if not isinstance(self.a, NaturalNumber) or not isinstance(
|
|
31
|
+
self.b, NaturalNumber
|
|
32
|
+
):
|
|
33
|
+
raise TypeError("Integer.a and Integer.b must be NaturalNumber values")
|
|
34
|
+
|
|
35
|
+
def __repr__(self) -> str:
|
|
36
|
+
return f"<Z({int(self.a)},{int(self.b)})>"
|
|
37
|
+
|
|
38
|
+
def __str__(self) -> str:
|
|
39
|
+
return str(int(self))
|
|
40
|
+
|
|
41
|
+
def __int__(self) -> int:
|
|
42
|
+
return int(self.a) - int(self.b)
|
|
43
|
+
|
|
44
|
+
def __abs__(self) -> NaturalNumber:
|
|
45
|
+
if self.a >= self.b:
|
|
46
|
+
return self.a - self.b
|
|
47
|
+
return self.b - self.a
|
|
48
|
+
|
|
49
|
+
@log(log_level=11)
|
|
50
|
+
def __eq__(self, other: object) -> tuple[bool, LogMessage]:
|
|
51
|
+
converted = _coerce_integer(other)
|
|
52
|
+
if converted is None:
|
|
53
|
+
return (
|
|
54
|
+
cast(bool, NotImplemented),
|
|
55
|
+
lambda: f"{self!r} == {other!r} = NotImplemented",
|
|
56
|
+
)
|
|
57
|
+
result = self.a + converted.b == self.b + converted.a
|
|
58
|
+
return (
|
|
59
|
+
result,
|
|
60
|
+
lambda: (
|
|
61
|
+
f"{self!r} == {converted!r} ⇔ "
|
|
62
|
+
f"{self.a!r} + {converted.b!r} == "
|
|
63
|
+
f"{self.b!r} + {converted.a!r}"
|
|
64
|
+
),
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
@log(log_level=12)
|
|
68
|
+
def __lt__(self, other: object) -> tuple[bool, LogMessage]:
|
|
69
|
+
converted = _coerce_integer(other)
|
|
70
|
+
if converted is None:
|
|
71
|
+
return (
|
|
72
|
+
cast(bool, NotImplemented),
|
|
73
|
+
lambda: f"{self!r} < {other!r} = NotImplemented",
|
|
74
|
+
)
|
|
75
|
+
result = self.a + converted.b < self.b + converted.a
|
|
76
|
+
return (
|
|
77
|
+
result,
|
|
78
|
+
lambda: (
|
|
79
|
+
f"{self!r} < {converted!r} ⇔ "
|
|
80
|
+
f"{self.a!r} + {converted.b!r} < "
|
|
81
|
+
f"{self.b!r} + {converted.a!r}"
|
|
82
|
+
),
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
@log(log_level=12)
|
|
86
|
+
def __le__(self, other: object) -> tuple[bool, LogMessage]:
|
|
87
|
+
converted = _coerce_integer(other)
|
|
88
|
+
if converted is None:
|
|
89
|
+
return (
|
|
90
|
+
cast(bool, NotImplemented),
|
|
91
|
+
lambda: f"{self!r} <= {other!r} = NotImplemented",
|
|
92
|
+
)
|
|
93
|
+
result = self.a + converted.b <= self.b + converted.a
|
|
94
|
+
return (
|
|
95
|
+
result,
|
|
96
|
+
lambda: (
|
|
97
|
+
f"{self!r} <= {converted!r} ⇔ "
|
|
98
|
+
f"{self.a!r} + {converted.b!r} <= "
|
|
99
|
+
f"{self.b!r} + {converted.a!r}"
|
|
100
|
+
),
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
@log(log_level=14)
|
|
104
|
+
def __add__(self, other: object) -> tuple[Integer, LogMessage]:
|
|
105
|
+
converted = _coerce_integer(other)
|
|
106
|
+
if converted is None:
|
|
107
|
+
return (
|
|
108
|
+
cast(Integer, NotImplemented),
|
|
109
|
+
lambda: f"{self!r} + {other!r} = NotImplemented",
|
|
110
|
+
)
|
|
111
|
+
result = Integer(self.a + converted.a, self.b + converted.b)
|
|
112
|
+
return (
|
|
113
|
+
result,
|
|
114
|
+
lambda: (
|
|
115
|
+
f"{self!r} + {converted!r} = "
|
|
116
|
+
f"({self.a!r} + {converted.a!r}, "
|
|
117
|
+
f"{self.b!r} + {converted.b!r})"
|
|
118
|
+
),
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
def __radd__(self, other: object) -> Integer:
|
|
122
|
+
return self + other
|
|
123
|
+
|
|
124
|
+
@log(log_level=14)
|
|
125
|
+
def __neg__(self) -> tuple[Integer, LogMessage]:
|
|
126
|
+
return (
|
|
127
|
+
Integer(self.b, self.a),
|
|
128
|
+
lambda: f"-{self!r} = ({self.b!r}, {self.a!r})",
|
|
129
|
+
)
|
|
130
|
+
|
|
131
|
+
@log(log_level=14)
|
|
132
|
+
def __sub__(self, other: object) -> tuple[Integer, LogMessage]:
|
|
133
|
+
converted = _coerce_integer(other)
|
|
134
|
+
if converted is None:
|
|
135
|
+
return (
|
|
136
|
+
cast(Integer, NotImplemented),
|
|
137
|
+
lambda: f"{self!r} - {other!r} = NotImplemented",
|
|
138
|
+
)
|
|
139
|
+
return (
|
|
140
|
+
self + -converted,
|
|
141
|
+
lambda: f"{self!r} - {converted!r} = {self!r} + (-{converted!r})",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
def __rsub__(self, other: object) -> Integer:
|
|
145
|
+
converted = _coerce_integer(other)
|
|
146
|
+
if converted is None:
|
|
147
|
+
return cast(Integer, NotImplemented)
|
|
148
|
+
return converted - self
|
|
149
|
+
|
|
150
|
+
@log(log_level=15)
|
|
151
|
+
def __mul__(self, other: object) -> tuple[Integer, LogMessage]:
|
|
152
|
+
converted = _coerce_integer(other)
|
|
153
|
+
if converted is None:
|
|
154
|
+
return (
|
|
155
|
+
cast(Integer, NotImplemented),
|
|
156
|
+
lambda: f"{self!r} * {other!r} = NotImplemented",
|
|
157
|
+
)
|
|
158
|
+
result = Integer(
|
|
159
|
+
self.a * converted.a + self.b * converted.b,
|
|
160
|
+
self.a * converted.b + self.b * converted.a,
|
|
161
|
+
)
|
|
162
|
+
return (
|
|
163
|
+
result,
|
|
164
|
+
lambda: (
|
|
165
|
+
f"{self!r} * {converted!r} = "
|
|
166
|
+
f"({self.a!r} * {converted.a!r} + "
|
|
167
|
+
f"{self.b!r} * {converted.b!r}, "
|
|
168
|
+
f"{self.a!r} * {converted.b!r} + "
|
|
169
|
+
f"{self.b!r} * {converted.a!r})"
|
|
170
|
+
),
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def __rmul__(self, other: object) -> Integer:
|
|
174
|
+
return self * other
|
|
175
|
+
|
|
176
|
+
@log(log_level=15)
|
|
177
|
+
def __truediv__(self, other: object) -> tuple[Rational, LogMessage]:
|
|
178
|
+
converted = _coerce_integer(other)
|
|
179
|
+
if converted is None:
|
|
180
|
+
return (
|
|
181
|
+
cast("Rational", NotImplemented),
|
|
182
|
+
lambda: f"{self!r} / {other!r} = NotImplemented",
|
|
183
|
+
)
|
|
184
|
+
from .rational import Rational
|
|
185
|
+
|
|
186
|
+
result = Rational(self, converted)
|
|
187
|
+
return result, lambda: f"{self!r} / {converted!r} = {result!r}"
|
|
188
|
+
|
|
189
|
+
def __rtruediv__(self, other: object) -> Rational:
|
|
190
|
+
converted = _coerce_integer(other)
|
|
191
|
+
if converted is None:
|
|
192
|
+
return cast("Rational", NotImplemented)
|
|
193
|
+
return converted / self
|
|
194
|
+
|
|
195
|
+
def _divmod(self, divisor: Integer) -> tuple[Integer, Integer]:
|
|
196
|
+
if not divisor:
|
|
197
|
+
raise ZeroDivisionError("division by zero")
|
|
198
|
+
|
|
199
|
+
quotient_magnitude, remainder_magnitude = divmod(abs(self), abs(divisor))
|
|
200
|
+
same_sign = (self < Z_ZERO) == (divisor < Z_ZERO)
|
|
201
|
+
|
|
202
|
+
if same_sign:
|
|
203
|
+
quotient = Integer(quotient_magnitude, N_ZERO)
|
|
204
|
+
remainder = Integer(remainder_magnitude, N_ZERO)
|
|
205
|
+
elif not remainder_magnitude:
|
|
206
|
+
quotient = Integer(N_ZERO, quotient_magnitude)
|
|
207
|
+
remainder = Z_ZERO
|
|
208
|
+
else:
|
|
209
|
+
quotient = Integer(N_ZERO, quotient_magnitude + N_ONE)
|
|
210
|
+
remainder = Integer(abs(divisor) - remainder_magnitude, N_ZERO)
|
|
211
|
+
|
|
212
|
+
if divisor < Z_ZERO:
|
|
213
|
+
remainder = -remainder
|
|
214
|
+
return quotient, remainder
|
|
215
|
+
|
|
216
|
+
@log(log_level=15)
|
|
217
|
+
def __floordiv__(self, other: object) -> tuple[Integer, LogMessage]:
|
|
218
|
+
converted = _coerce_integer(other)
|
|
219
|
+
if converted is None:
|
|
220
|
+
return (
|
|
221
|
+
cast(Integer, NotImplemented),
|
|
222
|
+
lambda: f"{self!r} // {other!r} = NotImplemented",
|
|
223
|
+
)
|
|
224
|
+
quotient, _ = self._divmod(converted)
|
|
225
|
+
return (
|
|
226
|
+
quotient,
|
|
227
|
+
lambda: (
|
|
228
|
+
f"{self!r} = ({quotient!r}) * {converted!r} + "
|
|
229
|
+
f"({self!r} % {converted!r})"
|
|
230
|
+
),
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def __rfloordiv__(self, other: object) -> Integer:
|
|
234
|
+
converted = _coerce_integer(other)
|
|
235
|
+
if converted is None:
|
|
236
|
+
return cast(Integer, NotImplemented)
|
|
237
|
+
return converted // self
|
|
238
|
+
|
|
239
|
+
@log(log_level=15)
|
|
240
|
+
def __mod__(self, other: object) -> tuple[Integer, LogMessage]:
|
|
241
|
+
converted = _coerce_integer(other)
|
|
242
|
+
if converted is None:
|
|
243
|
+
return (
|
|
244
|
+
cast(Integer, NotImplemented),
|
|
245
|
+
lambda: f"{self!r} % {other!r} = NotImplemented",
|
|
246
|
+
)
|
|
247
|
+
_, remainder = self._divmod(converted)
|
|
248
|
+
return (
|
|
249
|
+
remainder,
|
|
250
|
+
lambda: (
|
|
251
|
+
f"{self!r} % {converted!r} = {remainder!r}, "
|
|
252
|
+
f"sign(remainder) = sign({converted!r})"
|
|
253
|
+
),
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
def __rmod__(self, other: object) -> Integer:
|
|
257
|
+
converted = _coerce_integer(other)
|
|
258
|
+
if converted is None:
|
|
259
|
+
return cast(Integer, NotImplemented)
|
|
260
|
+
return converted % self
|
|
261
|
+
|
|
262
|
+
def __divmod__(self, other: object) -> tuple[Integer, Integer]:
|
|
263
|
+
converted = _coerce_integer(other)
|
|
264
|
+
if converted is None:
|
|
265
|
+
return cast(tuple[Integer, Integer], NotImplemented)
|
|
266
|
+
return self._divmod(converted)
|
|
267
|
+
|
|
268
|
+
def __rdivmod__(self, other: object) -> tuple[Integer, Integer]:
|
|
269
|
+
converted = _coerce_integer(other)
|
|
270
|
+
if converted is None:
|
|
271
|
+
return cast(tuple[Integer, Integer], NotImplemented)
|
|
272
|
+
return converted._divmod(self)
|
|
273
|
+
|
|
274
|
+
@log(log_level=16)
|
|
275
|
+
def __pow__(self, exponent: object) -> tuple[Integer, LogMessage]:
|
|
276
|
+
if not isinstance(exponent, NaturalNumber):
|
|
277
|
+
return (
|
|
278
|
+
cast(Integer, NotImplemented),
|
|
279
|
+
lambda: f"{self!r} ** {exponent!r} = NotImplemented",
|
|
280
|
+
)
|
|
281
|
+
if exponent == N_ZERO:
|
|
282
|
+
return Z_ONE, lambda: f"{self!r} ** {exponent!r} = {Z_ONE!r}"
|
|
283
|
+
return (
|
|
284
|
+
(self ** (exponent - N_ONE)) * self,
|
|
285
|
+
lambda: (
|
|
286
|
+
f"{self!r} ** {exponent!r} = "
|
|
287
|
+
f"({self!r} ** ({exponent!r} - {N_ONE!r})) * {self!r}"
|
|
288
|
+
),
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
def __bool__(self) -> bool:
|
|
292
|
+
return self.a != self.b
|
|
293
|
+
|
|
294
|
+
def __hash__(self) -> int:
|
|
295
|
+
return hash(int(self))
|
|
296
|
+
|
|
297
|
+
def __pos__(self) -> Integer:
|
|
298
|
+
return self
|
|
299
|
+
|
|
300
|
+
def normalize(self) -> Integer:
|
|
301
|
+
"""Return the representative ``(n, 0)`` or ``(0, n)``."""
|
|
302
|
+
|
|
303
|
+
if self.a >= self.b:
|
|
304
|
+
return Integer(self.a - self.b, N_ZERO)
|
|
305
|
+
return Integer(N_ZERO, self.b - self.a)
|
|
306
|
+
|
|
307
|
+
|
|
308
|
+
def integer(value: int) -> Integer:
|
|
309
|
+
"""Construct the canonical difference representation of a Python integer."""
|
|
310
|
+
|
|
311
|
+
if isinstance(value, bool) or not isinstance(value, int):
|
|
312
|
+
raise TypeError("only int values can be converted to Integer")
|
|
313
|
+
if value >= 0:
|
|
314
|
+
return Integer(natural_number(value), N_ZERO)
|
|
315
|
+
return Integer(N_ZERO, natural_number(-value))
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def n2z(value: NaturalNumber) -> Integer:
|
|
319
|
+
"""Embed a natural number into the integers."""
|
|
320
|
+
|
|
321
|
+
if not isinstance(value, NaturalNumber):
|
|
322
|
+
raise TypeError("n2z expects a NaturalNumber")
|
|
323
|
+
return Integer(value, N_ZERO)
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
def _coerce_integer(value: object) -> Integer | None:
|
|
327
|
+
if isinstance(value, NaturalNumber):
|
|
328
|
+
return n2z(value)
|
|
329
|
+
if isinstance(value, Integer):
|
|
330
|
+
return value
|
|
331
|
+
return None
|
|
332
|
+
|
|
333
|
+
|
|
334
|
+
def cast2z(value: object) -> Integer:
|
|
335
|
+
"""Coerce a ``NaturalNumber`` or ``Integer`` to ``Integer``."""
|
|
336
|
+
|
|
337
|
+
converted = _coerce_integer(value)
|
|
338
|
+
if converted is None:
|
|
339
|
+
raise TypeError(f"{value!r} is not an Integer")
|
|
340
|
+
return converted
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
Z_ZERO = Integer(N_ZERO, N_ZERO)
|
|
344
|
+
Z_ONE = Integer(N_ONE, N_ZERO)
|
|
345
|
+
Z_MINUS_ONE = Integer(N_ZERO, N_ONE)
|