daatypes 0.1.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.
- daatypes/__init__.py +75 -0
- daatypes/ball.py +22 -0
- daatypes/cf.py +79 -0
- daatypes/comp.py +49 -0
- daatypes/fixed.py +243 -0
- daatypes/float.py +232 -0
- daatypes/int.py +4 -0
- daatypes/interval.py +5 -0
- daatypes/monzo.py +286 -0
- daatypes/octo.py +29 -0
- daatypes/polar.py +10 -0
- daatypes/qint.py +4 -0
- daatypes/quat.py +60 -0
- daatypes/real.py +16 -0
- daatypes/uint.py +4 -0
- daatypes/uqint.py +4 -0
- daatypes/vector.py +33 -0
- daatypes-0.1.0.dist-info/METADATA +22 -0
- daatypes-0.1.0.dist-info/RECORD +21 -0
- daatypes-0.1.0.dist-info/WHEEL +4 -0
- daatypes-0.1.0.dist-info/licenses/LICENSE +21 -0
daatypes/__init__.py
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# hello! welcome to daatypes where i propose my own datatypes i find useful in my work
|
|
2
|
+
|
|
3
|
+
# unsigned integer
|
|
4
|
+
# represents the natural numbers starting from zero
|
|
5
|
+
# can also do saturating and modular arithmetic
|
|
6
|
+
from .uint import UInt
|
|
7
|
+
|
|
8
|
+
# 2's complement signed integer
|
|
9
|
+
# represents the integers
|
|
10
|
+
# can also do saturating and modular arithmetic
|
|
11
|
+
from .int import Int
|
|
12
|
+
|
|
13
|
+
# unsigned binary fixed point number format
|
|
14
|
+
# represents the non-negative binary rationals
|
|
15
|
+
# can also do saturating and modular arithmetic
|
|
16
|
+
from .uqint import UQInt
|
|
17
|
+
|
|
18
|
+
# signed binary fixed point number format
|
|
19
|
+
# represents the binary rationals
|
|
20
|
+
# can also do saturating and modular arithmetic
|
|
21
|
+
from .qint import QInt
|
|
22
|
+
|
|
23
|
+
# fixed point numbers
|
|
24
|
+
from .fixed import Fixed
|
|
25
|
+
|
|
26
|
+
# scientific notation in arbitrary radix
|
|
27
|
+
# represents the rational numbers
|
|
28
|
+
from .float import Float, ieee_binary, ieee_decimal, f16, f32, f64, f128, f256, d32, d64, d128
|
|
29
|
+
|
|
30
|
+
# a composed datatype
|
|
31
|
+
# two arrays of int primes[] and int exponents[]
|
|
32
|
+
# represents the rationals
|
|
33
|
+
# conducive to slow additive but fast multiplicative arithmetic
|
|
34
|
+
from .monzo import Monzo
|
|
35
|
+
|
|
36
|
+
# (simple)? continued fraction
|
|
37
|
+
from .cf import CF, SCF
|
|
38
|
+
|
|
39
|
+
# a composed datatype
|
|
40
|
+
# an ordered pair of (any lower, any upper)
|
|
41
|
+
# represents a closed-closed interval on the real number line
|
|
42
|
+
# conducive to fast tolerance-aware arithmetic
|
|
43
|
+
from .interval import Interval
|
|
44
|
+
|
|
45
|
+
# a composed datatype
|
|
46
|
+
# an ordered pair of (centre, +ve radius)
|
|
47
|
+
# represents a ball in an R-vector space
|
|
48
|
+
# conducive to tolerance-aware arithmetic with accurate tolerance
|
|
49
|
+
from .ball import Ball
|
|
50
|
+
|
|
51
|
+
# vectors of real numbers ------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
from .vector import Vector
|
|
54
|
+
|
|
55
|
+
# represents the real numbers
|
|
56
|
+
from .real import Real
|
|
57
|
+
|
|
58
|
+
# a composed datatype
|
|
59
|
+
# represents the complexes in cartesian form
|
|
60
|
+
from .comp import Comp
|
|
61
|
+
|
|
62
|
+
# a composed datatype
|
|
63
|
+
# represents the quaternions in cartesian form
|
|
64
|
+
from .quat import Quat
|
|
65
|
+
|
|
66
|
+
# a composed datatype
|
|
67
|
+
# represents the quaternions in cartesian form
|
|
68
|
+
from .octo import Octo
|
|
69
|
+
|
|
70
|
+
# a composed datatype
|
|
71
|
+
# an ordered pair of (qint magnitude, float angle) where angle is from real axis to imaginary axis
|
|
72
|
+
# represents the complex numbers
|
|
73
|
+
# conducive to slow additive but fast multiplicative arithmetic, and uniform angle precision
|
|
74
|
+
from .polar import Polar
|
|
75
|
+
|
daatypes/ball.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
from numbers import Number, Real
|
|
2
|
+
import daacorations
|
|
3
|
+
from dataclasses import dataclass, Field
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Ball(Number):
|
|
7
|
+
'a composed datatype representing an axially symmetric (i think) p-norm ball in an R-vector space'
|
|
8
|
+
|
|
9
|
+
midpoint: Number
|
|
10
|
+
radius: Real
|
|
11
|
+
power: Real
|
|
12
|
+
|
|
13
|
+
def __post_init__(self):
|
|
14
|
+
if self.radius < 0:
|
|
15
|
+
raise ValueError(f'{self.radius=} must be non-negative')
|
|
16
|
+
if self.power < 0:
|
|
17
|
+
raise ValueError(f'{self.radius=} must be non-negative')
|
|
18
|
+
|
|
19
|
+
__repr__ = daacorations.pretty_repr
|
|
20
|
+
|
|
21
|
+
class EBall:
|
|
22
|
+
'a Ball but with p = 2 (euclidean), thus representing a hypersphere'
|
daatypes/cf.py
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import math, builtins
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from numbers import Number, Rational, Integral
|
|
4
|
+
from collections.abc import Sequence, MutableSequence
|
|
5
|
+
from typing import Literal
|
|
6
|
+
from daacorations import pretty_repr
|
|
7
|
+
from fractions import Fraction
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class CF(Number):
|
|
11
|
+
'a continued fraction'
|
|
12
|
+
numerators: Sequence
|
|
13
|
+
denominators: Sequence
|
|
14
|
+
signum: Literal[-1, 0, +1] = +1
|
|
15
|
+
numerators_repeat: None | int = None
|
|
16
|
+
denominators_repeat: None | int = None
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def integral(self) -> Integral:
|
|
20
|
+
return self.denominators[0]
|
|
21
|
+
|
|
22
|
+
@property
|
|
23
|
+
def fractional(self) -> Rational:
|
|
24
|
+
...
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
sign = {-1: '-', 0: '', +1: '+'}[self.signum]
|
|
28
|
+
return f'{sign}({self.denominators[0]} + {" + ".join(f"{n}/{d}"for n, d in zip(self.numerators[1:], self.denominators[1:]))})'
|
|
29
|
+
|
|
30
|
+
__repr__ = pretty_repr
|
|
31
|
+
|
|
32
|
+
def quotrem_nearest_even(n, d):
|
|
33
|
+
q = round(n / d)
|
|
34
|
+
return q, n - q * d
|
|
35
|
+
|
|
36
|
+
class SCF(CF, Sequence):
|
|
37
|
+
'a simple continued fraction with a Sequence interface'
|
|
38
|
+
def __init__(self, denominators: Sequence, signum = +1):
|
|
39
|
+
super().__init__([0, 1], denominators, signum, numerators_repeat = 1)
|
|
40
|
+
|
|
41
|
+
# Sequence methods
|
|
42
|
+
def __getitem__(self, index: int) -> int:
|
|
43
|
+
return self.denominators[index]
|
|
44
|
+
def __len__(self) -> int:
|
|
45
|
+
return len(self.denominators)
|
|
46
|
+
|
|
47
|
+
@classmethod
|
|
48
|
+
def from_rational(cls, rational: Rational, round = math.floor) -> SCF:
|
|
49
|
+
'set round=builtins.round or round=math.ceil for interesting partial denominators'
|
|
50
|
+
signum = (rational > 0) - (rational < 0)
|
|
51
|
+
rational = abs(rational)
|
|
52
|
+
|
|
53
|
+
denominators: MutableSequence[int] = list()
|
|
54
|
+
|
|
55
|
+
while True:
|
|
56
|
+
quotient = round(rational)
|
|
57
|
+
denominators.append(quotient)
|
|
58
|
+
remainder = rational.numerator - quotient * rational.denominator
|
|
59
|
+
if remainder == 0:
|
|
60
|
+
break
|
|
61
|
+
rational = Fraction(rational.denominator, remainder)
|
|
62
|
+
|
|
63
|
+
return cls(denominators, signum)
|
|
64
|
+
|
|
65
|
+
def to_rational(self) -> Rational:
|
|
66
|
+
if len(self.denominators) == 1:
|
|
67
|
+
return self.signum * self.denominators[0]
|
|
68
|
+
|
|
69
|
+
ac = Fraction(self.denominators[-1], 1)
|
|
70
|
+
|
|
71
|
+
for d in reversed(self.denominators[:-1]):
|
|
72
|
+
ac = d + 1 / ac
|
|
73
|
+
|
|
74
|
+
return self.signum * ac
|
|
75
|
+
|
|
76
|
+
def __str__(self) -> str:
|
|
77
|
+
return f'[{self.denominators[0]};{",".join(str(d) for d in self.denominators[1:])}]'
|
|
78
|
+
|
|
79
|
+
__repr__ = pretty_repr
|
daatypes/comp.py
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from numbers import Complex, Real
|
|
3
|
+
from .vector import Vector
|
|
4
|
+
|
|
5
|
+
class Comp(Vector, Complex):
|
|
6
|
+
'a complex number. a vector of 2 real numbers'
|
|
7
|
+
def __init__(self, real, imag):
|
|
8
|
+
self.real = real
|
|
9
|
+
self.imag = imag
|
|
10
|
+
super.__init__((real, imag))
|
|
11
|
+
|
|
12
|
+
@classmethod
|
|
13
|
+
def from_complex(cls, c: Complex) -> Comp:
|
|
14
|
+
return cls(c.real, c.imag)
|
|
15
|
+
|
|
16
|
+
def norm(self) -> Real:
|
|
17
|
+
return math.sqrt(self.real * self.real + self.imag * self.imag)
|
|
18
|
+
|
|
19
|
+
def unit(self) -> Real:
|
|
20
|
+
return type(self)(self.real / self.norm(), self.imag / self.norm())
|
|
21
|
+
|
|
22
|
+
def conjugate(self) -> Comp:
|
|
23
|
+
return type(self)(self.real, -self.imag)
|
|
24
|
+
|
|
25
|
+
__abs__ = norm
|
|
26
|
+
|
|
27
|
+
def __neg__(self) -> Comp:
|
|
28
|
+
return type(self)(-self.real, -self.imag)
|
|
29
|
+
def __pos__(self) -> Comp:
|
|
30
|
+
return type(self)(+self.real, +self.imag)
|
|
31
|
+
|
|
32
|
+
def __add__(self, other) -> Comp:
|
|
33
|
+
return type(self)(self.real + other.real, self.imag + other.imag)
|
|
34
|
+
|
|
35
|
+
def __mul__(self, other) -> Comp:
|
|
36
|
+
return type(self)(self.real*other.real-self.imag*other.imag, self.real*other.imag+self.imag*other.real)
|
|
37
|
+
|
|
38
|
+
def __complex__(self) -> complex:
|
|
39
|
+
return complex(self.real, self.imag)
|
|
40
|
+
|
|
41
|
+
def __eq__(self, other) -> bool:
|
|
42
|
+
return self.real == other.real and self.imag == other.imag
|
|
43
|
+
|
|
44
|
+
# '__pow__',
|
|
45
|
+
# '__radd__',
|
|
46
|
+
# '__rmul__',
|
|
47
|
+
# '__rpow__',
|
|
48
|
+
# '__rtruediv__',
|
|
49
|
+
# '__truediv__',
|
daatypes/fixed.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
from stringman.unicode import to_subscript
|
|
3
|
+
from numbers import Rational, Integral
|
|
4
|
+
from collections.abc import Sequence, MutableSequence, Iterable
|
|
5
|
+
from typing import Literal
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from itertools import chain, cycle, repeat
|
|
8
|
+
from fractions import Fraction
|
|
9
|
+
import math
|
|
10
|
+
|
|
11
|
+
@dataclass(kw_only = True)
|
|
12
|
+
class Fixed(Rational):
|
|
13
|
+
'a rational number with a fixed radix point. could potentially be an n-adic number class some day…'
|
|
14
|
+
signum: Literal[-1, 0, +1]
|
|
15
|
+
radix: Integral
|
|
16
|
+
left: Sequence[Integral] = field(default_factory = tuple)
|
|
17
|
+
right: Sequence[Integral] = field(default_factory = tuple)
|
|
18
|
+
left_repeat: None | Integral = None
|
|
19
|
+
right_repeat: None | Integral = None
|
|
20
|
+
|
|
21
|
+
def to_fraction(self) -> Fraction:
|
|
22
|
+
value = self.signum * sum(chain(
|
|
23
|
+
(digit * self.radix ** i for i, digit in enumerate(self.left, start = 0)),
|
|
24
|
+
(digit * self.radix ** -i for i, digit in enumerate(self.right, start = 1))))
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def numerator(self) -> Integral:
|
|
28
|
+
if self.left_repeat is not None:
|
|
29
|
+
raise ValueError(f'{self} is not a rational number')
|
|
30
|
+
return
|
|
31
|
+
@property
|
|
32
|
+
def denominator(self) -> Integral:
|
|
33
|
+
if self.left_repeat is not None:
|
|
34
|
+
raise ValueError(f'{self} is not a rational number')
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
@property
|
|
38
|
+
def left_preperiod(self):
|
|
39
|
+
return self.left if self.left_repeat is None else self.left[:self.left_repeat]
|
|
40
|
+
@property
|
|
41
|
+
def right_preperiod(self):
|
|
42
|
+
return self.right if self.right_repeat is None else self.right[:self.right_repeat]
|
|
43
|
+
@property
|
|
44
|
+
def left_repetend(self):
|
|
45
|
+
return () if self.left_repeat is None else self.left[self.left_repeat:]
|
|
46
|
+
@property
|
|
47
|
+
def right_repetend(self):
|
|
48
|
+
return () if self.right_repeat is None else self.right[self.right_repeat:]
|
|
49
|
+
|
|
50
|
+
@property
|
|
51
|
+
def left_iterable(self) -> Iterable:
|
|
52
|
+
yield from self.left_preperiod
|
|
53
|
+
if self.left_repetend:
|
|
54
|
+
yield from cycle(self.left_repetend)
|
|
55
|
+
else:
|
|
56
|
+
yield from repeat(0)
|
|
57
|
+
@property
|
|
58
|
+
def right_iterable(self) -> Iterable:
|
|
59
|
+
yield from self.right_preperiod
|
|
60
|
+
if self.right_repetend:
|
|
61
|
+
yield from cycle(self.right_repetend)
|
|
62
|
+
else:
|
|
63
|
+
yield from repeat(0)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_integral(cls, integral: Integral, radix: Integral) -> Fixed:
|
|
67
|
+
if integral == 0:
|
|
68
|
+
return cls(signum=0, radix=radix)
|
|
69
|
+
signum = (integral > 0) - (integral < 0)
|
|
70
|
+
integral = abs(integral)
|
|
71
|
+
|
|
72
|
+
left: MutableSequence[Integral] = list()
|
|
73
|
+
while integral > 0:
|
|
74
|
+
integral, index = divmod(integral, radix)
|
|
75
|
+
left.append(index)
|
|
76
|
+
|
|
77
|
+
return cls(signum=signum, left=left, radix=radix)
|
|
78
|
+
|
|
79
|
+
@classmethod
|
|
80
|
+
def from_rational(cls, rational: Rational, radix: Integral, right_limit: None | int = None) -> Fixed:
|
|
81
|
+
if rational == 0:
|
|
82
|
+
return cls(signum=0, radix=radix)
|
|
83
|
+
|
|
84
|
+
signum = (rational > 0) - (rational < 0)
|
|
85
|
+
rational = abs(rational)
|
|
86
|
+
|
|
87
|
+
integral, remainder = divmod(rational.numerator, rational.denominator)
|
|
88
|
+
|
|
89
|
+
left: MutableSequence[Integral] = list()
|
|
90
|
+
while integral > 0:
|
|
91
|
+
integral, index = divmod(integral, radix)
|
|
92
|
+
left.append(index)
|
|
93
|
+
|
|
94
|
+
right: MutableSequence = list()
|
|
95
|
+
remainders: dict[int, int] = dict() # remainders[remainder] = index
|
|
96
|
+
|
|
97
|
+
while True:
|
|
98
|
+
if remainder == 0 or remainder in remainders:
|
|
99
|
+
break
|
|
100
|
+
|
|
101
|
+
remainders[remainder] = len(right)
|
|
102
|
+
|
|
103
|
+
digit, remainder = divmod(remainder * radix, rational.denominator)
|
|
104
|
+
right.append(digit)
|
|
105
|
+
|
|
106
|
+
if right_limit is not None and len(right) >= right_limit:
|
|
107
|
+
break
|
|
108
|
+
|
|
109
|
+
return cls(signum = signum, radix = radix, left=left, right=right, right_repeat = remainders.get(remainder, None))
|
|
110
|
+
|
|
111
|
+
def __pos__(self) -> Fixed:
|
|
112
|
+
return self
|
|
113
|
+
def __neg__(self) -> Fixed:
|
|
114
|
+
return type(self)(signum=-self.signum, radix=self.radix, left=left, right=right, left_repeat=left_repeat, right_repeat=right_repeat)
|
|
115
|
+
def __abs__(self) -> Fixed:
|
|
116
|
+
return type(self)(signum=+1, radix=self.radix, left=left, right=right, left_repeat=left_repeat, right_repeat=right_repeat)
|
|
117
|
+
def __add__(self, other) -> Fixed:
|
|
118
|
+
...
|
|
119
|
+
def __floor__(self) -> Fixed:
|
|
120
|
+
return type(self)(signum=self.signum, radix=self.radix, left=left, right=right, left_repeat=left_repeat, right_repeat=right_repeat)
|
|
121
|
+
def __ceil__(self) -> Fixed:
|
|
122
|
+
return
|
|
123
|
+
def __trunc__(self) -> Fixed:
|
|
124
|
+
return type(self)(signum=self.signum, radix=self.radix, left=left, left_repeat=left_repeat)
|
|
125
|
+
def __floordiv__(self, other) -> Fixed:
|
|
126
|
+
return
|
|
127
|
+
def __le__(self, other) -> bool:
|
|
128
|
+
return
|
|
129
|
+
def __lt__(self, other) -> bool:
|
|
130
|
+
return self <= other and not other <= self
|
|
131
|
+
def __mod__(self, other) -> Fixed:
|
|
132
|
+
return
|
|
133
|
+
def __mul__(self, other) -> Fixed:
|
|
134
|
+
return
|
|
135
|
+
def __pow__(self, other) -> Fixed:
|
|
136
|
+
return
|
|
137
|
+
def __radd__(self) -> Fixed:
|
|
138
|
+
return
|
|
139
|
+
def __rfloordiv__(self) -> Fixed:
|
|
140
|
+
return
|
|
141
|
+
def __rmod__(self) -> Fixed:
|
|
142
|
+
return
|
|
143
|
+
def __rmul__(self) -> Fixed:
|
|
144
|
+
return
|
|
145
|
+
def __round__(self) -> Fixed:
|
|
146
|
+
return
|
|
147
|
+
def __rpow__(self) -> Fixed:
|
|
148
|
+
return
|
|
149
|
+
def __rtruediv__(self) -> Fixed:
|
|
150
|
+
return
|
|
151
|
+
def __truediv__(self) -> Fixed:
|
|
152
|
+
return
|
|
153
|
+
|
|
154
|
+
def __float__(self) -> float:
|
|
155
|
+
if self.left_repeat is not None:
|
|
156
|
+
return self.signum * math.inf
|
|
157
|
+
return self.signum * sum(chain(
|
|
158
|
+
(digit * self.radix ** i for i, digit in enumerate(self.left, start = 0)),
|
|
159
|
+
(digit * self.radix ** -i for i, digit in enumerate(self.right, start = 1))))
|
|
160
|
+
|
|
161
|
+
def __str__(self,
|
|
162
|
+
char_set = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
|
163
|
+
left_precision: None | int = None,
|
|
164
|
+
right_precision: None | int = None
|
|
165
|
+
) -> str:
|
|
166
|
+
|
|
167
|
+
sign: str = {-1: '−', 0: '', +1: '+'}[self.signum]
|
|
168
|
+
|
|
169
|
+
if left_precision is not None:
|
|
170
|
+
if self.left_repeat is None:
|
|
171
|
+
# left terminates
|
|
172
|
+
left: str = ''.join(char_set[digit] for digit in reversed(self.left_preperiod))
|
|
173
|
+
else:
|
|
174
|
+
# left has repeating digits
|
|
175
|
+
left: str = ...
|
|
176
|
+
else:
|
|
177
|
+
repetend: str = ''.join(char_set[digit] for digit in reversed(self.left_repetend)) if len(self.left_repetend) != 0 else '0'
|
|
178
|
+
preperiod: str = ''.join(char_set[digit] for digit in reversed(self.left_preperiod))
|
|
179
|
+
left: str = '(' + repetend + ')' + preperiod
|
|
180
|
+
|
|
181
|
+
if right_precision is not None:
|
|
182
|
+
if self.right_repeat is None:
|
|
183
|
+
# right terminates
|
|
184
|
+
right: str = ''.join(char_set[digit] for digit in self.right_preperiod)
|
|
185
|
+
else:
|
|
186
|
+
# right has repeating digits
|
|
187
|
+
right: str = ...
|
|
188
|
+
else:
|
|
189
|
+
repetend: str = ''.join(char_set[digit] for digit in self.right_repetend) if len(self.right_repetend) != 0 else '0'
|
|
190
|
+
preperiod: str = ''.join(char_set[digit] for digit in self.right_preperiod)
|
|
191
|
+
right: str = preperiod + '(' + repetend + ')'
|
|
192
|
+
|
|
193
|
+
return f'{sign}{left}.{right}'
|
|
194
|
+
|
|
195
|
+
def round_expansion(integral, fractional, radix, tie):
|
|
196
|
+
'round a positional number'
|
|
197
|
+
twice = 2 * remainder
|
|
198
|
+
|
|
199
|
+
if twice < divisor:
|
|
200
|
+
# round down/do nothing
|
|
201
|
+
return integral, fractional_digits, None
|
|
202
|
+
elif twice == divisor:
|
|
203
|
+
# a tie. like rounding 0.95 to 1 decimal digit. you have to choose a rule
|
|
204
|
+
match tie:
|
|
205
|
+
case 'up': fractional_digits[-1] += 1
|
|
206
|
+
case 'down': fractional_digits[-1] += 0
|
|
207
|
+
case 'even': fractional_digits[-1] += fractional_digits[-1] % 2 == 1
|
|
208
|
+
case 'odd': fractional_digits[-1] += fractional_digits[-1] % 2 == 0
|
|
209
|
+
elif twice > divisor:
|
|
210
|
+
fractional_digits[-1] += 1
|
|
211
|
+
|
|
212
|
+
# propagate the addition
|
|
213
|
+
for i in range(fractional_digit_count - 1, 0, -1):
|
|
214
|
+
if fractional_digits[i] >= radix:
|
|
215
|
+
fractional_digits[i] = 0
|
|
216
|
+
fractional_digits[i - 1] += 1
|
|
217
|
+
if fractional_digits[0] >= radix:
|
|
218
|
+
integral += 1
|
|
219
|
+
'''
|
|
220
|
+
def rational_to_scientific(rational: Rational, precision: int, radix: int = 10, exponent_marker: str = 'e', char_set: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ') -> str:
|
|
221
|
+
'stringify a rational number to scientific format (e.g. 1/3 → +3.(3)e-1)'
|
|
222
|
+
|
|
223
|
+
if rational == 0:
|
|
224
|
+
return '0' + exponent_marker + '0'
|
|
225
|
+
|
|
226
|
+
# normalize significand and exponent so that 1 <= significand < radix
|
|
227
|
+
sig: Rational = abs(rational)
|
|
228
|
+
exp: int = 0
|
|
229
|
+
while not 1 <= sig:
|
|
230
|
+
sig *= radix
|
|
231
|
+
exp -= 1
|
|
232
|
+
while not sig < radix:
|
|
233
|
+
sig /= radix
|
|
234
|
+
exp += 1
|
|
235
|
+
|
|
236
|
+
# now we just need to convert sig rational to digits, and exponent integer to digits
|
|
237
|
+
sig_sign = '' if rational == 0 else ('-' if rational < 0 else '+').lstrip('+')
|
|
238
|
+
sig = rational_to_digits(sig, fractional_precision = precision - 1, radix = radix, char_set = char_set)
|
|
239
|
+
exp_sign = '' if exp == 0 else ('-' if exp < 0 else '+')
|
|
240
|
+
exp = integral_to_digits(exp, radix = radix, char_set = char_set).lstrip('+')
|
|
241
|
+
|
|
242
|
+
return sig_sign + sig + exponent_marker + exp_sign + exp
|
|
243
|
+
'''
|
daatypes/float.py
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
import daacorations, math, builtins
|
|
2
|
+
from numbers import Rational
|
|
3
|
+
from fractions import Fraction
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
def xor(a, b): return (a and b) or (not(a or b))
|
|
6
|
+
|
|
7
|
+
def fraction_to_radix(numerator: int, denominator: int, *,
|
|
8
|
+
radix: int,
|
|
9
|
+
alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
|
|
10
|
+
fractional_digit_count: int = 99) -> str:
|
|
11
|
+
|
|
12
|
+
print(radix, numerator, denominator)
|
|
13
|
+
|
|
14
|
+
assert isinstance(numerator, int)
|
|
15
|
+
assert isinstance(denominator, int)
|
|
16
|
+
|
|
17
|
+
positive = xor(numerator >= 0, denominator >= 0)
|
|
18
|
+
sign = '' if positive else '-'
|
|
19
|
+
|
|
20
|
+
integral, fractional = divmod(abs(numerator), abs(denominator))
|
|
21
|
+
|
|
22
|
+
if integral == 0:
|
|
23
|
+
integral_str = '0'
|
|
24
|
+
else:
|
|
25
|
+
integral_digits: list[str] = []
|
|
26
|
+
while integral:
|
|
27
|
+
integral_digits.append(alphabet[integral % radix])
|
|
28
|
+
integral //= radix
|
|
29
|
+
integral_str = ''.join(reversed(integral_digits))
|
|
30
|
+
|
|
31
|
+
if fractional == 0:
|
|
32
|
+
return sign + integral_str
|
|
33
|
+
|
|
34
|
+
seen: dict[int, int] = dict()
|
|
35
|
+
fractional_digits: list[str] = list()
|
|
36
|
+
|
|
37
|
+
for _ in range(fractional_digit_count):
|
|
38
|
+
if not fractional:
|
|
39
|
+
break
|
|
40
|
+
|
|
41
|
+
if fractional in seen:
|
|
42
|
+
# repeating pattern
|
|
43
|
+
index = seen[fractional]
|
|
44
|
+
return sign + integral_str + '.' + ''.join(fractional_digits[:index]) + '(' + ''.join(fractional_digits[index:]) + ')'
|
|
45
|
+
|
|
46
|
+
seen[fractional] = len(fractional_digits)
|
|
47
|
+
|
|
48
|
+
fractional *= radix
|
|
49
|
+
digit = fractional // denominator
|
|
50
|
+
fractional %= denominator
|
|
51
|
+
fractional_digits.append(alphabet[digit])
|
|
52
|
+
else:
|
|
53
|
+
return sign + integral_str + '.' + ''.join(fractional_digits) + '…'
|
|
54
|
+
|
|
55
|
+
return sign + integral_str + '.' + ''.join(fractional_digits)
|
|
56
|
+
|
|
57
|
+
@dataclass(kw_only = True)
|
|
58
|
+
class Float(Rational):
|
|
59
|
+
"""store a rational number in scientific notation as a floating-point number
|
|
60
|
+
|
|
61
|
+
example
|
|
62
|
+
-------
|
|
63
|
+
significand * radix ** (exponent - precision + 1)
|
|
64
|
+
S.SSSSSS * RR ^ (EE - P + 1)
|
|
65
|
+
1.234567 * 10 ^ (89 - 7 + 1)
|
|
66
|
+
|
|
67
|
+
notes to myself
|
|
68
|
+
---------------
|
|
69
|
+
when printing to another radix, if input radix and output radix are coprime, you need to specify precision to the equivalent of the input radix.
|
|
70
|
+
"""
|
|
71
|
+
radix: int
|
|
72
|
+
precision: int
|
|
73
|
+
significand: int
|
|
74
|
+
exponent: int
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def numerator(self) -> int:
|
|
78
|
+
return self.significand * self.radix ** max(0, self.exponent - self.precision + 1)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def denominator(self) -> int:
|
|
82
|
+
return self.radix ** max(0, self.precision - 1 - self.exponent)
|
|
83
|
+
|
|
84
|
+
@classmethod
|
|
85
|
+
def from_str(cls, string: str, *, radix: int = 10, precision: None | int = None, exponent_marker: str = 'e') -> Float:
|
|
86
|
+
'construct a Float from a string. radix=10 by default (like builtins.int). precision is inferred.'
|
|
87
|
+
# '-0,1.2,3E-4,5'
|
|
88
|
+
string = string.replace(',', '').lower()
|
|
89
|
+
# '-01.23e-45'
|
|
90
|
+
significand_str, exponent_str = string.split(exponent_marker, 1) if exponent_marker in string else (string, '0')
|
|
91
|
+
# '-01.23', '-45'
|
|
92
|
+
negative = significand_str.startswith('-')
|
|
93
|
+
digits = significand_str.lstrip('+-0')
|
|
94
|
+
# -ve, '1.23', '-45'
|
|
95
|
+
point = digits.find('.') if '.' in digits else len(digits)
|
|
96
|
+
digits = digits.replace('.', '')
|
|
97
|
+
# -ve, '123', point=1, '-45'
|
|
98
|
+
|
|
99
|
+
precision = len(digits) if precision is None else precision
|
|
100
|
+
significand = int(('-' if negative else '') + digits, base=radix)
|
|
101
|
+
exponent = int(exponent_str, base=radix) + point - 1
|
|
102
|
+
|
|
103
|
+
return cls(radix=radix, precision=precision, significand=significand, exponent=exponent)
|
|
104
|
+
|
|
105
|
+
@classmethod
|
|
106
|
+
def from_rational(cls, rational: Rational, *, radix: int, precision: int) -> Float:
|
|
107
|
+
signum = (rational > 0) - (rational < 0)
|
|
108
|
+
|
|
109
|
+
significand = abs(rational)
|
|
110
|
+
exponent = 0
|
|
111
|
+
|
|
112
|
+
# get exponent
|
|
113
|
+
while significand >= radix:
|
|
114
|
+
exponent += 1
|
|
115
|
+
significand /= radix
|
|
116
|
+
|
|
117
|
+
# get significand
|
|
118
|
+
|
|
119
|
+
significand = Fixed.from_rational(significand)
|
|
120
|
+
|
|
121
|
+
return cls(radix=radix, precision=precision, significand=significand, exponent=exponent)
|
|
122
|
+
|
|
123
|
+
def __pos__(self): return Float.from_rational(+Fraction(self))
|
|
124
|
+
def __neg__(self): return Float.from_rational(-Fraction(self))
|
|
125
|
+
def __abs__(self): return Float.from_rational(abs(Fraction(self)))
|
|
126
|
+
def __ceil__(self): return Float.from_rational(math.ceil(Fraction(self)))
|
|
127
|
+
def __floor__(self): return Float.from_rational(math.floor(Fraction(self)))
|
|
128
|
+
def __round__(self): return Float.from_rational(round(Fraction(self)))
|
|
129
|
+
def __trunc__(self): return Float.from_rational(math.trunc(Fraction(self)))
|
|
130
|
+
def __floordiv__(self, other): return Float.from_rational(Fraction(self) // Fraction(other))
|
|
131
|
+
def __mod__(self, other): return Float.from_rational(Fraction(self) % Fraction(other))
|
|
132
|
+
def __le__(self, other): return Fraction(self) <= Fraction(other)
|
|
133
|
+
def __lt__(self, other): return Fraction(self) < Fraction(other)
|
|
134
|
+
def __add__(self, other): return Float.from_rational(Fraction(self) + Fraction(other))
|
|
135
|
+
def __mul__(self, other): return Float.from_rational(Fraction(self) * Fraction(other))
|
|
136
|
+
def __truediv__(self, other): return Float.from_rational(Fraction(self) / Fraction(other))
|
|
137
|
+
def __pow__(self, other): return Float.from_rational(Fraction(self) ** Fraction(other))
|
|
138
|
+
def __rfloordiv__(other, self): return Float.from_rational(Fraction(self) // Fraction(other))
|
|
139
|
+
def __radd__(other, self): return Float.from_rational(Fraction(self) + Fraction(other))
|
|
140
|
+
def __rmul__(other, self): return Float.from_rational(Fraction(self) * Fraction(other))
|
|
141
|
+
def __rmod__(other, self): return Float.from_rational(Fraction(self) % Fraction(other))
|
|
142
|
+
def __rpow__(other, self): return Float.from_rational(Fraction(self) ** Fraction(other))
|
|
143
|
+
def __rtruediv__(other, self): return Float.from_rational(Fraction(self) / Fraction(other))
|
|
144
|
+
|
|
145
|
+
def as_fraction(self) -> Fraction:
|
|
146
|
+
return Fraction(self)
|
|
147
|
+
|
|
148
|
+
def __float__(self) -> builtins.float:
|
|
149
|
+
return builtins.float(self.as_fraction())
|
|
150
|
+
|
|
151
|
+
def __str__(self, *, radix: int = 10, exponent_marker: str = 'e', alphabet: str = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz') -> str:
|
|
152
|
+
return fraction_to_radix(
|
|
153
|
+
numerator = self.numerator,
|
|
154
|
+
denominator = self.denominator,
|
|
155
|
+
radix = radix,
|
|
156
|
+
alphabet = alphabet)
|
|
157
|
+
|
|
158
|
+
str = __str__
|
|
159
|
+
|
|
160
|
+
__repr__ = daacorations.pretty_repr
|
|
161
|
+
|
|
162
|
+
def ieee_binary(bits: int) -> type:
|
|
163
|
+
match bits:
|
|
164
|
+
case 16: precision = 11; emin = - 14; emax = 15
|
|
165
|
+
case 32: precision = 24; emin = - 126; emax = 127
|
|
166
|
+
case 64: precision = 53; emin = -1022; emax = 1023
|
|
167
|
+
case _:
|
|
168
|
+
if bits < 128 or bits % 32 != 0:
|
|
169
|
+
raise ValueError('IEEE defines interchange formats for bits: 16, 32, 64, and multiples of 32 ≥128')
|
|
170
|
+
|
|
171
|
+
exponent_digits = round(4 * math.log2(bits)) - 13
|
|
172
|
+
precision = bits - exponent_digits
|
|
173
|
+
emax = 2 ** (exponent_digits - 1) - 1
|
|
174
|
+
emin = 1 - emax
|
|
175
|
+
|
|
176
|
+
class IEEE754Binary(Float):
|
|
177
|
+
'an IEEE 754 binary float'
|
|
178
|
+
|
|
179
|
+
def __init__(self, significand: int, exponent: int):
|
|
180
|
+
super().__init__(radix=2, precision=precision, significand=significand, exponent=exponent)
|
|
181
|
+
|
|
182
|
+
@classmethod
|
|
183
|
+
def from_str(cls, string) -> IEEE754Binary:
|
|
184
|
+
return super().from_str(string, radix = 2, precision = precision)
|
|
185
|
+
|
|
186
|
+
return IEEE754Binary, emin, emax
|
|
187
|
+
|
|
188
|
+
def ieee_decimal(bits: int) -> type:
|
|
189
|
+
match bits:
|
|
190
|
+
case 32: precision = 7; emin = - 95; emax = 96
|
|
191
|
+
case 64: precision = 16; emin = -383; emax = 384
|
|
192
|
+
case _:
|
|
193
|
+
if bits < 128 or bits % 32 != 0:
|
|
194
|
+
raise ValueError('IEEE defines interchange formats for bits: 32, 64, and multiples of 32 ≥128')
|
|
195
|
+
|
|
196
|
+
precision = 9 * (bits // 32) - 2
|
|
197
|
+
emax = 3 * 2 ** ((2 * (bits // 32) + 4) - 1)
|
|
198
|
+
emin = 1 - emax
|
|
199
|
+
|
|
200
|
+
class IEEE754Decimal(Float):
|
|
201
|
+
'an IEEE 754 decimal float'
|
|
202
|
+
|
|
203
|
+
def __init__(self, significand: int, exponent: int):
|
|
204
|
+
super().__init__(radix=10, precision=precision, significand=significand, exponent=exponent)
|
|
205
|
+
|
|
206
|
+
@classmethod
|
|
207
|
+
def from_str(cls, string, *, radix = 10) -> IEEE754Decimal:
|
|
208
|
+
return super().from_str(string, radix = radix, precision = precision)
|
|
209
|
+
|
|
210
|
+
return IEEE754Decimal, emin, emax
|
|
211
|
+
|
|
212
|
+
f16 = ieee_binary( 16)
|
|
213
|
+
f32 = ieee_binary( 32)
|
|
214
|
+
f64 = ieee_binary( 64)
|
|
215
|
+
f128 = ieee_binary(128)
|
|
216
|
+
f256 = ieee_binary(256)
|
|
217
|
+
|
|
218
|
+
d32 = ieee_decimal( 32)
|
|
219
|
+
d64 = ieee_decimal( 64)
|
|
220
|
+
d128 = ieee_decimal(128)
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
'''
|
|
224
|
+
f16:
|
|
225
|
+
0 00000 0000000000
|
|
226
|
+
False/True 00001–11110 0000000000
|
|
227
|
+
−/+ [−14, +15] [0/2E10, 1023/2E10]
|
|
228
|
+
|
|
229
|
+
d32:
|
|
230
|
+
0 000 0000000
|
|
231
|
+
−/+ −95, +96] [0/1E7, 9999999/1E7]
|
|
232
|
+
'''
|
daatypes/int.py
ADDED
daatypes/interval.py
ADDED
daatypes/monzo.py
ADDED
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
import collections, math, daacorations
|
|
2
|
+
from collections.abc import Sequence, MutableSequence, Mapping, MutableMapping, Iterable, Hashable, Container
|
|
3
|
+
from itertools import pairwise, cycle
|
|
4
|
+
from numbers import Integral, Rational
|
|
5
|
+
from frozendefaultdict import frozendefaultdict
|
|
6
|
+
from fractions import Fraction
|
|
7
|
+
from typing import Literal
|
|
8
|
+
from functools import cached_property
|
|
9
|
+
from collections import defaultdict
|
|
10
|
+
|
|
11
|
+
class Wheel(collections.abc.Iterator):
|
|
12
|
+
"""a wheel for generating primes. its iterator returns the residues of the given wheel size. you get diminishing returns as you go up in size:
|
|
13
|
+
|
|
14
|
+
>>> primes = [2,3,5,7,11,13,17,19,23]
|
|
15
|
+
>>> for i in range(len(primes)):
|
|
16
|
+
>>> size = math.prod(primes[:i])
|
|
17
|
+
>>> wheel_count = Wheel(size).cycle_size
|
|
18
|
+
>>> naïve_count = size
|
|
19
|
+
>>> efficiency = wheel_count / naïve_count
|
|
20
|
+
>>> print(size, f'{wheel_count}/{naïve_count}={efficiency:.2%}')
|
|
21
|
+
>>>
|
|
22
|
+
1 1/1=100.00%
|
|
23
|
+
2 1/2=50.00%
|
|
24
|
+
6 2/6=33.33%
|
|
25
|
+
30 8/30=26.67%
|
|
26
|
+
210 48/210=22.86%
|
|
27
|
+
2310 480/2310=20.78%
|
|
28
|
+
30030 5760/30030=19.18%
|
|
29
|
+
510510 92160/510510=18.05%
|
|
30
|
+
9699690 1658880/9699690=17.10%
|
|
31
|
+
223092870 36495360/223092870=16.36%
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
@staticmethod
|
|
35
|
+
def _generate_steps(size: int) -> Sequence[int]:
|
|
36
|
+
candidates = tuple(filter(lambda candidate: math.gcd(candidate, size) == 1, range(size)))
|
|
37
|
+
return [b - a for a, b in pairwise(candidates)] + [(candidates[0] - candidates[-1]) % size]
|
|
38
|
+
|
|
39
|
+
def __init__(self, factors: collections.abc.Iterable[int]):
|
|
40
|
+
'if you pass non-primes as factors, you wont be able to track them at the start of your generator. thats on you, idiot. sorry, harsh words, sorry but if you do that youre kinda dumb.. a bit'
|
|
41
|
+
size = math.lcm(*factors)
|
|
42
|
+
|
|
43
|
+
if math.prod(factors) != size:
|
|
44
|
+
raise ValueError('factors must be coprime!')
|
|
45
|
+
|
|
46
|
+
self.size: int = size
|
|
47
|
+
self.candidate: int = 1
|
|
48
|
+
|
|
49
|
+
steps: Sequence[int] = Wheel._generate_steps(size)
|
|
50
|
+
self.cycle: cycle[int] = cycle(steps)
|
|
51
|
+
self.cycle_size: int = len(steps)
|
|
52
|
+
|
|
53
|
+
# initialize candidate properly
|
|
54
|
+
#for factor in factors: # indexitis
|
|
55
|
+
# next(self)
|
|
56
|
+
|
|
57
|
+
def __iter__(self):
|
|
58
|
+
return self
|
|
59
|
+
|
|
60
|
+
def __next__(self) -> int:
|
|
61
|
+
self.candidate += next(self.cycle)
|
|
62
|
+
return self.candidate
|
|
63
|
+
|
|
64
|
+
__repr__ = daacorations.pretty_repr
|
|
65
|
+
|
|
66
|
+
class PrimesIterableContainer(Iterable, Container):
|
|
67
|
+
"""a singleton class representing the infinite sequence of prime numbers. you can do things like:
|
|
68
|
+
|
|
69
|
+
Primes = PrimeSequence()
|
|
70
|
+
|
|
71
|
+
Primes[0] == 2
|
|
72
|
+
Primes[1] == 3
|
|
73
|
+
Primes[2] == 5
|
|
74
|
+
Primes[3] == 7
|
|
75
|
+
|
|
76
|
+
assert 5 in Primes
|
|
77
|
+
assert 13 in Primes
|
|
78
|
+
|
|
79
|
+
primes = Primes[:10]
|
|
80
|
+
|
|
81
|
+
for prime in Primes[:100]:
|
|
82
|
+
print(prime)
|
|
83
|
+
|
|
84
|
+
uses a 2x3 wheel by default, thus only testing 6*1-1, 6*1+1, 6*2-1, 6*2+1, 6*3-1, 6*3+1, … for generation. larger wheels like 2x3x5, 2x3x5x7, … can be used for better efficiency (see Wheel class)
|
|
85
|
+
"""
|
|
86
|
+
|
|
87
|
+
_cache: MutableSequence[Integral] = [2, 3]
|
|
88
|
+
|
|
89
|
+
def __init__(self, wheel_factors: Iterable[int] = [2, 3]):
|
|
90
|
+
self.wheel: Wheel = Wheel(wheel_factors)
|
|
91
|
+
self._cache.extend(wheel_factors[len(self._cache):])
|
|
92
|
+
|
|
93
|
+
def _extend_cache_by(self, count: int = 1) -> None:
|
|
94
|
+
'grow cache by a certain .count of primes'
|
|
95
|
+
|
|
96
|
+
while count > 0:
|
|
97
|
+
candidate = next(self.wheel)
|
|
98
|
+
|
|
99
|
+
# "is candidate prime?"
|
|
100
|
+
if all(candidate % prime != 0 for prime in self._cache):
|
|
101
|
+
count -= 1
|
|
102
|
+
self._cache.append(candidate)
|
|
103
|
+
|
|
104
|
+
def __getitem__(self, index: slice | int):
|
|
105
|
+
if isinstance(index, slice):
|
|
106
|
+
self._extend_cache_by(index.stop - len(self._cache))
|
|
107
|
+
elif isinstance(index, int):
|
|
108
|
+
if index < 0:
|
|
109
|
+
raise ValueError('cannot use negative indices on an infinite sequence')
|
|
110
|
+
self._extend_cache_by(index + 1 - len(self._cache))
|
|
111
|
+
else:
|
|
112
|
+
raise TypeError('index must be either slice or int')
|
|
113
|
+
|
|
114
|
+
return self._cache[index]
|
|
115
|
+
|
|
116
|
+
def __iter__(self):
|
|
117
|
+
i = 0
|
|
118
|
+
|
|
119
|
+
while True:
|
|
120
|
+
yield self[i := i + 1]
|
|
121
|
+
|
|
122
|
+
@classmethod
|
|
123
|
+
def __contains__(cls, number: Natural) -> bool:
|
|
124
|
+
# number is known (from cache) to be prime
|
|
125
|
+
if number in cls._cache:
|
|
126
|
+
return True
|
|
127
|
+
|
|
128
|
+
# number is within cache
|
|
129
|
+
if cls._cache[-1] >= number:
|
|
130
|
+
return number in cls._cache
|
|
131
|
+
|
|
132
|
+
# number is not within cache. perform divisibility check
|
|
133
|
+
# this way, cache is generated up to ≥⌊√n⌋ instead of ≥n
|
|
134
|
+
limit: int = math.floor(math.sqrt(number))
|
|
135
|
+
|
|
136
|
+
for prime in Primes:
|
|
137
|
+
if prime > limit:
|
|
138
|
+
return True
|
|
139
|
+
if number % prime == 0:
|
|
140
|
+
return False
|
|
141
|
+
|
|
142
|
+
def index(self, number):
|
|
143
|
+
if number not in self:
|
|
144
|
+
raise numberError(f'{number} is not prime')
|
|
145
|
+
|
|
146
|
+
while self._cache[-1] < number:
|
|
147
|
+
self._extend_cache_by(1)
|
|
148
|
+
|
|
149
|
+
return self._cache.index(number)
|
|
150
|
+
|
|
151
|
+
__repr__ = daacorations.pretty_repr
|
|
152
|
+
|
|
153
|
+
Primes = PrimesIterableContainer()
|
|
154
|
+
|
|
155
|
+
'''
|
|
156
|
+
# here we dont subclass Rational because surds are not rational!
|
|
157
|
+
class Surd(Real):
|
|
158
|
+
'like how Integral is a sub(Natural, Natural) pair, and Rational is a div(Integral, Integral) pair, Surd is a root(Integral, Integral) pair. its cousin is the log(Integral, Integral) pair, which i have not named yet. the equivalence class is op(a, b) = op(c, d). so, for example, 2√2 = 4√4'
|
|
159
|
+
|
|
160
|
+
def __init__(self, base: Rational, degree: Rational):
|
|
161
|
+
self.base = base
|
|
162
|
+
self.degree = degree
|
|
163
|
+
|
|
164
|
+
def __float__(self) -> float:
|
|
165
|
+
return self.base ** (1 / self.degree)
|
|
166
|
+
|
|
167
|
+
def __mul__(self, other) -> Surd:
|
|
168
|
+
return self.base
|
|
169
|
+
'''
|
|
170
|
+
|
|
171
|
+
class Monzo(Hashable, Sequence):#, Rational):
|
|
172
|
+
"""a datatype that represents rational number stored as prime factors — conceptually as a sparse sequence, implemented as a mapping, but has the interface of a sequence :)
|
|
173
|
+
|
|
174
|
+
examples:
|
|
175
|
+
Monzo(2) = [1⟩ = {0: 1} = 2¹
|
|
176
|
+
Monzo(3) = [0 1⟩ = {1: 1} = 3¹
|
|
177
|
+
Monzo(4) = [2⟩ = {0: 2} = 2²
|
|
178
|
+
Monzo(5) = [0 0 1⟩ = {2: 1} = 5¹
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
def __init__(self, factors: HashableMapping, sign: Literal[-1, 0, +1] = 1):
|
|
182
|
+
self.factors: HashableMapping = factors
|
|
183
|
+
self.sign: Literal[-1, 0, +1] = sign
|
|
184
|
+
|
|
185
|
+
@cached_property
|
|
186
|
+
def numerator(self) -> Integral:
|
|
187
|
+
return self.sign * math.prod(Primes[prime_index] ** exponent for prime_index, exponent in self.factors.items() if exponent > 0)
|
|
188
|
+
|
|
189
|
+
@cached_property
|
|
190
|
+
def denominator(self) -> Integral:
|
|
191
|
+
return math.prod(Primes[prime_index] ** -exponent for prime_index, exponent in self.factors.items() if exponent < 0)
|
|
192
|
+
|
|
193
|
+
@cached_property
|
|
194
|
+
def prime_factors(self) -> Mapping[Integral, Integral]:
|
|
195
|
+
return frozendefaultdict.from_items(int, ((Primes[prime_index], exponent) for prime_index, exponent in self.factors.items()))
|
|
196
|
+
|
|
197
|
+
# to support Sequence interface
|
|
198
|
+
def __getitem__(self, index: int) -> int:
|
|
199
|
+
return self.factors[index] if index in self.factors else 0
|
|
200
|
+
def __len__(self) -> Cardinal:
|
|
201
|
+
keys = self.factors.keys()
|
|
202
|
+
return max(keys) + 1 if len(keys) > 0 else 1
|
|
203
|
+
def __iter__(self):
|
|
204
|
+
yield from (self.factors[index] for index in range(len(self)))
|
|
205
|
+
|
|
206
|
+
# to support Rational interface
|
|
207
|
+
def __add__(self, other) -> Monzo:
|
|
208
|
+
'a / b + c / d = (a * d + b * c) / (b * d)'
|
|
209
|
+
a, b, c, d = self.numerator, self.denominator, other.numerator, other.denominator
|
|
210
|
+
return cls.from_parts(a * d + b * c, b * d)
|
|
211
|
+
def __eq__(self, other) -> bool:
|
|
212
|
+
'a / b = c / d'
|
|
213
|
+
a, b, c, d = self.numerator, self.denominator, other.numerator, other.denominator
|
|
214
|
+
return a * d == b * c
|
|
215
|
+
def __float__(self) -> float:
|
|
216
|
+
return self.numerator / self.denominator
|
|
217
|
+
|
|
218
|
+
@classmethod
|
|
219
|
+
def from_prime_factors(cls, prime_factors: Mapping, *args, **kwargs) -> Monzo:
|
|
220
|
+
return cls(frozendefaultdict.from_items(int, ((Primes.index(factor), exponent) for factor, exponent in prime_factors.items())), *args, **kwargs)
|
|
221
|
+
|
|
222
|
+
@staticmethod
|
|
223
|
+
def _prime_factorize(number: Natural) -> HashableMapping[int, int]:
|
|
224
|
+
factors: MutableMapping = defaultdict(int)
|
|
225
|
+
|
|
226
|
+
prime_index = 0
|
|
227
|
+
|
|
228
|
+
while number > 1:
|
|
229
|
+
prime = Primes[prime_index]
|
|
230
|
+
|
|
231
|
+
if number % prime == 0:
|
|
232
|
+
number /= prime
|
|
233
|
+
if prime_index in factors:
|
|
234
|
+
factors[prime_index] += 1
|
|
235
|
+
else:
|
|
236
|
+
factors[prime_index] = 1
|
|
237
|
+
else:
|
|
238
|
+
prime_index += 1
|
|
239
|
+
|
|
240
|
+
return frozendefaultdict.from_items(int, factors.items())
|
|
241
|
+
|
|
242
|
+
@classmethod
|
|
243
|
+
def from_parts(cls, numerator: Integral, denominator: Integral) -> Monzo:
|
|
244
|
+
sign: Integral = int(math.copysign(1.0, numerator * denominator))
|
|
245
|
+
|
|
246
|
+
numerator = Monzo._prime_factorize(numerator)
|
|
247
|
+
denominator = Monzo._prime_factorize(denominator)
|
|
248
|
+
|
|
249
|
+
factors: MutableMapping = defaultdict(int, numerator)
|
|
250
|
+
|
|
251
|
+
for prime_factor, exponent in denominator.items():
|
|
252
|
+
if prime_factor in factors:
|
|
253
|
+
factors[prime_factor] -= exponent
|
|
254
|
+
else:
|
|
255
|
+
factors[prime_factor] = -exponent
|
|
256
|
+
|
|
257
|
+
return cls(frozendefaultdict.from_items(int, factors.items()), sign)
|
|
258
|
+
|
|
259
|
+
@classmethod
|
|
260
|
+
def from_rational(cls, number: Rational) -> Monzo:
|
|
261
|
+
return cls.from_parts(number.numerator, number.denominator)
|
|
262
|
+
|
|
263
|
+
@classmethod
|
|
264
|
+
def from_sequence(cls, exponents: Sequence[Integral]) -> Monzo:
|
|
265
|
+
'construct from a sequence. has to use reversed, len, and indexing'
|
|
266
|
+
# because we dont want [1⟩ from [1 0 0], not [1 0 0⟩
|
|
267
|
+
trailing_zero_count: int = 0
|
|
268
|
+
for exponent in reversed(exponents):
|
|
269
|
+
if exponent != 0:
|
|
270
|
+
break
|
|
271
|
+
trailing_zero_count += 1
|
|
272
|
+
|
|
273
|
+
items = enumerate(exponents if trailing_zero_count == 0 else exponents[:-trailing_zero_count])
|
|
274
|
+
|
|
275
|
+
return cls(frozendefaultdict.from_items(int, items))
|
|
276
|
+
|
|
277
|
+
def __hash__(self) -> int:
|
|
278
|
+
return hash(Fraction(self))
|
|
279
|
+
|
|
280
|
+
__repr__ = daacorations.pretty_repr
|
|
281
|
+
|
|
282
|
+
def __str__(self) -> str:
|
|
283
|
+
return '[' + ' '.join(str(exponent) for exponent in self) + '⟩'
|
|
284
|
+
|
|
285
|
+
Rational.register(Monzo)
|
|
286
|
+
|
daatypes/octo.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
from numbers import Number
|
|
2
|
+
from .vector import Vector
|
|
3
|
+
|
|
4
|
+
class Octo(Number, Vector):
|
|
5
|
+
'an octonion'
|
|
6
|
+
|
|
7
|
+
def __init__(self, e0, e1, e2, e3, e4, e5, e6, e7):
|
|
8
|
+
self.e0 = e0
|
|
9
|
+
self.e1 = e1
|
|
10
|
+
self.e2 = e2
|
|
11
|
+
self.e3 = e3
|
|
12
|
+
self.e4 = e4
|
|
13
|
+
self.e5 = e5
|
|
14
|
+
self.e6 = e6
|
|
15
|
+
self.e7 = e7
|
|
16
|
+
|
|
17
|
+
@classmethod
|
|
18
|
+
def from_complex(cls, c0, c1, c2, c3):
|
|
19
|
+
...
|
|
20
|
+
|
|
21
|
+
@classmethod
|
|
22
|
+
def from_quaternion(cls, q0, q1):
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
def as_real_matrix(self):
|
|
26
|
+
...
|
|
27
|
+
|
|
28
|
+
def as_complex_matrix(self):
|
|
29
|
+
...
|
daatypes/polar.py
ADDED
daatypes/qint.py
ADDED
daatypes/quat.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from numbers import Number, Real, Complex
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from daacorations import pretty_repr
|
|
5
|
+
|
|
6
|
+
@dataclass
|
|
7
|
+
class Quat(Number):
|
|
8
|
+
'a quaternion. a vector of 4 real numbers with special multiplication rules'
|
|
9
|
+
|
|
10
|
+
w: Real
|
|
11
|
+
x: Real
|
|
12
|
+
y: Real
|
|
13
|
+
z: Real
|
|
14
|
+
|
|
15
|
+
# @property
|
|
16
|
+
# def real(self) -> float:
|
|
17
|
+
# return self.w
|
|
18
|
+
#
|
|
19
|
+
# @property
|
|
20
|
+
# def imag(self) -> tuple[float, float, float]:
|
|
21
|
+
# return self.x, self.y, self.z
|
|
22
|
+
|
|
23
|
+
def norm(self) -> Real:
|
|
24
|
+
#return math.hypot(self.w, self.x, self.y, self.z)
|
|
25
|
+
return math.sqrt(self.w*self.w + self.x*self.x + self.y*self.y + self.z*self.z)
|
|
26
|
+
__abs__ = norm
|
|
27
|
+
|
|
28
|
+
def unit(self) -> Real:
|
|
29
|
+
norm = self.norm()
|
|
30
|
+
return type(self)(self.w/norm, self.x/norm, self.y/norm, self.z/norm)
|
|
31
|
+
|
|
32
|
+
@classmethod
|
|
33
|
+
def from_complex(cls, a: Complex, b: Complex) -> Quat:
|
|
34
|
+
'construct a quaternion from a pair of complex numbers, by cayley dickson construction'
|
|
35
|
+
return cls(a.real, a.imag, b.real, b.imag)
|
|
36
|
+
|
|
37
|
+
__repr__ = pretty_repr
|
|
38
|
+
|
|
39
|
+
def __add__(self, other) -> Quat:
|
|
40
|
+
return type(self)(self.w + other.w, self.x + other.x, self.y + other.y, self.z + other.z)
|
|
41
|
+
|
|
42
|
+
def __sub__(self, other) -> Quat:
|
|
43
|
+
return type(self)(self.w - other.w, self.x - other.x, self.y - other.y, self.z - other.z)
|
|
44
|
+
|
|
45
|
+
def __mul__(self, other) -> Quat:
|
|
46
|
+
return
|
|
47
|
+
|
|
48
|
+
def __str__(self) -> str:
|
|
49
|
+
return f'({self.w} + {self.x}i + {self.y}j + {self.z}k)'
|
|
50
|
+
|
|
51
|
+
def as_real_matrix(self):
|
|
52
|
+
return (( self.w, -self.x, -self.y, -self.z),
|
|
53
|
+
( self.x, self.w, -self.z, self.y),
|
|
54
|
+
( self.y, self.z, self.w, -self.x),
|
|
55
|
+
( self.z, -self.y, self.x, self.w))
|
|
56
|
+
|
|
57
|
+
def as_complex_matrix(self):
|
|
58
|
+
return ((complex( self.w, self.x), complex(self.y, self.z)),
|
|
59
|
+
(complex(-self.y, self.z), complex(self.w, -self.x)))
|
|
60
|
+
|
daatypes/real.py
ADDED
daatypes/uint.py
ADDED
daatypes/uqint.py
ADDED
daatypes/vector.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from collections.abc import Sequence, Iterable
|
|
3
|
+
from numbers import Real
|
|
4
|
+
|
|
5
|
+
class Vector(Sequence):
|
|
6
|
+
'an immutable vector'
|
|
7
|
+
def __init__(self, iterable: Iterable):
|
|
8
|
+
self.sequence: tuple = tuple(iterable)
|
|
9
|
+
|
|
10
|
+
def norm(self, power = 2) -> Real:
|
|
11
|
+
if power == 2:
|
|
12
|
+
return math.hypot(self.sequence)
|
|
13
|
+
else:
|
|
14
|
+
return sum(abs(x) ** power for x in self) ** (1 / power)
|
|
15
|
+
__abs__ = norm
|
|
16
|
+
|
|
17
|
+
def unit(self):
|
|
18
|
+
return self / self.norm()
|
|
19
|
+
|
|
20
|
+
# Sequence interface methods
|
|
21
|
+
def __len__(self):
|
|
22
|
+
return len(self.sequence)
|
|
23
|
+
def __getitem__(self, index):
|
|
24
|
+
return self.sequence[index]
|
|
25
|
+
|
|
26
|
+
def __mul__(self, other):
|
|
27
|
+
if isinstance(other, Real):
|
|
28
|
+
return type(self)(x * other for x in self)
|
|
29
|
+
|
|
30
|
+
def __truediv__(self, other):
|
|
31
|
+
if isinstance(other, Real):
|
|
32
|
+
return type(self)(x / other for x in self)
|
|
33
|
+
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: daatypes
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: i simulate datatypes that i find useful
|
|
5
|
+
Project-URL: Homepage, https://github.com/deftasparagusanaconda/daatypes
|
|
6
|
+
Project-URL: Repository, https://github.com/deftasparagusanaconda/daatypes
|
|
7
|
+
Project-URL: Issues, https://github.com/deftasparagusanaconda/daatypes/issues
|
|
8
|
+
Author: deftasparagusanaconda
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: math,numerical
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
|
|
22
|
+
a collection of datatypes (or, rather, simulations thereof) that i find useful
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
daatypes/__init__.py,sha256=G2On_8PV0cXu5Ygk5QENLzgeDgcmNnOrH-hIjbJ18DU,2258
|
|
2
|
+
daatypes/ball.py,sha256=ezj8sxLNvImTnnruhOXu7xyd8PeEnJDoxSd3of3sJyI,647
|
|
3
|
+
daatypes/cf.py,sha256=RSCQ2MqYLd21Xs3IeBvlneQUMCc3IVtTZACCrm2acy8,2517
|
|
4
|
+
daatypes/comp.py,sha256=Y-TA15E7bF5DM2CeKaRrOJAkStZnAuw1KouODkQmuTM,1473
|
|
5
|
+
daatypes/fixed.py,sha256=ISS9mRVxJM0RRN63nf6tfQCsoJCLIugDu74I8DCMGCs,9475
|
|
6
|
+
daatypes/float.py,sha256=qoDrvFRJFLCCmyZ2SIck7Yjza3PksiOY_TQ3nl716mU,8953
|
|
7
|
+
daatypes/int.py,sha256=1OlqFY3fuVdq9pwAz0FG0gx4YO7Yv-pDXGfS9jeq4ZE,59
|
|
8
|
+
daatypes/interval.py,sha256=Ug-A-vvZXubXK6VrimMpbs15XhXC1AIrXTNXcDTqqjo,112
|
|
9
|
+
daatypes/monzo.py,sha256=uZPkolMfU1iRaAtvnAE3Nb3uTyJsvSMm9e2qQOFbeZo,10392
|
|
10
|
+
daatypes/octo.py,sha256=5INSYFpg6IWA1Ub0194EeuLCbV4dLP6nGgm0www2xoA,560
|
|
11
|
+
daatypes/polar.py,sha256=MwzMLR8I8lbaBt3HNtkvB0Ad6AZHWj625shsAY-avTQ,157
|
|
12
|
+
daatypes/qint.py,sha256=MMhrnfXK_51-3HzbmaaqmxnlZCddjs5igTnYS0SdgSw,60
|
|
13
|
+
daatypes/quat.py,sha256=eIOeG1zYjre7TgooBdBYSVazoc7NRmdTzTReawQftlw,1948
|
|
14
|
+
daatypes/real.py,sha256=riUSsTHuLdGVJEu3UZ2wrnAPtqICVPaJKk179zkv2bE,271
|
|
15
|
+
daatypes/uint.py,sha256=7QDbriz1dv6d2YzJc7b0wyQGtMa-ZzdO7Bj0fv4_iXU,60
|
|
16
|
+
daatypes/uqint.py,sha256=e_W80VNQx2Xn2KMYyenuHojw1jrjcrTaU5gu62P3Vps,61
|
|
17
|
+
daatypes/vector.py,sha256=LvOJ4-srjkI-vuWp9vj3l0E0QY8T2nxGgKWgVI7NsCc,925
|
|
18
|
+
daatypes-0.1.0.dist-info/METADATA,sha256=fjfl770bBtMvaZzsUoFyeFEUexbyxsWIzWVGK4cQMMM,960
|
|
19
|
+
daatypes-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
20
|
+
daatypes-0.1.0.dist-info/licenses/LICENSE,sha256=VB2xlcQdtOxvalPfPY4FykGdH2Aqqjr0NO9YeF_3cyk,1078
|
|
21
|
+
daatypes-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Lalremruata Chongmang
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|