valuebridge-tdfloat 1.0.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.
- valuebridge/tdfloat/__init__.py +64 -0
- valuebridge/tdfloat/_arithmetic.py +258 -0
- valuebridge/tdfloat/_encoding.py +161 -0
- valuebridge/tdfloat/constants.py +112 -0
- valuebridge/tdfloat/math.py +333 -0
- valuebridge/tdfloat/tdfloat.py +532 -0
- valuebridge_tdfloat-1.0.0.dist-info/METADATA +192 -0
- valuebridge_tdfloat-1.0.0.dist-info/RECORD +11 -0
- valuebridge_tdfloat-1.0.0.dist-info/WHEEL +5 -0
- valuebridge_tdfloat-1.0.0.dist-info/licenses/LICENSE +21 -0
- valuebridge_tdfloat-1.0.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,532 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tdfloat.tdfloat
|
|
3
|
+
===============
|
|
4
|
+
The TDFloat class — a floating-point number as a single Python integer.
|
|
5
|
+
|
|
6
|
+
Encoding (from encoding_any_symbol.v):
|
|
7
|
+
position = 2 * rank + info_bit
|
|
8
|
+
rank = abs_digits * 78 + dot_pos
|
|
9
|
+
info_bit = 1 if dot_pos > 0 (the "." is present)
|
|
10
|
+
|
|
11
|
+
The "." is the info_bit:
|
|
12
|
+
info_bit = 0 → integer axis (no dot, exact integer)
|
|
13
|
+
info_bit = 1 → half-step axis (has dot, fractional)
|
|
14
|
+
|
|
15
|
+
Every number is stored as one Python int.
|
|
16
|
+
All arithmetic is exact rational arithmetic on integers.
|
|
17
|
+
No Python float is used anywhere in the core.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
from ._encoding import (
|
|
22
|
+
_NAN, _PINF, _NINF, _SPECIALS,
|
|
23
|
+
pack, unpack, from_rational, to_rational,
|
|
24
|
+
from_str, to_str, encode_symbol, decode_rank, decode_info,
|
|
25
|
+
DOT_POSITIONS, MAX_DOT_POS,
|
|
26
|
+
)
|
|
27
|
+
from ._arithmetic import (
|
|
28
|
+
add, sub, neg, mul, div, floordiv, mod, divmod_td,
|
|
29
|
+
eq, lt, abs_val, pow_int, sqrt,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# Try to use Rust-accelerated rational ops for fraction fast path
|
|
33
|
+
# NOTE: Disabled due to bugs in from_rational_bw for non-terminating decimals
|
|
34
|
+
# The Python version in _encoding.py works correctly
|
|
35
|
+
try:
|
|
36
|
+
from tdfloat_core import td_rational_add, td_rational_sub, td_rational_mul, td_rational_div
|
|
37
|
+
_RUST_RATIONAL = False # Force use of Python implementation until Rust is fixed
|
|
38
|
+
except ImportError:
|
|
39
|
+
_RUST_RATIONAL = False
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TDFloat:
|
|
43
|
+
"""
|
|
44
|
+
Triadic Dot Float — a number on the half-step rational line.
|
|
45
|
+
|
|
46
|
+
Encoding
|
|
47
|
+
--------
|
|
48
|
+
Every TDFloat is a single Python int.
|
|
49
|
+
|
|
50
|
+
encoded = 2 * rank + info_bit (encode_symbol)
|
|
51
|
+
rank = abs_digits * 78 + dot_pos
|
|
52
|
+
info_bit = 1 if '.' present, 0 if integer
|
|
53
|
+
|
|
54
|
+
The "." is the info_bit — its presence moves a number from
|
|
55
|
+
the integer axis (even position) to the half-step axis (odd position).
|
|
56
|
+
|
|
57
|
+
Constructors
|
|
58
|
+
------------
|
|
59
|
+
TDFloat(int) from raw encoded integer
|
|
60
|
+
TDFloat.from_int(n) exact integer
|
|
61
|
+
TDFloat.from_frac(p,q) exact rational
|
|
62
|
+
TDFloat.from_str('3.14') decimal string (no Python float)
|
|
63
|
+
td(x) convenience: int, str, or float
|
|
64
|
+
|
|
65
|
+
Arithmetic
|
|
66
|
+
----------
|
|
67
|
+
+ - * / // % ** abs neg all exact rational
|
|
68
|
+
sqrt() rational Newton's method
|
|
69
|
+
All operations are associative and commutative.
|
|
70
|
+
|
|
71
|
+
Constants
|
|
72
|
+
---------
|
|
73
|
+
TDFloat.PI = 22/7
|
|
74
|
+
TDFloat.TAU = 44/7
|
|
75
|
+
TDFloat.E = 19/7
|
|
76
|
+
TDFloat.PHI = 3/2
|
|
77
|
+
TDFloat.SQRT2 = 7/5
|
|
78
|
+
TDFloat.HALF = 1/2
|
|
79
|
+
... (see tdfloat.constants for all)
|
|
80
|
+
|
|
81
|
+
Phase / axis
|
|
82
|
+
------------
|
|
83
|
+
.is_integer True if on the integer axis (info_bit=0)
|
|
84
|
+
.is_fractional True if on the half-step axis (info_bit=1)
|
|
85
|
+
.dot_pos number of decimal places after '.'
|
|
86
|
+
.info_bit 0 or 1
|
|
87
|
+
.encoded the raw integer
|
|
88
|
+
|
|
89
|
+
Special values
|
|
90
|
+
--------------
|
|
91
|
+
TDFloat.NAN not-a-number
|
|
92
|
+
TDFloat.INF positive infinity
|
|
93
|
+
TDFloat.NINF negative infinity
|
|
94
|
+
"""
|
|
95
|
+
|
|
96
|
+
__slots__ = ('_e', '_p', '_q')
|
|
97
|
+
|
|
98
|
+
def __init__(self, encoded: int, p: int = None, q: int = None):
|
|
99
|
+
self._e = int(encoded)
|
|
100
|
+
# Store canonical fraction if provided (avoids decimal round-trip loss)
|
|
101
|
+
if p is not None and q is not None and q != 0:
|
|
102
|
+
from math import gcd
|
|
103
|
+
g = gcd(abs(int(p)), abs(int(q)))
|
|
104
|
+
self._p = int(p) // g
|
|
105
|
+
self._q = int(q) // g
|
|
106
|
+
if self._q < 0: self._p, self._q = -self._p, -self._q
|
|
107
|
+
else:
|
|
108
|
+
# Auto-extract fraction from encoded value if not provided
|
|
109
|
+
# This makes the fraction fast path automatic and transparent
|
|
110
|
+
p_rat, q_rat = to_rational(self._e)
|
|
111
|
+
if q_rat != 0 and (p_rat != 0 or q_rat == 1):
|
|
112
|
+
self._p = p_rat
|
|
113
|
+
self._q = q_rat
|
|
114
|
+
else:
|
|
115
|
+
self._p = None
|
|
116
|
+
self._q = None
|
|
117
|
+
|
|
118
|
+
# ── Constructors ──────────────────────────────────────────────────
|
|
119
|
+
|
|
120
|
+
@classmethod
|
|
121
|
+
def from_int(cls, n: int) -> TDFloat:
|
|
122
|
+
"""Exact integer. Always I-phase (integer axis, info_bit=0)."""
|
|
123
|
+
return cls(from_rational(n, 1), n, 1)
|
|
124
|
+
|
|
125
|
+
@classmethod
|
|
126
|
+
def from_frac(cls, p: int, q: int) -> TDFloat:
|
|
127
|
+
"""
|
|
128
|
+
Exact rational p/q.
|
|
129
|
+
I-phase if q is a power of 2 and divides p*10^k for small k.
|
|
130
|
+
N-phase (half-step axis) otherwise.
|
|
131
|
+
"""
|
|
132
|
+
return cls(from_rational(p, q), p, q)
|
|
133
|
+
|
|
134
|
+
@classmethod
|
|
135
|
+
def from_str(cls, s: str) -> TDFloat:
|
|
136
|
+
"""
|
|
137
|
+
Parse decimal string without using Python float.
|
|
138
|
+
Supports: '3.14', '-0.5', '1e-3', 'inf', '-inf', 'nan'
|
|
139
|
+
"""
|
|
140
|
+
return cls(from_str(s))
|
|
141
|
+
|
|
142
|
+
@classmethod
|
|
143
|
+
def from_float(cls, x: float) -> TDFloat:
|
|
144
|
+
"""
|
|
145
|
+
Lift a Python float by extracting its exact binary fraction.
|
|
146
|
+
This is the only place a Python float is accepted.
|
|
147
|
+
"""
|
|
148
|
+
import struct
|
|
149
|
+
if x != x: return cls(_NAN)
|
|
150
|
+
if x == float('inf'): return cls(_PINF)
|
|
151
|
+
if x == float('-inf'): return cls(_NINF)
|
|
152
|
+
raw = struct.pack('>d', x)
|
|
153
|
+
bits = int.from_bytes(raw, 'big')
|
|
154
|
+
s = (bits >> 63) & 1
|
|
155
|
+
e = (bits >> 52) & 0x7FF
|
|
156
|
+
m = bits & ((1 << 52) - 1)
|
|
157
|
+
if e == 0: sig, exp = m, 1 - 1023 - 52
|
|
158
|
+
else: sig, exp = (1 << 52) | m, e - 1023 - 52
|
|
159
|
+
if sig == 0: return cls(from_rational(0, 1))
|
|
160
|
+
# value = sig * 2^exp = sig / 2^(-exp) if exp < 0
|
|
161
|
+
if exp >= 0:
|
|
162
|
+
p, q = sig * (2 ** exp), 1
|
|
163
|
+
else:
|
|
164
|
+
p, q = sig, 2 ** (-exp)
|
|
165
|
+
pp = -p if s else p
|
|
166
|
+
return cls(from_rational(pp, q), pp, q)
|
|
167
|
+
|
|
168
|
+
# ── Arithmetic ────────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
def __add__(self, o) -> TDFloat:
|
|
171
|
+
if not isinstance(o, TDFloat): o = _coerce(o)
|
|
172
|
+
# Use exact fractions if both available
|
|
173
|
+
if self._p is not None and o._p is not None:
|
|
174
|
+
if _RUST_RATIONAL:
|
|
175
|
+
enc, p, q = td_rational_add(self._p, self._q, o._p, o._q)
|
|
176
|
+
return TDFloat(enc, p, q)
|
|
177
|
+
from ._arithmetic import _simplify
|
|
178
|
+
p, q = _simplify(self._p * o._q + o._p * self._q, self._q * o._q)
|
|
179
|
+
return TDFloat(from_rational(p, q), p, q)
|
|
180
|
+
return TDFloat(add(self._e, o._e))
|
|
181
|
+
|
|
182
|
+
def __radd__(self, o) -> TDFloat:
|
|
183
|
+
return _coerce(o) + self
|
|
184
|
+
|
|
185
|
+
def __sub__(self, o) -> TDFloat:
|
|
186
|
+
if not isinstance(o, TDFloat): o = _coerce(o)
|
|
187
|
+
if self._p is not None and o._p is not None:
|
|
188
|
+
if _RUST_RATIONAL:
|
|
189
|
+
enc, p, q = td_rational_sub(self._p, self._q, o._p, o._q)
|
|
190
|
+
return TDFloat(enc, p, q)
|
|
191
|
+
from ._arithmetic import _simplify
|
|
192
|
+
p, q = _simplify(self._p * o._q - o._p * self._q, self._q * o._q)
|
|
193
|
+
return TDFloat(from_rational(p, q), p, q)
|
|
194
|
+
return TDFloat(sub(self._e, o._e))
|
|
195
|
+
|
|
196
|
+
def __rsub__(self, o) -> TDFloat:
|
|
197
|
+
return _coerce(o) - self
|
|
198
|
+
|
|
199
|
+
def __mul__(self, o) -> TDFloat:
|
|
200
|
+
if not isinstance(o, TDFloat): o = _coerce(o)
|
|
201
|
+
if self._p is not None and o._p is not None:
|
|
202
|
+
if _RUST_RATIONAL:
|
|
203
|
+
enc, p, q = td_rational_mul(self._p, self._q, o._p, o._q)
|
|
204
|
+
return TDFloat(enc, p, q)
|
|
205
|
+
from ._arithmetic import _simplify
|
|
206
|
+
p, q = _simplify(self._p * o._p, self._q * o._q)
|
|
207
|
+
return TDFloat(from_rational(p, q), p, q)
|
|
208
|
+
return TDFloat(mul(self._e, o._e))
|
|
209
|
+
|
|
210
|
+
def __rmul__(self, o) -> TDFloat:
|
|
211
|
+
return _coerce(o) * self
|
|
212
|
+
|
|
213
|
+
def __truediv__(self, o) -> TDFloat:
|
|
214
|
+
if not isinstance(o, TDFloat): o = _coerce(o)
|
|
215
|
+
if self._p is not None and o._p is not None:
|
|
216
|
+
if o._p == 0:
|
|
217
|
+
return TDFloat(_NAN) if self._p == 0 else (TDFloat(_NINF) if self._p < 0 else TDFloat(_PINF))
|
|
218
|
+
if _RUST_RATIONAL:
|
|
219
|
+
enc, p, q = td_rational_div(self._p, self._q, o._p, o._q)
|
|
220
|
+
return TDFloat(enc, p, q)
|
|
221
|
+
from ._arithmetic import _simplify
|
|
222
|
+
p, q = _simplify(self._p * o._q, self._q * o._p)
|
|
223
|
+
return TDFloat(from_rational(p, q), p, q)
|
|
224
|
+
return TDFloat(div(self._e, o._e))
|
|
225
|
+
|
|
226
|
+
def __rtruediv__(self, o) -> TDFloat:
|
|
227
|
+
return _coerce(o) / self
|
|
228
|
+
|
|
229
|
+
def __floordiv__(self, o) -> TDFloat:
|
|
230
|
+
if not isinstance(o, TDFloat): o = _coerce(o)
|
|
231
|
+
return TDFloat(floordiv(self._e, o._e))
|
|
232
|
+
|
|
233
|
+
def __mod__(self, o) -> TDFloat:
|
|
234
|
+
if not isinstance(o, TDFloat): o = _coerce(o)
|
|
235
|
+
return TDFloat(mod(self._e, o._e))
|
|
236
|
+
|
|
237
|
+
def divmod_td(self, o) -> tuple:
|
|
238
|
+
"""Three-term division: returns (quotient, remainder, denominator).
|
|
239
|
+
|
|
240
|
+
Returns the complete quotient-remainder decomposition:
|
|
241
|
+
self/o = quotient + remainder/denominator
|
|
242
|
+
|
|
243
|
+
Args:
|
|
244
|
+
o: TDFloat divisor
|
|
245
|
+
|
|
246
|
+
Returns:
|
|
247
|
+
(quotient, remainder, denominator) as a tuple of TDFloat values
|
|
248
|
+
with _p and _q set for the fast path
|
|
249
|
+
"""
|
|
250
|
+
if not isinstance(o, TDFloat): o = _coerce(o)
|
|
251
|
+
from ._arithmetic import divmod_td as divmod_td_encoded
|
|
252
|
+
quot_e, rem_e, den_e = divmod_td_encoded(self._e, o._e)
|
|
253
|
+
|
|
254
|
+
# Decode each result to preserve _p and _q for fast path
|
|
255
|
+
p_q, q_q = to_rational(quot_e)
|
|
256
|
+
p_r, q_r = to_rational(rem_e)
|
|
257
|
+
p_d, q_d = to_rational(den_e)
|
|
258
|
+
|
|
259
|
+
return (
|
|
260
|
+
TDFloat(quot_e, p_q, q_q),
|
|
261
|
+
TDFloat(rem_e, p_r, q_r),
|
|
262
|
+
TDFloat(den_e, p_d, q_d)
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
def __pow__(self, n) -> TDFloat:
|
|
266
|
+
if isinstance(n, TDFloat):
|
|
267
|
+
# Only integer powers supported natively
|
|
268
|
+
pn, pd = to_rational(n._e)
|
|
269
|
+
if pd == 1:
|
|
270
|
+
n_int = int(pn)
|
|
271
|
+
else:
|
|
272
|
+
raise TypeError("Non-integer powers require tdfloat.math")
|
|
273
|
+
else:
|
|
274
|
+
n_int = int(n)
|
|
275
|
+
|
|
276
|
+
# For fraction fast path: compute directly on numerator and denominator
|
|
277
|
+
if self._p is not None:
|
|
278
|
+
p_result = self._p ** n_int
|
|
279
|
+
q_result = self._q ** n_int
|
|
280
|
+
return TDFloat(pow_int(self._e, n_int), p_result, q_result)
|
|
281
|
+
return TDFloat(pow_int(self._e, n_int))
|
|
282
|
+
|
|
283
|
+
def __neg__(self) -> TDFloat:
|
|
284
|
+
if self._p is not None:
|
|
285
|
+
return TDFloat(neg(self._e), -self._p, self._q)
|
|
286
|
+
return TDFloat(neg(self._e))
|
|
287
|
+
|
|
288
|
+
def __pos__(self) -> TDFloat:
|
|
289
|
+
return self
|
|
290
|
+
|
|
291
|
+
def __abs__(self) -> TDFloat:
|
|
292
|
+
if self._p is not None:
|
|
293
|
+
return TDFloat(abs_val(self._e), abs(self._p), self._q)
|
|
294
|
+
return TDFloat(abs_val(self._e))
|
|
295
|
+
|
|
296
|
+
def sqrt(self) -> TDFloat:
|
|
297
|
+
"""Square root via rational Newton's method.
|
|
298
|
+
Automatically preserves _p and _q through auto-extraction."""
|
|
299
|
+
return TDFloat(sqrt(self._e))
|
|
300
|
+
|
|
301
|
+
# ── Comparison ────────────────────────────────────────────────────
|
|
302
|
+
|
|
303
|
+
def __eq__(self, o) -> bool:
|
|
304
|
+
if not isinstance(o, TDFloat): o = _coerce(o)
|
|
305
|
+
# Use exact fractions if available
|
|
306
|
+
if self._p is not None and o._p is not None:
|
|
307
|
+
return self._p * o._q == o._p * self._q
|
|
308
|
+
return eq(self._e, o._e)
|
|
309
|
+
|
|
310
|
+
def __lt__(self, o) -> bool:
|
|
311
|
+
if not isinstance(o, TDFloat): o = _coerce(o)
|
|
312
|
+
if self._p is not None and o._p is not None:
|
|
313
|
+
return self._p * o._q < o._p * self._q
|
|
314
|
+
return lt(self._e, o._e)
|
|
315
|
+
|
|
316
|
+
def __le__(self, o) -> bool: return self == o or self < o
|
|
317
|
+
def __gt__(self, o) -> bool: return not self <= o
|
|
318
|
+
def __ge__(self, o) -> bool: return not self < o
|
|
319
|
+
|
|
320
|
+
def __bool__(self) -> bool:
|
|
321
|
+
return self._e != 0 and self._e not in _SPECIALS
|
|
322
|
+
|
|
323
|
+
def __hash__(self) -> int:
|
|
324
|
+
p, q = self.as_fraction()
|
|
325
|
+
return hash((p, q))
|
|
326
|
+
|
|
327
|
+
# ── Encoding / phase inspection ───────────────────────────────────
|
|
328
|
+
|
|
329
|
+
@property
|
|
330
|
+
def encoded(self) -> int:
|
|
331
|
+
"""The single integer that IS this float on the half-step line."""
|
|
332
|
+
return self._e
|
|
333
|
+
|
|
334
|
+
@property
|
|
335
|
+
def info_bit(self) -> int:
|
|
336
|
+
"""0 = integer axis (no dot), 1 = half-step axis (has dot)."""
|
|
337
|
+
if self._e in _SPECIALS: return 0
|
|
338
|
+
return decode_info(abs(self._e))
|
|
339
|
+
|
|
340
|
+
@property
|
|
341
|
+
def is_integer(self) -> bool:
|
|
342
|
+
"""True if this number lives on the integer axis (no dot)."""
|
|
343
|
+
return self.info_bit == 0 and self._e not in _SPECIALS
|
|
344
|
+
|
|
345
|
+
@property
|
|
346
|
+
def is_fractional(self) -> bool:
|
|
347
|
+
"""True if this number lives on the half-step axis (has dot)."""
|
|
348
|
+
return self.info_bit == 1
|
|
349
|
+
|
|
350
|
+
@property
|
|
351
|
+
def dot_pos(self) -> int:
|
|
352
|
+
"""Number of decimal places after the dot (0 if integer)."""
|
|
353
|
+
if self._e in _SPECIALS: return 0
|
|
354
|
+
rank = decode_rank(abs(self._e))
|
|
355
|
+
return rank % DOT_POSITIONS
|
|
356
|
+
|
|
357
|
+
@property
|
|
358
|
+
def abs_digits(self) -> int:
|
|
359
|
+
"""The digit string as an integer (no dot, no sign)."""
|
|
360
|
+
if self._e in _SPECIALS: return 0
|
|
361
|
+
rank = decode_rank(abs(self._e))
|
|
362
|
+
return rank // DOT_POSITIONS
|
|
363
|
+
|
|
364
|
+
@property
|
|
365
|
+
def is_nan(self) -> bool: return self._e == _NAN
|
|
366
|
+
@property
|
|
367
|
+
def is_inf(self) -> bool: return self._e in (_PINF, _NINF)
|
|
368
|
+
@property
|
|
369
|
+
def is_finite(self) -> bool: return self._e not in _SPECIALS
|
|
370
|
+
|
|
371
|
+
def as_fraction(self) -> tuple:
|
|
372
|
+
"""Return (numerator, denominator) as exact integers.
|
|
373
|
+
|
|
374
|
+
Uses internal _p and _q when available (fast path).
|
|
375
|
+
Falls back to decoding from encoded value.
|
|
376
|
+
"""
|
|
377
|
+
if self._p is not None and self._q is not None:
|
|
378
|
+
return self._p, self._q
|
|
379
|
+
p, q = to_rational(self._e)
|
|
380
|
+
from math import gcd
|
|
381
|
+
g = gcd(abs(p), q) if q else 1
|
|
382
|
+
return p // g, q // g
|
|
383
|
+
|
|
384
|
+
@property
|
|
385
|
+
def numerator(self) -> int:
|
|
386
|
+
"""Numerator of the canonical fraction representation."""
|
|
387
|
+
p, q = self.as_fraction()
|
|
388
|
+
return p
|
|
389
|
+
|
|
390
|
+
@property
|
|
391
|
+
def denominator(self) -> int:
|
|
392
|
+
"""Denominator of the canonical fraction representation."""
|
|
393
|
+
p, q = self.as_fraction()
|
|
394
|
+
return q
|
|
395
|
+
|
|
396
|
+
def fields(self) -> dict:
|
|
397
|
+
"""Full encoding breakdown for inspection."""
|
|
398
|
+
if self._e in _SPECIALS:
|
|
399
|
+
return {'encoded': self._e, 'special': to_str(self._e)}
|
|
400
|
+
s, digits, dp = unpack(self._e)
|
|
401
|
+
return {
|
|
402
|
+
'encoded': self._e,
|
|
403
|
+
'sign': s,
|
|
404
|
+
'abs_digits': digits,
|
|
405
|
+
'dot_pos': dp,
|
|
406
|
+
'info_bit': self.info_bit,
|
|
407
|
+
'rank': decode_rank(abs(self._e)),
|
|
408
|
+
'position': abs(self._e),
|
|
409
|
+
'axis': 'half-step' if self.info_bit else 'integer',
|
|
410
|
+
'value': str(self),
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
# ── Conversion ────────────────────────────────────────────────────
|
|
414
|
+
|
|
415
|
+
def __float__(self) -> float:
|
|
416
|
+
"""Convert to Python float (lossy for repeating decimals)."""
|
|
417
|
+
p, q = to_rational(self._e)
|
|
418
|
+
if q == 0: return float('nan')
|
|
419
|
+
return p / q
|
|
420
|
+
|
|
421
|
+
def __int__(self) -> int:
|
|
422
|
+
p, q = to_rational(self._e)
|
|
423
|
+
if q == 0: return 0
|
|
424
|
+
return p // q
|
|
425
|
+
|
|
426
|
+
def __str__(self) -> str:
|
|
427
|
+
"""String representation: show floating-point version for backward compatibility."""
|
|
428
|
+
p, q = to_rational(self._e)
|
|
429
|
+
if q == 0: return 'nan'
|
|
430
|
+
# Convert to float for display (maintains backward compatibility)
|
|
431
|
+
return str(p / q)
|
|
432
|
+
|
|
433
|
+
def __repr__(self) -> str:
|
|
434
|
+
"""Repr: show as td() constructor for clarity."""
|
|
435
|
+
s = to_str(self._e)
|
|
436
|
+
return f'td({s!r})' # concise; use .fields() for full breakdown
|
|
437
|
+
|
|
438
|
+
def __format__(self, spec: str) -> str:
|
|
439
|
+
if not spec:
|
|
440
|
+
return str(self)
|
|
441
|
+
# Support basic format specs by going through float
|
|
442
|
+
return format(float(self), spec)
|
|
443
|
+
|
|
444
|
+
# ── Class-level constants ──────────────────────────────────────────
|
|
445
|
+
|
|
446
|
+
NAN : 'TDFloat'
|
|
447
|
+
INF : 'TDFloat'
|
|
448
|
+
NINF : 'TDFloat'
|
|
449
|
+
ZERO : 'TDFloat'
|
|
450
|
+
ONE : 'TDFloat'
|
|
451
|
+
TWO : 'TDFloat'
|
|
452
|
+
HALF : 'TDFloat'
|
|
453
|
+
PI : 'TDFloat'
|
|
454
|
+
TAU : 'TDFloat'
|
|
455
|
+
E : 'TDFloat'
|
|
456
|
+
PHI : 'TDFloat'
|
|
457
|
+
SQRT2: 'TDFloat'
|
|
458
|
+
SQRT3: 'TDFloat'
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
# Set class-level constants after class definition
|
|
462
|
+
TDFloat.NAN = TDFloat(_NAN)
|
|
463
|
+
TDFloat.INF = TDFloat(_PINF)
|
|
464
|
+
TDFloat.NINF = TDFloat(_NINF)
|
|
465
|
+
TDFloat.ZERO = TDFloat.from_int(0)
|
|
466
|
+
TDFloat.ONE = TDFloat.from_int(1)
|
|
467
|
+
TDFloat.TWO = TDFloat.from_int(2)
|
|
468
|
+
TDFloat.HALF = TDFloat.from_frac(1, 2)
|
|
469
|
+
TDFloat.PI = TDFloat.from_frac(22, 7)
|
|
470
|
+
TDFloat.TAU = TDFloat.from_frac(44, 7)
|
|
471
|
+
TDFloat.E = TDFloat.from_frac(19, 7)
|
|
472
|
+
TDFloat.PHI = TDFloat.from_frac(3, 2)
|
|
473
|
+
TDFloat.SQRT2 = TDFloat.from_frac(7, 5)
|
|
474
|
+
TDFloat.SQRT3 = TDFloat.from_frac(7, 4)
|
|
475
|
+
|
|
476
|
+
|
|
477
|
+
# ── Coercion helper ───────────────────────────────────────────────────
|
|
478
|
+
|
|
479
|
+
def _coerce(x) -> TDFloat:
|
|
480
|
+
if isinstance(x, TDFloat): return x
|
|
481
|
+
if isinstance(x, int): return TDFloat.from_int(x)
|
|
482
|
+
if isinstance(x, str): return TDFloat.from_str(x)
|
|
483
|
+
if isinstance(x, float): return TDFloat.from_float(x)
|
|
484
|
+
raise TypeError(f"Cannot convert {type(x).__name__} to TDFloat")
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
# ── Module-level convenience ──────────────────────────────────────────
|
|
488
|
+
|
|
489
|
+
def td(x) -> TDFloat:
|
|
490
|
+
"""
|
|
491
|
+
Create a TDFloat from int, str, float, or fraction tuple.
|
|
492
|
+
|
|
493
|
+
Examples:
|
|
494
|
+
td(3) → integer 3 (integer axis)
|
|
495
|
+
td('3.14') → 3.14 (half-step axis)
|
|
496
|
+
td(0.1) → exact binary value of Python's 0.1
|
|
497
|
+
td((1, 3)) → 1/3 (half-step axis, repeating)
|
|
498
|
+
"""
|
|
499
|
+
if isinstance(x, TDFloat): return x
|
|
500
|
+
if isinstance(x, tuple) and len(x) == 2:
|
|
501
|
+
return TDFloat.from_frac(x[0], x[1])
|
|
502
|
+
return _coerce(x)
|
|
503
|
+
|
|
504
|
+
def frac(p: int, q: int) -> TDFloat:
|
|
505
|
+
"""Exact rational p/q."""
|
|
506
|
+
return TDFloat.from_frac(p, q)
|
|
507
|
+
|
|
508
|
+
def divmod_td(a, b) -> tuple:
|
|
509
|
+
"""Three-term division: returns (quotient, remainder, denominator).
|
|
510
|
+
|
|
511
|
+
Returns the complete quotient-remainder decomposition:
|
|
512
|
+
a/b = quotient + remainder/denominator
|
|
513
|
+
|
|
514
|
+
All three terms are TDFloat values. This is the full triadic division
|
|
515
|
+
semantic that exposes the quotient, remainder, and external denominator.
|
|
516
|
+
|
|
517
|
+
Args:
|
|
518
|
+
a, b: TDFloat or coercible to TDFloat
|
|
519
|
+
|
|
520
|
+
Returns:
|
|
521
|
+
(quotient, remainder, denominator) as a tuple of TDFloat values
|
|
522
|
+
|
|
523
|
+
Examples:
|
|
524
|
+
>>> q, r, d = divmod_td(frac(7, 1), frac(3, 1))
|
|
525
|
+
>>> q, r, d
|
|
526
|
+
(td(2), td(1), td(3))
|
|
527
|
+
>>> q + r / d # Reconstruct: 2 + 1/3 = 7/3
|
|
528
|
+
td('2.333...')
|
|
529
|
+
"""
|
|
530
|
+
a = td(a)
|
|
531
|
+
b = td(b)
|
|
532
|
+
return a.divmod_td(b)
|