foxmath 0.2.0__tar.gz → 0.3.0__tar.gz
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.
- {foxmath-0.2.0/src/foxmath.egg-info → foxmath-0.3.0}/PKG-INFO +15 -3
- {foxmath-0.2.0 → foxmath-0.3.0}/README.md +14 -2
- {foxmath-0.2.0 → foxmath-0.3.0}/pyproject.toml +2 -1
- {foxmath-0.2.0 → foxmath-0.3.0}/src/foxmath/foxmath.py +50 -0
- {foxmath-0.2.0 → foxmath-0.3.0/src/foxmath.egg-info}/PKG-INFO +15 -3
- {foxmath-0.2.0 → foxmath-0.3.0}/src/foxmath.egg-info/entry_points.txt +1 -0
- {foxmath-0.2.0 → foxmath-0.3.0}/tests/test_cli.py +25 -0
- {foxmath-0.2.0 → foxmath-0.3.0}/tests/test_foxmath.py +38 -1
- {foxmath-0.2.0 → foxmath-0.3.0}/LICENSE +0 -0
- {foxmath-0.2.0 → foxmath-0.3.0}/setup.cfg +0 -0
- {foxmath-0.2.0 → foxmath-0.3.0}/src/foxmath/__init__.py +0 -0
- {foxmath-0.2.0 → foxmath-0.3.0}/src/foxmath.egg-info/SOURCES.txt +0 -0
- {foxmath-0.2.0 → foxmath-0.3.0}/src/foxmath.egg-info/dependency_links.txt +0 -0
- {foxmath-0.2.0 → foxmath-0.3.0}/src/foxmath.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foxmath
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Minimalist CLI for math & cryptography exploration 🦊
|
|
5
5
|
Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -30,9 +30,10 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
|
30
30
|
- Euler/Hermann Machin-like π approximation
|
|
31
31
|
- Elliptic curve point addition (over finite fields)
|
|
32
32
|
- Chinese Remainder Theorem solver (handles non-coprime moduli)
|
|
33
|
-
-
|
|
33
|
+
- Continued fraction expansion and convergents
|
|
34
|
+
- Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt, foxmath-cf)
|
|
34
35
|
- JSON output mode
|
|
35
|
-
- Coming soon:
|
|
36
|
+
- Coming soon: challenge mode, more curves
|
|
36
37
|
|
|
37
38
|
## Installation
|
|
38
39
|
|
|
@@ -79,6 +80,17 @@ foxmath crt --r 2 3 2 --m 3 5 7
|
|
|
79
80
|
x ≡ 23 (mod 105)
|
|
80
81
|
```
|
|
81
82
|
|
|
83
|
+
```bash
|
|
84
|
+
# Continued fraction expansion + convergents
|
|
85
|
+
foxmath cf --num 355 --den 113
|
|
86
|
+
```
|
|
87
|
+
```
|
|
88
|
+
[3; 7, 16]
|
|
89
|
+
3/1
|
|
90
|
+
22/7
|
|
91
|
+
355/113
|
|
92
|
+
```
|
|
93
|
+
|
|
82
94
|
```bash
|
|
83
95
|
# JSON output
|
|
84
96
|
foxmath pi --terms 100 --json
|
|
@@ -11,9 +11,10 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
|
11
11
|
- Euler/Hermann Machin-like π approximation
|
|
12
12
|
- Elliptic curve point addition (over finite fields)
|
|
13
13
|
- Chinese Remainder Theorem solver (handles non-coprime moduli)
|
|
14
|
-
-
|
|
14
|
+
- Continued fraction expansion and convergents
|
|
15
|
+
- Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt, foxmath-cf)
|
|
15
16
|
- JSON output mode
|
|
16
|
-
- Coming soon:
|
|
17
|
+
- Coming soon: challenge mode, more curves
|
|
17
18
|
|
|
18
19
|
## Installation
|
|
19
20
|
|
|
@@ -60,6 +61,17 @@ foxmath crt --r 2 3 2 --m 3 5 7
|
|
|
60
61
|
x ≡ 23 (mod 105)
|
|
61
62
|
```
|
|
62
63
|
|
|
64
|
+
```bash
|
|
65
|
+
# Continued fraction expansion + convergents
|
|
66
|
+
foxmath cf --num 355 --den 113
|
|
67
|
+
```
|
|
68
|
+
```
|
|
69
|
+
[3; 7, 16]
|
|
70
|
+
3/1
|
|
71
|
+
22/7
|
|
72
|
+
355/113
|
|
73
|
+
```
|
|
74
|
+
|
|
63
75
|
```bash
|
|
64
76
|
# JSON output
|
|
65
77
|
foxmath pi --terms 100 --json
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "foxmath"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "Minimalist CLI for math & cryptography exploration 🦊"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "MIT"}
|
|
@@ -32,3 +32,4 @@ foxmath-legendre = "foxmath.foxmath:main"
|
|
|
32
32
|
foxmath-pi = "foxmath.foxmath:main"
|
|
33
33
|
foxmath-ecadd = "foxmath.foxmath:main"
|
|
34
34
|
foxmath-crt = "foxmath.foxmath:main"
|
|
35
|
+
foxmath-cf = "foxmath.foxmath:main"
|
|
@@ -96,6 +96,38 @@ def crt_solve(remainders, moduli):
|
|
|
96
96
|
r, m = _combine_crt(r, m, ri % mi, mi)
|
|
97
97
|
return r, m
|
|
98
98
|
|
|
99
|
+
def continued_fraction(num: int, den: int, max_terms: int = 100):
|
|
100
|
+
"""
|
|
101
|
+
Continued fraction expansion [a0; a1, a2, ...] of num/den via the
|
|
102
|
+
Euclidean algorithm. Terminates naturally for any rational input.
|
|
103
|
+
"""
|
|
104
|
+
if den == 0:
|
|
105
|
+
raise ValueError("denominator cannot be zero")
|
|
106
|
+
cf = []
|
|
107
|
+
n, d = num, den
|
|
108
|
+
while d != 0 and len(cf) < max_terms:
|
|
109
|
+
a = n // d
|
|
110
|
+
cf.append(a)
|
|
111
|
+
n, d = d, n - a * d
|
|
112
|
+
return cf
|
|
113
|
+
|
|
114
|
+
def cf_convergents(cf):
|
|
115
|
+
"""
|
|
116
|
+
Convergents (p_n, q_n) of a continued fraction, via the standard
|
|
117
|
+
recurrence h_n = a_n*h_(n-1) + h_(n-2), same for k_n, with
|
|
118
|
+
h_(-2)=0, h_(-1)=1, k_(-2)=1, k_(-1)=0.
|
|
119
|
+
"""
|
|
120
|
+
convergents = []
|
|
121
|
+
h2, h1 = 0, 1
|
|
122
|
+
k2, k1 = 1, 0
|
|
123
|
+
for a in cf:
|
|
124
|
+
h = a * h1 + h2
|
|
125
|
+
k = a * k1 + k2
|
|
126
|
+
convergents.append((h, k))
|
|
127
|
+
h2, h1 = h1, h
|
|
128
|
+
k2, k1 = k1, k
|
|
129
|
+
return convergents
|
|
130
|
+
|
|
99
131
|
# ------------------- CLI -------------------
|
|
100
132
|
|
|
101
133
|
def get_command():
|
|
@@ -151,6 +183,12 @@ def main():
|
|
|
151
183
|
crt.add_argument("--r", type=int, nargs="+", required=True, help="Remainders, e.g. --r 2 3 2")
|
|
152
184
|
crt.add_argument("--m", type=int, nargs="+", required=True, help="Moduli, e.g. --m 3 5 7")
|
|
153
185
|
|
|
186
|
+
# Continued fractions
|
|
187
|
+
cf_cmd = subparsers.add_parser("cf", help="Continued fraction expansion and convergents")
|
|
188
|
+
cf_cmd.add_argument("--num", type=int, required=True, help="Numerator")
|
|
189
|
+
cf_cmd.add_argument("--den", type=int, required=True, help="Denominator")
|
|
190
|
+
cf_cmd.add_argument("--terms", type=int, default=100, help="Max terms to expand")
|
|
191
|
+
|
|
154
192
|
args = parser.parse_args(argv)
|
|
155
193
|
|
|
156
194
|
result = {"tool": "foxmath"}
|
|
@@ -180,6 +218,18 @@ def main():
|
|
|
180
218
|
if not json_output:
|
|
181
219
|
print(f"x ≡ {x} (mod {m})")
|
|
182
220
|
|
|
221
|
+
elif cmd == "cf" or args.command == "cf":
|
|
222
|
+
cf = continued_fraction(args.num, args.den, args.terms)
|
|
223
|
+
convs = cf_convergents(cf)
|
|
224
|
+
result.update({
|
|
225
|
+
"command": "cf", "num": args.num, "den": args.den,
|
|
226
|
+
"cf": cf, "convergents": convs,
|
|
227
|
+
})
|
|
228
|
+
if not json_output:
|
|
229
|
+
print(f"[{cf[0]}; {', '.join(str(a) for a in cf[1:])}]")
|
|
230
|
+
for p, q in convs:
|
|
231
|
+
print(f" {p}/{q}")
|
|
232
|
+
|
|
183
233
|
except Exception as e:
|
|
184
234
|
print(f"Error: {e}", file=sys.stderr)
|
|
185
235
|
sys.exit(1)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: foxmath
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Minimalist CLI for math & cryptography exploration 🦊
|
|
5
5
|
Author-email: Abhrankan Chakrabarti <abhrankan@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -30,9 +30,10 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
|
30
30
|
- Euler/Hermann Machin-like π approximation
|
|
31
31
|
- Elliptic curve point addition (over finite fields)
|
|
32
32
|
- Chinese Remainder Theorem solver (handles non-coprime moduli)
|
|
33
|
-
-
|
|
33
|
+
- Continued fraction expansion and convergents
|
|
34
|
+
- Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt, foxmath-cf)
|
|
34
35
|
- JSON output mode
|
|
35
|
-
- Coming soon:
|
|
36
|
+
- Coming soon: challenge mode, more curves
|
|
36
37
|
|
|
37
38
|
## Installation
|
|
38
39
|
|
|
@@ -79,6 +80,17 @@ foxmath crt --r 2 3 2 --m 3 5 7
|
|
|
79
80
|
x ≡ 23 (mod 105)
|
|
80
81
|
```
|
|
81
82
|
|
|
83
|
+
```bash
|
|
84
|
+
# Continued fraction expansion + convergents
|
|
85
|
+
foxmath cf --num 355 --den 113
|
|
86
|
+
```
|
|
87
|
+
```
|
|
88
|
+
[3; 7, 16]
|
|
89
|
+
3/1
|
|
90
|
+
22/7
|
|
91
|
+
355/113
|
|
92
|
+
```
|
|
93
|
+
|
|
82
94
|
```bash
|
|
83
95
|
# JSON output
|
|
84
96
|
foxmath pi --terms 100 --json
|
|
@@ -85,6 +85,12 @@ class TestSymlinkInvocation(unittest.TestCase):
|
|
|
85
85
|
self.assertEqual(code, 0)
|
|
86
86
|
self.assertIn("x ≡ 23 (mod 105)", out)
|
|
87
87
|
|
|
88
|
+
def test_cf_symlink(self):
|
|
89
|
+
script = self._make_symlink_script("foxmath-cf")
|
|
90
|
+
code, out, err = run("--num", "355", "--den", "113", script=script)
|
|
91
|
+
self.assertEqual(code, 0)
|
|
92
|
+
self.assertIn("[3; 7, 16]", out)
|
|
93
|
+
|
|
88
94
|
def test_symlink_with_json(self):
|
|
89
95
|
script = self._make_symlink_script("foxmath-legendre")
|
|
90
96
|
code, out, err = run("2", "7", "--json", script=script)
|
|
@@ -122,6 +128,25 @@ class TestNormalUsage(unittest.TestCase):
|
|
|
122
128
|
self.assertEqual(code, 1)
|
|
123
129
|
self.assertIn("inconsistent", err)
|
|
124
130
|
|
|
131
|
+
def test_cf(self):
|
|
132
|
+
code, out, err = run("cf", "--num", "355", "--den", "113")
|
|
133
|
+
self.assertEqual(code, 0)
|
|
134
|
+
self.assertIn("[3; 7, 16]", out)
|
|
135
|
+
self.assertIn("355/113", out)
|
|
136
|
+
|
|
137
|
+
def test_cf_json_is_pure_json(self):
|
|
138
|
+
code, out, err = run("cf", "--num", "355", "--den", "113", "--json")
|
|
139
|
+
self.assertEqual(code, 0)
|
|
140
|
+
import json
|
|
141
|
+
parsed = json.loads(out)
|
|
142
|
+
self.assertEqual(parsed["cf"], [3, 7, 16])
|
|
143
|
+
self.assertEqual(parsed["convergents"][-1], [355, 113])
|
|
144
|
+
|
|
145
|
+
def test_cf_zero_denominator_errors(self):
|
|
146
|
+
code, out, err = run("cf", "--num", "5", "--den", "0")
|
|
147
|
+
self.assertEqual(code, 1)
|
|
148
|
+
self.assertIn("Error", err)
|
|
149
|
+
|
|
125
150
|
|
|
126
151
|
if __name__ == "__main__":
|
|
127
152
|
unittest.main()
|
|
@@ -12,7 +12,10 @@ from decimal import Decimal
|
|
|
12
12
|
|
|
13
13
|
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
|
|
14
14
|
|
|
15
|
-
from foxmath.foxmath import
|
|
15
|
+
from foxmath.foxmath import (
|
|
16
|
+
legendre_symbol, euler_hermann_pi_approx, ec_point_add, crt_solve,
|
|
17
|
+
continued_fraction, cf_convergents,
|
|
18
|
+
)
|
|
16
19
|
|
|
17
20
|
# Reference pi digits (mpmath, 250 dps of working precision), "3" + fractional digits.
|
|
18
21
|
PI_REFERENCE = Decimal(
|
|
@@ -115,5 +118,39 @@ class TestCRT(unittest.TestCase):
|
|
|
115
118
|
self.assertEqual(x % 7, 3)
|
|
116
119
|
|
|
117
120
|
|
|
121
|
+
class TestContinuedFractions(unittest.TestCase):
|
|
122
|
+
def test_classic_pi_approximation(self):
|
|
123
|
+
cf = continued_fraction(355, 113)
|
|
124
|
+
self.assertEqual(cf, [3, 7, 16])
|
|
125
|
+
convs = cf_convergents(cf)
|
|
126
|
+
self.assertEqual(convs, [(3, 1), (22, 7), (355, 113)])
|
|
127
|
+
|
|
128
|
+
def test_convergents_reduce_to_final_fraction(self):
|
|
129
|
+
cf = continued_fraction(19, 7)
|
|
130
|
+
convs = cf_convergents(cf)
|
|
131
|
+
self.assertEqual(convs[-1], (19, 7))
|
|
132
|
+
|
|
133
|
+
def test_negative_numerator(self):
|
|
134
|
+
cf = continued_fraction(-19, 7)
|
|
135
|
+
convs = cf_convergents(cf)
|
|
136
|
+
self.assertEqual(convs[-1], (-19, 7))
|
|
137
|
+
|
|
138
|
+
def test_zero_denominator_raises(self):
|
|
139
|
+
with self.assertRaises(ValueError):
|
|
140
|
+
continued_fraction(5, 0)
|
|
141
|
+
|
|
142
|
+
def test_randomized_against_fractions_module(self):
|
|
143
|
+
import random
|
|
144
|
+
from fractions import Fraction
|
|
145
|
+
random.seed(42)
|
|
146
|
+
for _ in range(200):
|
|
147
|
+
n = random.randint(-10000, 10000)
|
|
148
|
+
d = random.randint(1, 10000)
|
|
149
|
+
cf = continued_fraction(n, d)
|
|
150
|
+
convs = cf_convergents(cf)
|
|
151
|
+
expected = Fraction(n, d)
|
|
152
|
+
self.assertEqual(convs[-1], (expected.numerator, expected.denominator))
|
|
153
|
+
|
|
154
|
+
|
|
118
155
|
if __name__ == "__main__":
|
|
119
156
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|