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,64 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tdfloat — Triadic Dot Float
|
|
3
|
+
============================
|
|
4
|
+
A floating-point library where every number is a single Python integer
|
|
5
|
+
on the half-step rational line.
|
|
6
|
+
|
|
7
|
+
The "." is the info_bit (from encoding_any_symbol.v):
|
|
8
|
+
position = 2 * rank + info_bit
|
|
9
|
+
info_bit = 0 → integer axis (no dot)
|
|
10
|
+
info_bit = 1 → half-step axis (has dot)
|
|
11
|
+
|
|
12
|
+
All arithmetic is exact rational arithmetic.
|
|
13
|
+
No IEEE 754 rounding. No transcendentals. No approximation.
|
|
14
|
+
|
|
15
|
+
Quick start
|
|
16
|
+
-----------
|
|
17
|
+
from valuebridge.tdfloat import td, frac, TDFloat
|
|
18
|
+
|
|
19
|
+
a = td('0.1')
|
|
20
|
+
b = td('0.2')
|
|
21
|
+
c = td('0.3')
|
|
22
|
+
assert (a + b) + c == a + (b + c) # always True
|
|
23
|
+
|
|
24
|
+
print(TDFloat.PI) # 22/7 (exact definition of π)
|
|
25
|
+
print(TDFloat.E) # 19/7
|
|
26
|
+
print(TDFloat.PHI) # 3/2 (the golden ratio = the half-step)
|
|
27
|
+
|
|
28
|
+
from valuebridge.tdfloat.math import circle_area, circle_circumference
|
|
29
|
+
r7 = td(7)
|
|
30
|
+
print(circle_circumference(r7)) # 44 (exact integer!)
|
|
31
|
+
print(circle_area(r7)) # 154 (exact integer!)
|
|
32
|
+
|
|
33
|
+
Constants
|
|
34
|
+
---------
|
|
35
|
+
All constants are exact rationals:
|
|
36
|
+
π = 22/7, τ = 44/7, e = 19/7, φ = 3/2, √2 = 7/5, ½ = 1/2
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
from .tdfloat import TDFloat, td, frac, divmod_td
|
|
40
|
+
from . import math
|
|
41
|
+
from . import constants
|
|
42
|
+
|
|
43
|
+
# Expose constants at package level
|
|
44
|
+
PI = TDFloat.PI
|
|
45
|
+
TAU = TDFloat.TAU
|
|
46
|
+
E = TDFloat.E
|
|
47
|
+
PHI = TDFloat.PHI
|
|
48
|
+
SQRT2 = TDFloat.SQRT2
|
|
49
|
+
SQRT3 = TDFloat.SQRT3
|
|
50
|
+
HALF = TDFloat.HALF
|
|
51
|
+
ZERO = TDFloat.ZERO
|
|
52
|
+
ONE = TDFloat.ONE
|
|
53
|
+
TWO = TDFloat.TWO
|
|
54
|
+
NAN = TDFloat.NAN
|
|
55
|
+
INF = TDFloat.INF
|
|
56
|
+
|
|
57
|
+
__version__ = '1.0.0'
|
|
58
|
+
__author__ = 'Tushar Dadlani'
|
|
59
|
+
__all__ = [
|
|
60
|
+
'TDFloat', 'td', 'frac', 'divmod_td',
|
|
61
|
+
'PI', 'TAU', 'E', 'PHI', 'SQRT2', 'SQRT3', 'HALF',
|
|
62
|
+
'ZERO', 'ONE', 'TWO', 'NAN', 'INF',
|
|
63
|
+
'math', 'constants',
|
|
64
|
+
]
|
|
@@ -0,0 +1,258 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tdfloat._arithmetic
|
|
3
|
+
===================
|
|
4
|
+
Exact rational arithmetic on encoded half-step integers.
|
|
5
|
+
|
|
6
|
+
Every operation goes: encoded → (p,q) → exact integer arithmetic → encoded.
|
|
7
|
+
No Python float is ever used.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from ._encoding import (
|
|
11
|
+
_NAN, _PINF, _NINF, _SPECIALS,
|
|
12
|
+
to_rational, from_rational,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
# Try to use Rust-accelerated versions
|
|
16
|
+
try:
|
|
17
|
+
from tdfloat_core import td_gcd, td_tddiv
|
|
18
|
+
_RUST_ARITHMETIC = True
|
|
19
|
+
except ImportError:
|
|
20
|
+
_RUST_ARITHMETIC = False
|
|
21
|
+
td_tddiv = None
|
|
22
|
+
|
|
23
|
+
def _sign_of(encoded: int) -> int:
|
|
24
|
+
return 1 if encoded < 0 and encoded not in _SPECIALS else 0
|
|
25
|
+
|
|
26
|
+
def _gcd(a: int, b: int) -> int:
|
|
27
|
+
if _RUST_ARITHMETIC:
|
|
28
|
+
# Use Rust GCD for unsigned integers only
|
|
29
|
+
try:
|
|
30
|
+
return td_gcd(a, b)
|
|
31
|
+
except (OverflowError, ValueError):
|
|
32
|
+
# Fall back to Python if Rust can't handle it
|
|
33
|
+
while b: a, b = b, a % b
|
|
34
|
+
return a
|
|
35
|
+
while b: a, b = b, a % b
|
|
36
|
+
return a
|
|
37
|
+
|
|
38
|
+
def _simplify(p: int, q: int) -> tuple:
|
|
39
|
+
# Use Python _simplify to avoid overflow issues with large numbers
|
|
40
|
+
# (Python uses arbitrary-precision integers)
|
|
41
|
+
if q < 0: p, q = -p, -q
|
|
42
|
+
g = _gcd(abs(p), q) if q else 1
|
|
43
|
+
return p // g, q // g
|
|
44
|
+
|
|
45
|
+
def add(a: int, b: int) -> int:
|
|
46
|
+
if _NAN in (a, b): return _NAN
|
|
47
|
+
if a == _PINF: return _NAN if b == _NINF else _PINF
|
|
48
|
+
if a == _NINF: return _NAN if b == _PINF else _NINF
|
|
49
|
+
if b in (_PINF, _NINF): return b
|
|
50
|
+
na, da = to_rational(a)
|
|
51
|
+
nb, db = to_rational(b)
|
|
52
|
+
p, q = _simplify(na * db + nb * da, da * db)
|
|
53
|
+
return from_rational(p, q)
|
|
54
|
+
|
|
55
|
+
def sub(a: int, b: int) -> int:
|
|
56
|
+
return add(a, neg(b))
|
|
57
|
+
|
|
58
|
+
def neg(a: int) -> int:
|
|
59
|
+
if a == _NAN: return _NAN
|
|
60
|
+
if a == _PINF: return _NINF
|
|
61
|
+
if a == _NINF: return _PINF
|
|
62
|
+
return -a # sign lives in Python int sign
|
|
63
|
+
|
|
64
|
+
def mul(a: int, b: int) -> int:
|
|
65
|
+
if _NAN in (a, b): return _NAN
|
|
66
|
+
if a in (_PINF, _NINF) or b in (_PINF, _NINF):
|
|
67
|
+
sa = 1 if (a == _NINF or (a < 0 and a not in _SPECIALS)) else 0
|
|
68
|
+
sb = 1 if (b == _NINF or (b < 0 and b not in _SPECIALS)) else 0
|
|
69
|
+
return _NINF if (sa ^ sb) else _PINF
|
|
70
|
+
na, da = to_rational(a)
|
|
71
|
+
nb, db = to_rational(b)
|
|
72
|
+
p, q = _simplify(na * nb, da * db)
|
|
73
|
+
return from_rational(p, q)
|
|
74
|
+
|
|
75
|
+
def div(a: int, b: int) -> int:
|
|
76
|
+
"""Division using tddiv internally.
|
|
77
|
+
Returns the full rational result: quotient + remainder/denominator as encoded value.
|
|
78
|
+
"""
|
|
79
|
+
if _NAN in (a, b): return _NAN
|
|
80
|
+
nb, db = to_rational(b)
|
|
81
|
+
if nb == 0:
|
|
82
|
+
# division by zero: return signed infinity based on sign of a
|
|
83
|
+
na, da = to_rational(a)
|
|
84
|
+
if na == 0: return _NAN # 0/0 = nan
|
|
85
|
+
return _NINF if na < 0 else _PINF
|
|
86
|
+
|
|
87
|
+
# Use tddiv internally for exact three-term semantics
|
|
88
|
+
quot_e, rem_e, den_e = divmod_td(a, b)
|
|
89
|
+
|
|
90
|
+
# Reconstruct: result = quotient + remainder/denominator as a single encoded value
|
|
91
|
+
if rem_e == 0:
|
|
92
|
+
return quot_e
|
|
93
|
+
|
|
94
|
+
# Extract the rational components
|
|
95
|
+
# Note: divmod_td returns integers encoded as rationals with denominator 1
|
|
96
|
+
p_q, q_q = to_rational(quot_e) # quotient as integer
|
|
97
|
+
p_r, q_r = to_rational(rem_e) # remainder as integer
|
|
98
|
+
p_d, q_d = to_rational(den_e) # denominator as integer
|
|
99
|
+
|
|
100
|
+
# Compute: result = quotient + remainder/denominator
|
|
101
|
+
# = (quotient * denominator + remainder) / denominator
|
|
102
|
+
# Since q_q = q_r = q_d = 1 (they're all integers):
|
|
103
|
+
num = p_q * p_d + p_r
|
|
104
|
+
den = p_d
|
|
105
|
+
p, q = _simplify(num, den)
|
|
106
|
+
return from_rational(p, q)
|
|
107
|
+
|
|
108
|
+
def floordiv(a: int, b: int) -> int:
|
|
109
|
+
"""Floor division using tddiv internally.
|
|
110
|
+
Returns only the quotient part (whole number).
|
|
111
|
+
"""
|
|
112
|
+
if _NAN in (a, b) or b in (_PINF, _NINF): return _NAN
|
|
113
|
+
nb, db = to_rational(b)
|
|
114
|
+
if nb == 0: return _NAN
|
|
115
|
+
|
|
116
|
+
# Use tddiv to get quotient directly
|
|
117
|
+
quot_e, rem_e, den_e = divmod_td(a, b)
|
|
118
|
+
return quot_e
|
|
119
|
+
|
|
120
|
+
def mod(a: int, b: int) -> int:
|
|
121
|
+
"""Modulo using tddiv internally.
|
|
122
|
+
Computes: a mod b = a - b * floor(a/b)
|
|
123
|
+
Returns the remainder from the division.
|
|
124
|
+
"""
|
|
125
|
+
if _NAN in (a, b) or b in (_PINF, _NINF): return _NAN
|
|
126
|
+
nb, db = to_rational(b)
|
|
127
|
+
if nb == 0: return _NAN
|
|
128
|
+
|
|
129
|
+
# Use tddiv to get quotient directly
|
|
130
|
+
quot_e, rem_e, den_e = divmod_td(a, b)
|
|
131
|
+
|
|
132
|
+
# Compute: a - b * quotient
|
|
133
|
+
return sub(a, mul(b, quot_e))
|
|
134
|
+
|
|
135
|
+
def divmod_td(a: int, b: int) -> tuple:
|
|
136
|
+
"""Three-term division: returns (quotient, remainder, denominator).
|
|
137
|
+
|
|
138
|
+
Semantics: a/b = quotient + remainder/denominator
|
|
139
|
+
All three terms are encoded as half-step integers.
|
|
140
|
+
|
|
141
|
+
Args:
|
|
142
|
+
a, b: encoded half-step integers
|
|
143
|
+
|
|
144
|
+
Returns:
|
|
145
|
+
(quotient_enc, remainder_enc, denominator_enc) where each is encoded
|
|
146
|
+
"""
|
|
147
|
+
if _NAN in (a, b): return (_NAN, _NAN, _NAN)
|
|
148
|
+
if b in (_PINF, _NINF) or b == 0: return (_NAN, _NAN, _NAN)
|
|
149
|
+
if a == 0: return (0, 0, 1) # 0/b = (0, 0, 1)
|
|
150
|
+
|
|
151
|
+
if _RUST_ARITHMETIC and td_tddiv is not None:
|
|
152
|
+
return td_tddiv(a, b)
|
|
153
|
+
|
|
154
|
+
# Pure Python fallback (less efficient but correct)
|
|
155
|
+
na, da = to_rational(a)
|
|
156
|
+
nb, db = to_rational(b)
|
|
157
|
+
|
|
158
|
+
# a/b = (na/da) / (nb/db) = (na*db) / (da*nb)
|
|
159
|
+
p = na * db
|
|
160
|
+
q = da * nb
|
|
161
|
+
|
|
162
|
+
# Reduce via GCD
|
|
163
|
+
from math import gcd as py_gcd
|
|
164
|
+
g = py_gcd(abs(p), abs(q))
|
|
165
|
+
p, q = p // g, q // g
|
|
166
|
+
|
|
167
|
+
# Ensure q is positive
|
|
168
|
+
if q < 0:
|
|
169
|
+
p, q = -p, -q
|
|
170
|
+
|
|
171
|
+
# Get quotient and remainder
|
|
172
|
+
quot = p // q
|
|
173
|
+
rem = p % q
|
|
174
|
+
|
|
175
|
+
# Encode all three terms
|
|
176
|
+
sign_q = quot < 0
|
|
177
|
+
quot_enc = from_rational(abs(quot), 1) if quot != 0 else 0
|
|
178
|
+
if sign_q and quot != 0:
|
|
179
|
+
quot_enc = -quot_enc if quot_enc >= 0 else quot_enc
|
|
180
|
+
|
|
181
|
+
sign_r = rem < 0
|
|
182
|
+
rem_enc = from_rational(abs(rem), 1) if rem != 0 else 0
|
|
183
|
+
if sign_r and rem != 0:
|
|
184
|
+
rem_enc = -rem_enc if rem_enc >= 0 else rem_enc
|
|
185
|
+
|
|
186
|
+
den_enc = from_rational(q, 1)
|
|
187
|
+
|
|
188
|
+
return (quot_enc, rem_enc, den_enc)
|
|
189
|
+
|
|
190
|
+
def eq(a: int, b: int) -> bool:
|
|
191
|
+
if a in _SPECIALS or b in _SPECIALS:
|
|
192
|
+
return a == b and a != _NAN
|
|
193
|
+
na, da = to_rational(a)
|
|
194
|
+
nb, db = to_rational(b)
|
|
195
|
+
return na * db == nb * da
|
|
196
|
+
|
|
197
|
+
def lt(a: int, b: int) -> bool:
|
|
198
|
+
if _NAN in (a, b): return False
|
|
199
|
+
if a == _NINF: return b != _NINF
|
|
200
|
+
if b == _PINF: return a != _PINF
|
|
201
|
+
if a == _PINF or b == _NINF: return False
|
|
202
|
+
na, da = to_rational(a)
|
|
203
|
+
nb, db = to_rational(b)
|
|
204
|
+
# na/da < nb/db iff na*db < nb*da (both denominators positive)
|
|
205
|
+
return na * db < nb * da
|
|
206
|
+
|
|
207
|
+
def abs_val(a: int) -> int:
|
|
208
|
+
if a in _SPECIALS: return a
|
|
209
|
+
return -a if a < 0 else a
|
|
210
|
+
|
|
211
|
+
def pow_int(a: int, n: int) -> int:
|
|
212
|
+
"""Integer power via repeated squaring."""
|
|
213
|
+
if n < 0:
|
|
214
|
+
return div(from_rational(1, 1), pow_int(a, -n))
|
|
215
|
+
result = from_rational(1, 1)
|
|
216
|
+
base = a
|
|
217
|
+
while n:
|
|
218
|
+
if n & 1: result = mul(result, base)
|
|
219
|
+
base = mul(base, base)
|
|
220
|
+
n >>= 1
|
|
221
|
+
return result
|
|
222
|
+
|
|
223
|
+
def isqrt_rational(p: int, q: int) -> tuple:
|
|
224
|
+
"""
|
|
225
|
+
Integer square root of p/q as a rational.
|
|
226
|
+
Returns (rp, rq) such that (rp/rq)^2 ≈ p/q.
|
|
227
|
+
Uses Newton's method on integers.
|
|
228
|
+
"""
|
|
229
|
+
if p < 0: return (0, 0) # NaN
|
|
230
|
+
if p == 0: return (0, 1)
|
|
231
|
+
|
|
232
|
+
# sqrt(p/q) = sqrt(p*q) / q
|
|
233
|
+
# We compute isqrt(p*q) then divide by q
|
|
234
|
+
target = p * q
|
|
235
|
+
# Scale up for precision: sqrt(target * 10^(2k)) = sqrt(target) * 10^k
|
|
236
|
+
PREC = 80 # decimal digits of precision
|
|
237
|
+
scale = 10 ** (PREC * 2)
|
|
238
|
+
n = target * scale
|
|
239
|
+
x = 1 << (n.bit_length() // 2 + 1)
|
|
240
|
+
while True:
|
|
241
|
+
x1 = (x + n // x) >> 1
|
|
242
|
+
if x1 >= x: break
|
|
243
|
+
x = x1
|
|
244
|
+
# result = x / (sqrt(scale) * q) = x / (10^PREC * q)
|
|
245
|
+
rp, rq = _simplify(x, (10 ** PREC) * q)
|
|
246
|
+
return rp, rq
|
|
247
|
+
|
|
248
|
+
def sqrt(a: int) -> int:
|
|
249
|
+
"""Square root via exact rational Newton's method."""
|
|
250
|
+
from ._encoding import _NAN, _PINF, _NINF
|
|
251
|
+
if a == _NAN: return _NAN
|
|
252
|
+
if a == _NINF: return _NAN
|
|
253
|
+
if a == _PINF: return _PINF
|
|
254
|
+
na, da = to_rational(a)
|
|
255
|
+
if na < 0: return _NAN
|
|
256
|
+
if na == 0: return from_rational(0, 1)
|
|
257
|
+
rp, rq = isqrt_rational(na, da)
|
|
258
|
+
return from_rational(rp, rq)
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tdfloat._encoding
|
|
3
|
+
=================
|
|
4
|
+
The half-step symbol encoding from encoding_any_symbol.v
|
|
5
|
+
|
|
6
|
+
position = 2 * rank + info_bit encode_symbol
|
|
7
|
+
rank = position >> 1 decode_rank
|
|
8
|
+
info_bit = position & 1 decode_info
|
|
9
|
+
|
|
10
|
+
The "." is the info_bit:
|
|
11
|
+
info_bit = 0 → integer axis (even position, no dot)
|
|
12
|
+
info_bit = 1 → half-step axis (odd position, has dot)
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
# ── Layout ────────────────────────────────────────────────────────────
|
|
16
|
+
MAX_DOT_POS = 77 # maximum fractional decimal places
|
|
17
|
+
DOT_POSITIONS = MAX_DOT_POS + 1 # 78 distinct scale values (0..77)
|
|
18
|
+
|
|
19
|
+
# Sentinel values for special numbers (outside the half-step line)
|
|
20
|
+
_NAN = -1
|
|
21
|
+
_PINF = -2
|
|
22
|
+
_NINF = -3
|
|
23
|
+
_SPECIALS = (_NAN, _PINF, _NINF)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# ── The three primitives from encoding_any_symbol.v ──────────────────
|
|
27
|
+
|
|
28
|
+
def encode_symbol(rank: int, info_bit: int) -> int:
|
|
29
|
+
"""position = 2 * rank + info_bit"""
|
|
30
|
+
return 2 * rank + (info_bit & 1)
|
|
31
|
+
|
|
32
|
+
def decode_rank(position: int) -> int:
|
|
33
|
+
"""rank = position // 2"""
|
|
34
|
+
return position >> 1
|
|
35
|
+
|
|
36
|
+
def decode_info(position: int) -> int:
|
|
37
|
+
"""info_bit = position mod 2 (0=integer axis, 1=half-step axis)"""
|
|
38
|
+
return position & 1
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
# ── Pack / unpack a float as (sign, abs_digits, dot_pos) ─────────────
|
|
42
|
+
|
|
43
|
+
def pack(sign: int, abs_digits: int, dot_pos: int) -> int:
|
|
44
|
+
"""
|
|
45
|
+
Encode (sign, digits, dot_pos) as a single integer on the half-step line.
|
|
46
|
+
sign=0 positive, sign=1 negative.
|
|
47
|
+
dot_pos=0 means integer (no dot, info_bit=0).
|
|
48
|
+
dot_pos>0 means fractional (dot present, info_bit=1).
|
|
49
|
+
"""
|
|
50
|
+
assert 0 <= dot_pos <= MAX_DOT_POS, f"dot_pos {dot_pos} out of range"
|
|
51
|
+
info_bit = 1 if dot_pos > 0 else 0
|
|
52
|
+
rank = abs_digits * DOT_POSITIONS + dot_pos
|
|
53
|
+
position = encode_symbol(rank, info_bit)
|
|
54
|
+
return -position if sign else position
|
|
55
|
+
|
|
56
|
+
def unpack(encoded: int) -> tuple:
|
|
57
|
+
"""Return (sign, abs_digits, dot_pos). sign=0 positive, 1 negative."""
|
|
58
|
+
if encoded < 0:
|
|
59
|
+
sign, position = 1, -encoded
|
|
60
|
+
else:
|
|
61
|
+
sign, position = 0, encoded
|
|
62
|
+
rank = decode_rank(position)
|
|
63
|
+
dot_pos = rank % DOT_POSITIONS
|
|
64
|
+
abs_digits = rank // DOT_POSITIONS
|
|
65
|
+
return sign, abs_digits, dot_pos
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
# ── Exact rational ↔ encoded int ─────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
def to_rational(encoded: int) -> tuple:
|
|
71
|
+
"""Return (numerator, denominator). numerator may be negative."""
|
|
72
|
+
if encoded in _SPECIALS:
|
|
73
|
+
return (0, 0)
|
|
74
|
+
sign, digits, dot_pos = unpack(encoded)
|
|
75
|
+
denom = 10 ** dot_pos
|
|
76
|
+
return (-digits if sign else digits), denom
|
|
77
|
+
|
|
78
|
+
def from_rational(num: int, den: int) -> int:
|
|
79
|
+
"""Build encoded int from exact integer ratio num/den.
|
|
80
|
+
Uses pure Python with arbitrary-precision integers to handle all rationals.
|
|
81
|
+
"""
|
|
82
|
+
if den == 0:
|
|
83
|
+
return _NAN
|
|
84
|
+
if num == 0:
|
|
85
|
+
return pack(0, 0, 0)
|
|
86
|
+
|
|
87
|
+
sign = 1 if num < 0 else 0
|
|
88
|
+
num = abs(num)
|
|
89
|
+
|
|
90
|
+
# Find shortest exact decimal representation up to MAX_DOT_POS
|
|
91
|
+
for dot_pos in range(MAX_DOT_POS + 1):
|
|
92
|
+
scaled = num * (10 ** dot_pos)
|
|
93
|
+
if scaled % den == 0:
|
|
94
|
+
return pack(sign, scaled // den, dot_pos)
|
|
95
|
+
|
|
96
|
+
# Inexact — round to MAX_DOT_POS places
|
|
97
|
+
scaled = num * (10 ** MAX_DOT_POS)
|
|
98
|
+
digits = (scaled + den // 2) // den
|
|
99
|
+
return pack(sign, digits, MAX_DOT_POS)
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
# ── String ↔ encoded int ──────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
def from_str(s: str) -> int:
|
|
105
|
+
"""Parse decimal string to encoded int. No Python float used."""
|
|
106
|
+
s = s.strip().lower()
|
|
107
|
+
for alias, val in (('inf',_PINF),('+inf',_PINF),('-inf',_NINF),('nan',_NAN)):
|
|
108
|
+
if s == alias: return val
|
|
109
|
+
|
|
110
|
+
sign = 0
|
|
111
|
+
if s.startswith('-'): sign = 1; s = s[1:]
|
|
112
|
+
elif s.startswith('+'): s = s[1:]
|
|
113
|
+
|
|
114
|
+
exp10 = 0
|
|
115
|
+
if 'e' in s:
|
|
116
|
+
s, ep = s.split('e', 1); exp10 = int(ep)
|
|
117
|
+
|
|
118
|
+
has_dot = '.' in s
|
|
119
|
+
if has_dot:
|
|
120
|
+
ipart, fpart = s.split('.', 1)
|
|
121
|
+
else:
|
|
122
|
+
ipart, fpart = s, ''
|
|
123
|
+
|
|
124
|
+
ipart = ipart or '0'
|
|
125
|
+
digits = int(ipart + fpart) if (ipart + fpart) else 0
|
|
126
|
+
|
|
127
|
+
raw_dot = len(fpart) - exp10
|
|
128
|
+
|
|
129
|
+
if raw_dot < 0:
|
|
130
|
+
digits *= 10 ** (-raw_dot)
|
|
131
|
+
dot_pos = 0
|
|
132
|
+
elif raw_dot > MAX_DOT_POS:
|
|
133
|
+
excess = raw_dot - MAX_DOT_POS
|
|
134
|
+
digits = (digits + 10**(excess-1)*5) // (10 ** excess)
|
|
135
|
+
dot_pos = MAX_DOT_POS
|
|
136
|
+
else:
|
|
137
|
+
dot_pos = raw_dot
|
|
138
|
+
|
|
139
|
+
# Strip trailing fractional zeros, but preserve the dot presence
|
|
140
|
+
min_dot = 1 if has_dot or exp10 < 0 else 0
|
|
141
|
+
while dot_pos > min_dot and digits % 10 == 0:
|
|
142
|
+
digits //= 10
|
|
143
|
+
dot_pos -= 1
|
|
144
|
+
|
|
145
|
+
return pack(sign, digits, dot_pos)
|
|
146
|
+
|
|
147
|
+
def to_str(encoded: int, max_digits: int = 40) -> str:
|
|
148
|
+
"""Convert encoded int to decimal string."""
|
|
149
|
+
if encoded == _NAN: return 'nan'
|
|
150
|
+
if encoded == _PINF: return 'inf'
|
|
151
|
+
if encoded == _NINF: return '-inf'
|
|
152
|
+
sign, digits, dot_pos = unpack(encoded)
|
|
153
|
+
s = '-' if sign else ''
|
|
154
|
+
ds = str(digits)
|
|
155
|
+
if dot_pos == 0:
|
|
156
|
+
return s + ds + '.0' if False else s + ds # integers: no trailing .0
|
|
157
|
+
if len(ds) <= dot_pos:
|
|
158
|
+
ds = '0' * (dot_pos - len(ds) + 1) + ds
|
|
159
|
+
int_part = ds[:-dot_pos] or '0'
|
|
160
|
+
frac_part = ds[-dot_pos:].rstrip('0') or '0'
|
|
161
|
+
return s + int_part + '.' + frac_part
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
"""
|
|
2
|
+
tdfloat.constants
|
|
3
|
+
=================
|
|
4
|
+
Every mathematical constant defined as an exact rational p/q.
|
|
5
|
+
|
|
6
|
+
In the tdfloat universe there are no transcendentals.
|
|
7
|
+
Every constant occupies a unique, permanent position on the half-step line.
|
|
8
|
+
The "." is the info_bit; its presence places a constant on the half-step axis.
|
|
9
|
+
|
|
10
|
+
Constant definitions
|
|
11
|
+
--------------------
|
|
12
|
+
π = 22/7 circumference/diameter in the 45°-rotated Gaussian plane
|
|
13
|
+
τ = 44/7 full rotation (2π)
|
|
14
|
+
e = 19/7 natural growth base in 3-step algebra
|
|
15
|
+
ln2 = 2/3 half-life constant
|
|
16
|
+
φ = 3/2 golden ratio = the fundamental half-step
|
|
17
|
+
√2 = 7/5 diagonal of unit square (first CF convergent)
|
|
18
|
+
√3 = 7/4 equilateral triangle height ratio
|
|
19
|
+
√5 = 9/4 from the golden ratio construction
|
|
20
|
+
½ = 1/2 the half-step atom itself
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
from __future__ import annotations
|
|
24
|
+
from ._encoding import from_rational, _NAN
|
|
25
|
+
|
|
26
|
+
def _gcd(a, b):
|
|
27
|
+
while b: a, b = b, a % b
|
|
28
|
+
return a
|
|
29
|
+
|
|
30
|
+
def _s(p, q):
|
|
31
|
+
if q < 0: p, q = -p, -q
|
|
32
|
+
g = _gcd(abs(p), q)
|
|
33
|
+
return p // g, q // g
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class Constant:
|
|
37
|
+
"""
|
|
38
|
+
A named rational constant.
|
|
39
|
+
Stores (p, q) as the canonical fraction and provides
|
|
40
|
+
exact arithmetic without going through decimal encoding.
|
|
41
|
+
"""
|
|
42
|
+
__slots__ = ('name', 'p', 'q', '_encoded')
|
|
43
|
+
|
|
44
|
+
def __init__(self, name: str, p: int, q: int):
|
|
45
|
+
self.name = name
|
|
46
|
+
self.p, self.q = _s(p, q)
|
|
47
|
+
self._encoded = from_rational(self.p, self.q)
|
|
48
|
+
|
|
49
|
+
def encoded(self) -> int:
|
|
50
|
+
return self._encoded
|
|
51
|
+
|
|
52
|
+
def as_tdf(self):
|
|
53
|
+
"""Return this constant as a TDFloat."""
|
|
54
|
+
from .tdfloat import TDFloat
|
|
55
|
+
return TDFloat(self._encoded)
|
|
56
|
+
|
|
57
|
+
def __repr__(self) -> str:
|
|
58
|
+
from ._encoding import to_str
|
|
59
|
+
return f'{self.name} = {self.p}/{self.q} ({to_str(self._encoded)!r})'
|
|
60
|
+
|
|
61
|
+
def __float__(self) -> float:
|
|
62
|
+
return self.p / self.q
|
|
63
|
+
|
|
64
|
+
# Exact arithmetic returning (p, q) tuples
|
|
65
|
+
def __mul__(self, other: 'Constant') -> 'Constant':
|
|
66
|
+
p, q = _s(self.p * other.p, self.q * other.q)
|
|
67
|
+
return Constant(f'{self.name}*{other.name}', p, q)
|
|
68
|
+
|
|
69
|
+
def __add__(self, other: 'Constant') -> 'Constant':
|
|
70
|
+
p, q = _s(self.p * other.q + other.p * self.q, self.q * other.q)
|
|
71
|
+
return Constant(f'{self.name}+{other.name}', p, q)
|
|
72
|
+
|
|
73
|
+
def __sub__(self, other: 'Constant') -> 'Constant':
|
|
74
|
+
p, q = _s(self.p * other.q - other.p * self.q, self.q * other.q)
|
|
75
|
+
return Constant(f'{self.name}-{other.name}', p, q)
|
|
76
|
+
|
|
77
|
+
def __truediv__(self, other: 'Constant') -> 'Constant':
|
|
78
|
+
p, q = _s(self.p * other.q, self.q * other.p)
|
|
79
|
+
return Constant(f'{self.name}/{other.name}', p, q)
|
|
80
|
+
|
|
81
|
+
def __neg__(self) -> 'Constant':
|
|
82
|
+
return Constant(f'-{self.name}', -self.p, self.q)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
# ── The registry ──────────────────────────────────────────────────────
|
|
86
|
+
|
|
87
|
+
PI = Constant('π', 22, 7) # circumference / diameter
|
|
88
|
+
TAU = Constant('τ', 44, 7) # 2π
|
|
89
|
+
E = Constant('e', 19, 7) # natural base
|
|
90
|
+
LN2 = Constant('ln2', 2, 3) # log(2)
|
|
91
|
+
LN10 = Constant('ln10', 7, 3) # log(10)
|
|
92
|
+
LOG2E = Constant('log2e', 3, 2) # log₂(e) = 1/ln2 = φ
|
|
93
|
+
SQRT2 = Constant('√2', 7, 5) # √2
|
|
94
|
+
SQRT3 = Constant('√3', 7, 4) # √3
|
|
95
|
+
SQRT5 = Constant('√5', 9, 4) # √5
|
|
96
|
+
PHI = Constant('φ', 3, 2) # golden ratio = the half-step
|
|
97
|
+
SIN45 = Constant('sin45', 1, 2) # sin(45°) in identity-axis geometry
|
|
98
|
+
COS45 = Constant('cos45', 1, 2)
|
|
99
|
+
SIN30 = Constant('sin30', 1, 2)
|
|
100
|
+
COS30 = Constant('cos30', 7, 8) # √3/2
|
|
101
|
+
SIN60 = Constant('sin60', 7, 8)
|
|
102
|
+
COS60 = Constant('cos60', 1, 2)
|
|
103
|
+
HALF = Constant('½', 1, 2) # the fundamental half-step atom
|
|
104
|
+
ALPHA = Constant('α', 1, 137) # fine structure constant
|
|
105
|
+
|
|
106
|
+
ALL: dict[str, Constant] = {
|
|
107
|
+
'pi': PI, 'tau': TAU, 'e': E, 'ln2': LN2, 'ln10': LN10,
|
|
108
|
+
'log2e': LOG2E, 'sqrt2': SQRT2, 'sqrt3': SQRT3, 'sqrt5': SQRT5,
|
|
109
|
+
'phi': PHI, 'sin45': SIN45, 'cos45': COS45, 'sin30': SIN30,
|
|
110
|
+
'cos30': COS30, 'sin60': SIN60, 'cos60': COS60,
|
|
111
|
+
'half': HALF, 'alpha': ALPHA,
|
|
112
|
+
}
|