foxmath 0.1.2__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.1.2/src/foxmath.egg-info → foxmath-0.3.0}/PKG-INFO +49 -10
- {foxmath-0.1.2 → foxmath-0.3.0}/README.md +48 -9
- {foxmath-0.1.2 → foxmath-0.3.0}/pyproject.toml +3 -1
- {foxmath-0.1.2 → foxmath-0.3.0}/src/foxmath/foxmath.py +106 -3
- {foxmath-0.1.2 → foxmath-0.3.0/src/foxmath.egg-info}/PKG-INFO +49 -10
- {foxmath-0.1.2 → foxmath-0.3.0}/src/foxmath.egg-info/entry_points.txt +2 -0
- {foxmath-0.1.2 → foxmath-0.3.0}/tests/test_cli.py +49 -0
- {foxmath-0.1.2 → foxmath-0.3.0}/tests/test_foxmath.py +75 -5
- {foxmath-0.1.2 → foxmath-0.3.0}/LICENSE +0 -0
- {foxmath-0.1.2 → foxmath-0.3.0}/setup.cfg +0 -0
- {foxmath-0.1.2 → foxmath-0.3.0}/src/foxmath/__init__.py +0 -0
- {foxmath-0.1.2 → foxmath-0.3.0}/src/foxmath.egg-info/SOURCES.txt +0 -0
- {foxmath-0.1.2 → foxmath-0.3.0}/src/foxmath.egg-info/dependency_links.txt +0 -0
- {foxmath-0.1.2 → 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
|
|
@@ -27,11 +27,13 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
|
27
27
|
|
|
28
28
|
## Features
|
|
29
29
|
- Legendre symbol
|
|
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
|
+
- Continued fraction expansion and convergents
|
|
34
|
+
- Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt, foxmath-cf)
|
|
33
35
|
- JSON output mode
|
|
34
|
-
- Coming soon:
|
|
36
|
+
- Coming soon: challenge mode, more curves
|
|
35
37
|
|
|
36
38
|
## Installation
|
|
37
39
|
|
|
@@ -49,24 +51,61 @@ pip install -e .
|
|
|
49
51
|
## Usage Examples
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
|
-
# Basic
|
|
53
54
|
foxmath pi --terms 200
|
|
55
|
+
```
|
|
56
|
+
```
|
|
57
|
+
π ≈ 3.1415926535897932384626433832795028841971693993752 (200 terms)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```bash
|
|
54
61
|
foxmath legendre 5 17
|
|
62
|
+
```
|
|
63
|
+
```
|
|
64
|
+
Legendre (5/17) = -1
|
|
65
|
+
```
|
|
55
66
|
|
|
67
|
+
```bash
|
|
56
68
|
# Symlinks (optional)
|
|
57
69
|
foxmath-ecadd --x1 1 --y1 2 --x2 3 --y2 4 --p 17
|
|
70
|
+
```
|
|
71
|
+
```
|
|
72
|
+
Result point: (14, 2)
|
|
73
|
+
```
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
```bash
|
|
76
|
+
# Chinese Remainder Theorem
|
|
77
|
+
foxmath crt --r 2 3 2 --m 3 5 7
|
|
78
|
+
```
|
|
79
|
+
```
|
|
80
|
+
x ≡ 23 (mod 105)
|
|
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
|
|
61
92
|
```
|
|
62
93
|
|
|
63
|
-
|
|
94
|
+
```bash
|
|
95
|
+
# JSON output
|
|
96
|
+
foxmath pi --terms 100 --json
|
|
64
97
|
```
|
|
65
|
-
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"tool": "foxmath",
|
|
101
|
+
"command": "pi",
|
|
102
|
+
"terms": 100,
|
|
103
|
+
"approx": "3.1415926535897932384626433832795028841971693993752"
|
|
104
|
+
}
|
|
66
105
|
```
|
|
67
106
|
|
|
68
107
|
## Why FoxMath?
|
|
69
|
-
Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as FoxPipe and fox-vault.
|
|
108
|
+
Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as [FoxPipe](https://github.com/foxhackerzdevs/FoxPipe) and [fox-vault](https://github.com/foxhackerzdevs/fox-vault).
|
|
70
109
|
|
|
71
110
|
**Simple. Practical. Reliable.**
|
|
72
111
|
|
|
@@ -8,11 +8,13 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
|
8
8
|
|
|
9
9
|
## Features
|
|
10
10
|
- Legendre symbol
|
|
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
|
+
- Continued fraction expansion and convergents
|
|
15
|
+
- Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt, foxmath-cf)
|
|
14
16
|
- JSON output mode
|
|
15
|
-
- Coming soon:
|
|
17
|
+
- Coming soon: challenge mode, more curves
|
|
16
18
|
|
|
17
19
|
## Installation
|
|
18
20
|
|
|
@@ -30,24 +32,61 @@ pip install -e .
|
|
|
30
32
|
## Usage Examples
|
|
31
33
|
|
|
32
34
|
```bash
|
|
33
|
-
# Basic
|
|
34
35
|
foxmath pi --terms 200
|
|
36
|
+
```
|
|
37
|
+
```
|
|
38
|
+
π ≈ 3.1415926535897932384626433832795028841971693993752 (200 terms)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```bash
|
|
35
42
|
foxmath legendre 5 17
|
|
43
|
+
```
|
|
44
|
+
```
|
|
45
|
+
Legendre (5/17) = -1
|
|
46
|
+
```
|
|
36
47
|
|
|
48
|
+
```bash
|
|
37
49
|
# Symlinks (optional)
|
|
38
50
|
foxmath-ecadd --x1 1 --y1 2 --x2 3 --y2 4 --p 17
|
|
51
|
+
```
|
|
52
|
+
```
|
|
53
|
+
Result point: (14, 2)
|
|
54
|
+
```
|
|
39
55
|
|
|
40
|
-
|
|
41
|
-
|
|
56
|
+
```bash
|
|
57
|
+
# Chinese Remainder Theorem
|
|
58
|
+
foxmath crt --r 2 3 2 --m 3 5 7
|
|
59
|
+
```
|
|
60
|
+
```
|
|
61
|
+
x ≡ 23 (mod 105)
|
|
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
|
|
42
73
|
```
|
|
43
74
|
|
|
44
|
-
|
|
75
|
+
```bash
|
|
76
|
+
# JSON output
|
|
77
|
+
foxmath pi --terms 100 --json
|
|
45
78
|
```
|
|
46
|
-
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"tool": "foxmath",
|
|
82
|
+
"command": "pi",
|
|
83
|
+
"terms": 100,
|
|
84
|
+
"approx": "3.1415926535897932384626433832795028841971693993752"
|
|
85
|
+
}
|
|
47
86
|
```
|
|
48
87
|
|
|
49
88
|
## Why FoxMath?
|
|
50
|
-
Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as FoxPipe and fox-vault.
|
|
89
|
+
Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as [FoxPipe](https://github.com/foxhackerzdevs/FoxPipe) and [fox-vault](https://github.com/foxhackerzdevs/fox-vault).
|
|
51
90
|
|
|
52
91
|
**Simple. Practical. Reliable.**
|
|
53
92
|
|
|
@@ -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"}
|
|
@@ -31,3 +31,5 @@ foxmath = "foxmath.foxmath:main"
|
|
|
31
31
|
foxmath-legendre = "foxmath.foxmath:main"
|
|
32
32
|
foxmath-pi = "foxmath.foxmath:main"
|
|
33
33
|
foxmath-ecadd = "foxmath.foxmath:main"
|
|
34
|
+
foxmath-crt = "foxmath.foxmath:main"
|
|
35
|
+
foxmath-cf = "foxmath.foxmath:main"
|
|
@@ -27,7 +27,7 @@ def _arctan_series(inv_n: Decimal, terms: int) -> Decimal:
|
|
|
27
27
|
total += sign * inv_n**(2 * k + 1) / (2 * k + 1)
|
|
28
28
|
return total
|
|
29
29
|
|
|
30
|
-
def
|
|
30
|
+
def euler_hermann_pi_approx(terms: int = 100) -> Decimal:
|
|
31
31
|
"""
|
|
32
32
|
Fast-converging π approximation via the Euler/Hermann Machin-like
|
|
33
33
|
identity: pi/4 = arctan(1/2) + arctan(1/3).
|
|
@@ -54,6 +54,80 @@ def ec_point_add(x1, y1, x2, y2, a, p):
|
|
|
54
54
|
y3 = (lam * (x1 - x3) - y1) % p
|
|
55
55
|
return x3, y3
|
|
56
56
|
|
|
57
|
+
def _extended_gcd(a: int, b: int):
|
|
58
|
+
"""Returns (g, x, y) such that a*x + b*y == g == gcd(a, b)."""
|
|
59
|
+
old_r, r = a, b
|
|
60
|
+
old_s, s = 1, 0
|
|
61
|
+
old_t, t = 0, 1
|
|
62
|
+
while r != 0:
|
|
63
|
+
q = old_r // r
|
|
64
|
+
old_r, r = r, old_r - q * r
|
|
65
|
+
old_s, s = s, old_s - q * s
|
|
66
|
+
old_t, t = t, old_t - q * t
|
|
67
|
+
return old_r, old_s, old_t
|
|
68
|
+
|
|
69
|
+
def _combine_crt(r1: int, m1: int, r2: int, m2: int):
|
|
70
|
+
"""Combine x≡r1 (mod m1) and x≡r2 (mod m2) into a single x≡r (mod lcm(m1,m2))."""
|
|
71
|
+
g, p, _ = _extended_gcd(m1, m2)
|
|
72
|
+
if (r2 - r1) % g != 0:
|
|
73
|
+
raise ValueError(
|
|
74
|
+
f"No solution exists: moduli {m1} and {m2} are inconsistent "
|
|
75
|
+
f"for remainders {r1} and {r2}."
|
|
76
|
+
)
|
|
77
|
+
lcm = m1 // g * m2
|
|
78
|
+
x = (r1 + (r2 - r1) // g * p % (m2 // g) * m1) % lcm
|
|
79
|
+
return x, lcm
|
|
80
|
+
|
|
81
|
+
def crt_solve(remainders, moduli):
|
|
82
|
+
"""
|
|
83
|
+
Chinese Remainder Theorem, generalized to non-pairwise-coprime moduli.
|
|
84
|
+
Returns (x, m) such that x is the unique solution mod m = lcm(moduli),
|
|
85
|
+
or raises ValueError if the system is inconsistent.
|
|
86
|
+
"""
|
|
87
|
+
if len(remainders) != len(moduli):
|
|
88
|
+
raise ValueError("remainders and moduli must have the same length")
|
|
89
|
+
if not remainders:
|
|
90
|
+
raise ValueError("need at least one congruence")
|
|
91
|
+
if any(m <= 0 for m in moduli):
|
|
92
|
+
raise ValueError("all moduli must be positive")
|
|
93
|
+
|
|
94
|
+
r, m = remainders[0] % moduli[0], moduli[0]
|
|
95
|
+
for ri, mi in zip(remainders[1:], moduli[1:]):
|
|
96
|
+
r, m = _combine_crt(r, m, ri % mi, mi)
|
|
97
|
+
return r, m
|
|
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
|
+
|
|
57
131
|
# ------------------- CLI -------------------
|
|
58
132
|
|
|
59
133
|
def get_command():
|
|
@@ -92,7 +166,7 @@ def main():
|
|
|
92
166
|
leg.add_argument("p", type=int, help="Odd prime p")
|
|
93
167
|
|
|
94
168
|
# Pi
|
|
95
|
-
pi_cmd = subparsers.add_parser("pi", help="
|
|
169
|
+
pi_cmd = subparsers.add_parser("pi", help="Euler/Hermann Machin-like π approximation")
|
|
96
170
|
pi_cmd.add_argument("--terms", type=int, default=100)
|
|
97
171
|
|
|
98
172
|
# Elliptic curve add
|
|
@@ -104,6 +178,17 @@ def main():
|
|
|
104
178
|
ec.add_argument("--a", type=int, default=-3, help="Curve parameter a")
|
|
105
179
|
ec.add_argument("--p", type=int, required=True, help="Prime modulus")
|
|
106
180
|
|
|
181
|
+
# Chinese Remainder Theorem
|
|
182
|
+
crt = subparsers.add_parser("crt", help="Solve a system of congruences x = r (mod m)")
|
|
183
|
+
crt.add_argument("--r", type=int, nargs="+", required=True, help="Remainders, e.g. --r 2 3 2")
|
|
184
|
+
crt.add_argument("--m", type=int, nargs="+", required=True, help="Moduli, e.g. --m 3 5 7")
|
|
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
|
+
|
|
107
192
|
args = parser.parse_args(argv)
|
|
108
193
|
|
|
109
194
|
result = {"tool": "foxmath"}
|
|
@@ -116,7 +201,7 @@ def main():
|
|
|
116
201
|
print(f"Legendre ({args.a}/{args.p}) = {ls}")
|
|
117
202
|
|
|
118
203
|
elif cmd == "pi" or args.command == "pi":
|
|
119
|
-
approx =
|
|
204
|
+
approx = euler_hermann_pi_approx(args.terms)
|
|
120
205
|
result.update({"command": "pi", "terms": args.terms, "approx": str(approx)})
|
|
121
206
|
if not json_output:
|
|
122
207
|
print(f"π ≈ {approx} ({args.terms} terms)")
|
|
@@ -127,6 +212,24 @@ def main():
|
|
|
127
212
|
if not json_output:
|
|
128
213
|
print(f"Result point: ({x3}, {y3})")
|
|
129
214
|
|
|
215
|
+
elif cmd == "crt" or args.command == "crt":
|
|
216
|
+
x, m = crt_solve(args.r, args.m)
|
|
217
|
+
result.update({"command": "crt", "r": args.r, "m": args.m, "x": x, "mod": m})
|
|
218
|
+
if not json_output:
|
|
219
|
+
print(f"x ≡ {x} (mod {m})")
|
|
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
|
+
|
|
130
233
|
except Exception as e:
|
|
131
234
|
print(f"Error: {e}", file=sys.stderr)
|
|
132
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
|
|
@@ -27,11 +27,13 @@ Built with curiosity — for students, tinkerers, and crypto/math enthusiasts.
|
|
|
27
27
|
|
|
28
28
|
## Features
|
|
29
29
|
- Legendre symbol
|
|
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
|
+
- Continued fraction expansion and convergents
|
|
34
|
+
- Symlink-friendly (foxmath-legendre, foxmath-pi, foxmath-ecadd, foxmath-crt, foxmath-cf)
|
|
33
35
|
- JSON output mode
|
|
34
|
-
- Coming soon:
|
|
36
|
+
- Coming soon: challenge mode, more curves
|
|
35
37
|
|
|
36
38
|
## Installation
|
|
37
39
|
|
|
@@ -49,24 +51,61 @@ pip install -e .
|
|
|
49
51
|
## Usage Examples
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
|
-
# Basic
|
|
53
54
|
foxmath pi --terms 200
|
|
55
|
+
```
|
|
56
|
+
```
|
|
57
|
+
π ≈ 3.1415926535897932384626433832795028841971693993752 (200 terms)
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```bash
|
|
54
61
|
foxmath legendre 5 17
|
|
62
|
+
```
|
|
63
|
+
```
|
|
64
|
+
Legendre (5/17) = -1
|
|
65
|
+
```
|
|
55
66
|
|
|
67
|
+
```bash
|
|
56
68
|
# Symlinks (optional)
|
|
57
69
|
foxmath-ecadd --x1 1 --y1 2 --x2 3 --y2 4 --p 17
|
|
70
|
+
```
|
|
71
|
+
```
|
|
72
|
+
Result point: (14, 2)
|
|
73
|
+
```
|
|
58
74
|
|
|
59
|
-
|
|
60
|
-
|
|
75
|
+
```bash
|
|
76
|
+
# Chinese Remainder Theorem
|
|
77
|
+
foxmath crt --r 2 3 2 --m 3 5 7
|
|
78
|
+
```
|
|
79
|
+
```
|
|
80
|
+
x ≡ 23 (mod 105)
|
|
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
|
|
61
92
|
```
|
|
62
93
|
|
|
63
|
-
|
|
94
|
+
```bash
|
|
95
|
+
# JSON output
|
|
96
|
+
foxmath pi --terms 100 --json
|
|
64
97
|
```
|
|
65
|
-
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"tool": "foxmath",
|
|
101
|
+
"command": "pi",
|
|
102
|
+
"terms": 100,
|
|
103
|
+
"approx": "3.1415926535897932384626433832795028841971693993752"
|
|
104
|
+
}
|
|
66
105
|
```
|
|
67
106
|
|
|
68
107
|
## Why FoxMath?
|
|
69
|
-
Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as FoxPipe and fox-vault.
|
|
108
|
+
Because math and cryptography are more fun when you can play with them instantly in the terminal — with the same reliability as [FoxPipe](https://github.com/foxhackerzdevs/FoxPipe) and [fox-vault](https://github.com/foxhackerzdevs/fox-vault).
|
|
70
109
|
|
|
71
110
|
**Simple. Practical. Reliable.**
|
|
72
111
|
|
|
@@ -79,6 +79,18 @@ class TestSymlinkInvocation(unittest.TestCase):
|
|
|
79
79
|
self.assertEqual(code, 0)
|
|
80
80
|
self.assertIn("Result point:", out)
|
|
81
81
|
|
|
82
|
+
def test_crt_symlink(self):
|
|
83
|
+
script = self._make_symlink_script("foxmath-crt")
|
|
84
|
+
code, out, err = run("--r", "2", "3", "2", "--m", "3", "5", "7", script=script)
|
|
85
|
+
self.assertEqual(code, 0)
|
|
86
|
+
self.assertIn("x ≡ 23 (mod 105)", out)
|
|
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
|
+
|
|
82
94
|
def test_symlink_with_json(self):
|
|
83
95
|
script = self._make_symlink_script("foxmath-legendre")
|
|
84
96
|
code, out, err = run("2", "7", "--json", script=script)
|
|
@@ -98,6 +110,43 @@ class TestNormalUsage(unittest.TestCase):
|
|
|
98
110
|
self.assertEqual(code, 1)
|
|
99
111
|
self.assertIn("point at infinity", err)
|
|
100
112
|
|
|
113
|
+
def test_crt(self):
|
|
114
|
+
code, out, err = run("crt", "--r", "2", "3", "2", "--m", "3", "5", "7")
|
|
115
|
+
self.assertEqual(code, 0)
|
|
116
|
+
self.assertIn("x ≡ 23 (mod 105)", out)
|
|
117
|
+
|
|
118
|
+
def test_crt_json_is_pure_json(self):
|
|
119
|
+
code, out, err = run("crt", "--r", "2", "3", "2", "--m", "3", "5", "7", "--json")
|
|
120
|
+
self.assertEqual(code, 0)
|
|
121
|
+
import json
|
|
122
|
+
parsed = json.loads(out)
|
|
123
|
+
self.assertEqual(parsed["x"], 23)
|
|
124
|
+
self.assertEqual(parsed["mod"], 105)
|
|
125
|
+
|
|
126
|
+
def test_crt_inconsistent_system_errors(self):
|
|
127
|
+
code, out, err = run("crt", "--r", "1", "2", "--m", "4", "6")
|
|
128
|
+
self.assertEqual(code, 1)
|
|
129
|
+
self.assertIn("inconsistent", err)
|
|
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
|
+
|
|
101
150
|
|
|
102
151
|
if __name__ == "__main__":
|
|
103
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(
|
|
@@ -47,16 +50,16 @@ class TestLegendreSymbol(unittest.TestCase):
|
|
|
47
50
|
legendre_symbol(3, 1)
|
|
48
51
|
|
|
49
52
|
|
|
50
|
-
class
|
|
53
|
+
class TestEulerHermannPiApprox(unittest.TestCase):
|
|
51
54
|
def test_converges_to_pi(self):
|
|
52
55
|
# Regression check: this used to converge to 16*arctan(1/2) =~ 7.418,
|
|
53
56
|
# not pi, due to a double-counted scaling factor.
|
|
54
|
-
approx =
|
|
57
|
+
approx = euler_hermann_pi_approx(100)
|
|
55
58
|
self.assertLess(abs(approx - PI_REFERENCE), Decimal("1e-40"))
|
|
56
59
|
|
|
57
60
|
def test_more_terms_is_more_accurate(self):
|
|
58
|
-
err_10 = abs(
|
|
59
|
-
err_50 = abs(
|
|
61
|
+
err_10 = abs(euler_hermann_pi_approx(10) - PI_REFERENCE)
|
|
62
|
+
err_50 = abs(euler_hermann_pi_approx(50) - PI_REFERENCE)
|
|
60
63
|
self.assertLess(err_50, err_10)
|
|
61
64
|
|
|
62
65
|
|
|
@@ -82,5 +85,72 @@ class TestECPointAdd(unittest.TestCase):
|
|
|
82
85
|
ec_point_add(3, 6, 3, self.P - 6, self.A, self.P)
|
|
83
86
|
|
|
84
87
|
|
|
88
|
+
class TestCRT(unittest.TestCase):
|
|
89
|
+
def test_classic_coprime_case(self):
|
|
90
|
+
x, m = crt_solve([2, 3, 2], [3, 5, 7])
|
|
91
|
+
self.assertEqual((x, m), (23, 105))
|
|
92
|
+
self.assertEqual(x % 3, 2)
|
|
93
|
+
self.assertEqual(x % 5, 3)
|
|
94
|
+
self.assertEqual(x % 7, 2)
|
|
95
|
+
|
|
96
|
+
def test_non_coprime_consistent(self):
|
|
97
|
+
x, m = crt_solve([2, 2], [4, 6])
|
|
98
|
+
self.assertEqual((x, m), (2, 12))
|
|
99
|
+
|
|
100
|
+
def test_non_coprime_inconsistent_raises(self):
|
|
101
|
+
with self.assertRaises(ValueError):
|
|
102
|
+
crt_solve([1, 2], [4, 6])
|
|
103
|
+
|
|
104
|
+
def test_single_congruence(self):
|
|
105
|
+
self.assertEqual(crt_solve([5], [11]), (5, 11))
|
|
106
|
+
|
|
107
|
+
def test_mismatched_lengths_raises(self):
|
|
108
|
+
with self.assertRaises(ValueError):
|
|
109
|
+
crt_solve([2, 3], [3, 5, 7])
|
|
110
|
+
|
|
111
|
+
def test_empty_raises(self):
|
|
112
|
+
with self.assertRaises(ValueError):
|
|
113
|
+
crt_solve([], [])
|
|
114
|
+
|
|
115
|
+
def test_negative_remainder_normalized(self):
|
|
116
|
+
x, m = crt_solve([-1, 3], [5, 7])
|
|
117
|
+
self.assertEqual(x % 5, 4) # -1 mod 5 == 4
|
|
118
|
+
self.assertEqual(x % 7, 3)
|
|
119
|
+
|
|
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
|
+
|
|
85
155
|
if __name__ == "__main__":
|
|
86
156
|
unittest.main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|