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.
@@ -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)