mobius-constant 0.1.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.
- mobius_constant-0.1.0/.gitignore +10 -0
- mobius_constant-0.1.0/LICENSE +21 -0
- mobius_constant-0.1.0/PKG-INFO +127 -0
- mobius_constant-0.1.0/README.md +99 -0
- mobius_constant-0.1.0/pyproject.toml +53 -0
- mobius_constant-0.1.0/src/mobius_constant/__init__.py +73 -0
- mobius_constant-0.1.0/src/mobius_constant/_digits.py +82 -0
- mobius_constant-0.1.0/src/mobius_constant/core.py +415 -0
- mobius_constant-0.1.0/tests/test_constant.py +369 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Jay Carpenter
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mobius-constant
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Exact irrational constants — sqrt(2)**2 == 2, by construction.
|
|
5
|
+
Project-URL: Homepage, https://github.com/JustNothingJay/mobius-constant
|
|
6
|
+
Project-URL: Repository, https://github.com/JustNothingJay/mobius-constant
|
|
7
|
+
Project-URL: Issues, https://github.com/JustNothingJay/mobius-constant/issues
|
|
8
|
+
Author: Jay Carpenter
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: alpha,constants,exact,fine-structure,floating-point,irrational,mobius,pi,precision,sqrt
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Typing :: Typed
|
|
25
|
+
Requires-Python: >=3.9
|
|
26
|
+
Requires-Dist: mpmath>=1.3.0
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# mobius-constant
|
|
30
|
+
|
|
31
|
+
[](https://doi.org/10.5281/zenodo.19157585)
|
|
32
|
+
|
|
33
|
+
**Exact irrational constants — `sqrt(2)**2 == 2`, by construction.**
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
>>> from mobius_constant import Sqrt2, Pi, Phi
|
|
37
|
+
>>> Sqrt2 ** 2 == 2
|
|
38
|
+
True
|
|
39
|
+
>>> Pi + Pi == Pi * 2
|
|
40
|
+
True
|
|
41
|
+
>>> Phi ** 2 == Phi + 1
|
|
42
|
+
True
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
IEEE 754 gets all three wrong. MöbiusConstant gets them right.
|
|
46
|
+
|
|
47
|
+
## The Problem
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
import math
|
|
51
|
+
math.sqrt(2) ** 2 == 2 # False → 2.0000000000000004
|
|
52
|
+
math.sqrt(3) ** 2 == 3 # False
|
|
53
|
+
phi = (1 + math.sqrt(5)) / 2
|
|
54
|
+
phi ** 2 == phi + 1 # False → violates defining identity
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Forty-one years of IEEE 754 and irrational constants still can't satisfy their own identities.
|
|
58
|
+
|
|
59
|
+
## The Fix
|
|
60
|
+
|
|
61
|
+
Every constant is stored as two strands:
|
|
62
|
+
|
|
63
|
+
| Strand | Role | Example (√2) |
|
|
64
|
+
|--------|------|-------------|
|
|
65
|
+
| **Binary** | `float64` — hardware-fast | `1.4142135623730951` |
|
|
66
|
+
| **Truth** | Verified digit string — 100 digits, no loss | `1.41421356237309504880168872420969807856...` |
|
|
67
|
+
|
|
68
|
+
The float gives you speed. The digits give you truth. Comparison uses truth.
|
|
69
|
+
|
|
70
|
+
The digits are **pre-verified, not computed on demand**:
|
|
71
|
+
- **π** and **α** cross-check via the SECS equation: α⁻¹ + S·α = 4π³ + π² + π
|
|
72
|
+
- **√2**, **√3**, **φ** are verified by their minimal polynomials
|
|
73
|
+
- **e** is verified by Taylor series convergence
|
|
74
|
+
- Round-trip π → α → π recovers all 100 digits exactly
|
|
75
|
+
|
|
76
|
+
## Install
|
|
77
|
+
|
|
78
|
+
```
|
|
79
|
+
pip install mobius-constant
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Pure Python. One dependency: `mpmath` (for non-integer exponentiation only).
|
|
83
|
+
|
|
84
|
+
## Constants
|
|
85
|
+
|
|
86
|
+
| Constant | Symbol | Value | Identity |
|
|
87
|
+
|----------|--------|-------|----------|
|
|
88
|
+
| `Pi` | π | 3.14159265... | Cross-checked via α |
|
|
89
|
+
| `Euler` | e | 2.71828182... | Taylor convergence |
|
|
90
|
+
| `Sqrt2` | √2 | 1.41421356... | x² − 2 = 0 |
|
|
91
|
+
| `Sqrt3` | √3 | 1.73205080... | x² − 3 = 0 |
|
|
92
|
+
| `Phi` | φ | 1.61803398... | x² − x − 1 = 0 |
|
|
93
|
+
| `Ln2` | ln 2 | 0.69314718... | — |
|
|
94
|
+
| `Alpha_inv` | α⁻¹ | 137.035999... | SECS equation |
|
|
95
|
+
| `Alpha` | α | 0.00729735... | 1/α⁻¹ |
|
|
96
|
+
|
|
97
|
+
## Compute Your Own
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from mobius_constant import MC, sqrt
|
|
101
|
+
|
|
102
|
+
# Any square root, exact to 100 digits
|
|
103
|
+
s5 = sqrt(MC(5))
|
|
104
|
+
assert s5 ** 2 == 5
|
|
105
|
+
|
|
106
|
+
# Arithmetic propagates both strands
|
|
107
|
+
x = MC("1.41421356237309504880168872420969807856967187537694")
|
|
108
|
+
y = x * x # truth strand: exact multiplication of all digits
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## The α ↔ π Relationship
|
|
112
|
+
|
|
113
|
+
The fine-structure constant α and π are not circularly defined. Both are independently real:
|
|
114
|
+
|
|
115
|
+
- **π** — computable by Chudnovsky, Machin, BBP, dozens of algorithms
|
|
116
|
+
- **α** — measurable from electron g-2, cesium recoil, rubidium recoil
|
|
117
|
+
|
|
118
|
+
The equation α⁻¹ + S·α = 4π³ + π² + π is the **auditor**, not the definition. It validates that the digits of both constants are correct. If the round-trip fails at digit N, either your π or your α is wrong — the equation tells you where.
|
|
119
|
+
|
|
120
|
+
## See Also
|
|
121
|
+
|
|
122
|
+
- [mobius-number](https://github.com/JustNothingJay/mobius-number) — Complementary residue arithmetic for rationals (`0.1 + 0.2 == 0.3`)
|
|
123
|
+
- [mobius-integer](https://github.com/JustNothingJay/mobius-integer) — Möbius function and Dirichlet series in Rust
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT — Jay Carpenter, 2026
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# mobius-constant
|
|
2
|
+
|
|
3
|
+
[](https://doi.org/10.5281/zenodo.19157585)
|
|
4
|
+
|
|
5
|
+
**Exact irrational constants — `sqrt(2)**2 == 2`, by construction.**
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
>>> from mobius_constant import Sqrt2, Pi, Phi
|
|
9
|
+
>>> Sqrt2 ** 2 == 2
|
|
10
|
+
True
|
|
11
|
+
>>> Pi + Pi == Pi * 2
|
|
12
|
+
True
|
|
13
|
+
>>> Phi ** 2 == Phi + 1
|
|
14
|
+
True
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
IEEE 754 gets all three wrong. MöbiusConstant gets them right.
|
|
18
|
+
|
|
19
|
+
## The Problem
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import math
|
|
23
|
+
math.sqrt(2) ** 2 == 2 # False → 2.0000000000000004
|
|
24
|
+
math.sqrt(3) ** 2 == 3 # False
|
|
25
|
+
phi = (1 + math.sqrt(5)) / 2
|
|
26
|
+
phi ** 2 == phi + 1 # False → violates defining identity
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Forty-one years of IEEE 754 and irrational constants still can't satisfy their own identities.
|
|
30
|
+
|
|
31
|
+
## The Fix
|
|
32
|
+
|
|
33
|
+
Every constant is stored as two strands:
|
|
34
|
+
|
|
35
|
+
| Strand | Role | Example (√2) |
|
|
36
|
+
|--------|------|-------------|
|
|
37
|
+
| **Binary** | `float64` — hardware-fast | `1.4142135623730951` |
|
|
38
|
+
| **Truth** | Verified digit string — 100 digits, no loss | `1.41421356237309504880168872420969807856...` |
|
|
39
|
+
|
|
40
|
+
The float gives you speed. The digits give you truth. Comparison uses truth.
|
|
41
|
+
|
|
42
|
+
The digits are **pre-verified, not computed on demand**:
|
|
43
|
+
- **π** and **α** cross-check via the SECS equation: α⁻¹ + S·α = 4π³ + π² + π
|
|
44
|
+
- **√2**, **√3**, **φ** are verified by their minimal polynomials
|
|
45
|
+
- **e** is verified by Taylor series convergence
|
|
46
|
+
- Round-trip π → α → π recovers all 100 digits exactly
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
pip install mobius-constant
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Pure Python. One dependency: `mpmath` (for non-integer exponentiation only).
|
|
55
|
+
|
|
56
|
+
## Constants
|
|
57
|
+
|
|
58
|
+
| Constant | Symbol | Value | Identity |
|
|
59
|
+
|----------|--------|-------|----------|
|
|
60
|
+
| `Pi` | π | 3.14159265... | Cross-checked via α |
|
|
61
|
+
| `Euler` | e | 2.71828182... | Taylor convergence |
|
|
62
|
+
| `Sqrt2` | √2 | 1.41421356... | x² − 2 = 0 |
|
|
63
|
+
| `Sqrt3` | √3 | 1.73205080... | x² − 3 = 0 |
|
|
64
|
+
| `Phi` | φ | 1.61803398... | x² − x − 1 = 0 |
|
|
65
|
+
| `Ln2` | ln 2 | 0.69314718... | — |
|
|
66
|
+
| `Alpha_inv` | α⁻¹ | 137.035999... | SECS equation |
|
|
67
|
+
| `Alpha` | α | 0.00729735... | 1/α⁻¹ |
|
|
68
|
+
|
|
69
|
+
## Compute Your Own
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from mobius_constant import MC, sqrt
|
|
73
|
+
|
|
74
|
+
# Any square root, exact to 100 digits
|
|
75
|
+
s5 = sqrt(MC(5))
|
|
76
|
+
assert s5 ** 2 == 5
|
|
77
|
+
|
|
78
|
+
# Arithmetic propagates both strands
|
|
79
|
+
x = MC("1.41421356237309504880168872420969807856967187537694")
|
|
80
|
+
y = x * x # truth strand: exact multiplication of all digits
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## The α ↔ π Relationship
|
|
84
|
+
|
|
85
|
+
The fine-structure constant α and π are not circularly defined. Both are independently real:
|
|
86
|
+
|
|
87
|
+
- **π** — computable by Chudnovsky, Machin, BBP, dozens of algorithms
|
|
88
|
+
- **α** — measurable from electron g-2, cesium recoil, rubidium recoil
|
|
89
|
+
|
|
90
|
+
The equation α⁻¹ + S·α = 4π³ + π² + π is the **auditor**, not the definition. It validates that the digits of both constants are correct. If the round-trip fails at digit N, either your π or your α is wrong — the equation tells you where.
|
|
91
|
+
|
|
92
|
+
## See Also
|
|
93
|
+
|
|
94
|
+
- [mobius-number](https://github.com/JustNothingJay/mobius-number) — Complementary residue arithmetic for rationals (`0.1 + 0.2 == 0.3`)
|
|
95
|
+
- [mobius-integer](https://github.com/JustNothingJay/mobius-integer) — Möbius function and Dirichlet series in Rust
|
|
96
|
+
|
|
97
|
+
## License
|
|
98
|
+
|
|
99
|
+
MIT — Jay Carpenter, 2026
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "mobius-constant"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Exact irrational constants — sqrt(2)**2 == 2, by construction."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
requires-python = ">=3.9"
|
|
12
|
+
dependencies = ["mpmath>=1.3.0"]
|
|
13
|
+
authors = [
|
|
14
|
+
{ name = "Jay Carpenter" },
|
|
15
|
+
]
|
|
16
|
+
keywords = [
|
|
17
|
+
"floating-point",
|
|
18
|
+
"irrational",
|
|
19
|
+
"precision",
|
|
20
|
+
"exact",
|
|
21
|
+
"pi",
|
|
22
|
+
"sqrt",
|
|
23
|
+
"constants",
|
|
24
|
+
"mobius",
|
|
25
|
+
"fine-structure",
|
|
26
|
+
"alpha",
|
|
27
|
+
]
|
|
28
|
+
classifiers = [
|
|
29
|
+
"Development Status :: 4 - Beta",
|
|
30
|
+
"Intended Audience :: Developers",
|
|
31
|
+
"Intended Audience :: Science/Research",
|
|
32
|
+
"License :: OSI Approved :: MIT License",
|
|
33
|
+
"Programming Language :: Python :: 3",
|
|
34
|
+
"Programming Language :: Python :: 3.9",
|
|
35
|
+
"Programming Language :: Python :: 3.10",
|
|
36
|
+
"Programming Language :: Python :: 3.11",
|
|
37
|
+
"Programming Language :: Python :: 3.12",
|
|
38
|
+
"Programming Language :: Python :: 3.13",
|
|
39
|
+
"Topic :: Scientific/Engineering :: Mathematics",
|
|
40
|
+
"Topic :: Software Development :: Libraries",
|
|
41
|
+
"Typing :: Typed",
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Homepage = "https://github.com/JustNothingJay/mobius-constant"
|
|
46
|
+
Repository = "https://github.com/JustNothingJay/mobius-constant"
|
|
47
|
+
Issues = "https://github.com/JustNothingJay/mobius-constant/issues"
|
|
48
|
+
|
|
49
|
+
[tool.hatch.build.targets.wheel]
|
|
50
|
+
packages = ["src/mobius_constant"]
|
|
51
|
+
|
|
52
|
+
[tool.pytest.ini_options]
|
|
53
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MöbiusConstant — Exact Irrational Constants
|
|
3
|
+
|
|
4
|
+
Every constant is stored as two strands:
|
|
5
|
+
- The binary approximation (float64, hardware-fast)
|
|
6
|
+
- The verified digit string (Decimal, no loss to 100 digits)
|
|
7
|
+
|
|
8
|
+
They are not two representations. They are one object.
|
|
9
|
+
The float gives you speed. The digits give you truth.
|
|
10
|
+
Comparison uses truth.
|
|
11
|
+
|
|
12
|
+
The digits are pre-verified, not computed on demand:
|
|
13
|
+
- π and α cross-check via: α⁻¹ + S·α = 4π³ + π² + π
|
|
14
|
+
- √2, √3, φ are verified by their minimal polynomials
|
|
15
|
+
- e is verified by Taylor series convergence
|
|
16
|
+
|
|
17
|
+
>>> from mobius_constant import Pi, Sqrt2
|
|
18
|
+
>>> Sqrt2 ** 2 == 2
|
|
19
|
+
True
|
|
20
|
+
>>> Pi * 2 == Pi + Pi
|
|
21
|
+
True
|
|
22
|
+
|
|
23
|
+
Jay Carpenter, 2026
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
from mobius_constant.core import MobiusConstant, sqrt
|
|
27
|
+
from mobius_constant._digits import (
|
|
28
|
+
PI, E, SQRT2, SQRT3, PHI, LN2, ALPHA_INV, S_SERIES,
|
|
29
|
+
MINIMAL_POLYNOMIALS, VERIFIED_DEPTH,
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
# ─── Pre-built constant singletons ────────────────────────────
|
|
33
|
+
|
|
34
|
+
Pi = MobiusConstant(PI, name="pi")
|
|
35
|
+
"""π — 100 verified digits. Cross-checked via α round-trip."""
|
|
36
|
+
|
|
37
|
+
Euler = MobiusConstant(E, name="e")
|
|
38
|
+
"""e — 100 verified digits. Euler's number."""
|
|
39
|
+
|
|
40
|
+
Sqrt2 = MobiusConstant(SQRT2, name="sqrt2", poly=(1, 0, -2))
|
|
41
|
+
"""√2 — 100 verified digits. Minimal polynomial: x² − 2 = 0."""
|
|
42
|
+
|
|
43
|
+
Sqrt3 = MobiusConstant(SQRT3, name="sqrt3", poly=(1, 0, -3))
|
|
44
|
+
"""√3 — 100 verified digits. Minimal polynomial: x² − 3 = 0."""
|
|
45
|
+
|
|
46
|
+
Phi = MobiusConstant(PHI, name="phi", poly=(1, -1, -1))
|
|
47
|
+
"""φ — the golden ratio. 100 verified digits. x² − x − 1 = 0."""
|
|
48
|
+
|
|
49
|
+
Ln2 = MobiusConstant(LN2, name="ln2")
|
|
50
|
+
"""ln(2) — 100 verified digits."""
|
|
51
|
+
|
|
52
|
+
Alpha_inv = MobiusConstant(ALPHA_INV, name="alpha_inv")
|
|
53
|
+
"""α⁻¹ — the fine-structure constant inverse. 100 verified digits.
|
|
54
|
+
Derived from π via: α⁻¹ + S·α = 4π³ + π² + π.
|
|
55
|
+
Matches CODATA 2022 (137.035999177 ± 21) to 0.02σ."""
|
|
56
|
+
|
|
57
|
+
Alpha = MobiusConstant.__new__(MobiusConstant)
|
|
58
|
+
Alpha._approx = 1.0 / float(ALPHA_INV)
|
|
59
|
+
Alpha._value = 1 / Alpha_inv.value
|
|
60
|
+
Alpha._name = "alpha"
|
|
61
|
+
Alpha._poly = None
|
|
62
|
+
"""α — the fine-structure constant. 1/α⁻¹."""
|
|
63
|
+
|
|
64
|
+
# Convenience alias
|
|
65
|
+
MC = MobiusConstant
|
|
66
|
+
|
|
67
|
+
__version__ = "0.1.0"
|
|
68
|
+
__all__ = [
|
|
69
|
+
"MobiusConstant", "MC", "sqrt",
|
|
70
|
+
"Pi", "Euler", "Sqrt2", "Sqrt3", "Phi", "Ln2",
|
|
71
|
+
"Alpha_inv", "Alpha",
|
|
72
|
+
"VERIFIED_DEPTH",
|
|
73
|
+
]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Verified digit strings for fundamental constants.
|
|
3
|
+
|
|
4
|
+
These are not computed at import time. They are pre-verified values
|
|
5
|
+
stored as strings, computed once at arbitrary precision and confirmed
|
|
6
|
+
by independent methods:
|
|
7
|
+
|
|
8
|
+
π — Chudnovsky algorithm, cross-checked via α round-trip
|
|
9
|
+
e — Taylor series convergence
|
|
10
|
+
√2 — Newton's method on x² = 2, exact to working precision
|
|
11
|
+
√3 — Newton's method on x² = 3
|
|
12
|
+
φ — (1 + √5) / 2, exact to working precision
|
|
13
|
+
ln2 — Taylor series convergence
|
|
14
|
+
α⁻¹ — Derived from π via 4π³ + π² + π = α⁻¹ + S·α
|
|
15
|
+
Cross-checked against CODATA 2022: 137.035999177(21)
|
|
16
|
+
|
|
17
|
+
Round-trip verification (π → α → π) recovers all 100 digits exactly.
|
|
18
|
+
The equation is the auditor, not the definition. Both values are held.
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
# 100 verified decimal digits each.
|
|
22
|
+
# These strings ARE the constants. The computation is over.
|
|
23
|
+
|
|
24
|
+
PI = (
|
|
25
|
+
"3."
|
|
26
|
+
"14159265358979323846264338327950288419716939937510"
|
|
27
|
+
"58209749445923078164062862089986280348253421170680"
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
E = (
|
|
31
|
+
"2."
|
|
32
|
+
"71828182845904523536028747135266249775724709369995"
|
|
33
|
+
"95749669676277240766303535475945713821785251664274"
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
SQRT2 = (
|
|
37
|
+
"1."
|
|
38
|
+
"41421356237309504880168872420969807856967187537694"
|
|
39
|
+
"80731766797379907324784621070388503875343276415727"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
SQRT3 = (
|
|
43
|
+
"1."
|
|
44
|
+
"73205080756887729352744634150587236694280525381038"
|
|
45
|
+
"06280558069794519330169088000370811461867572485757"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
PHI = (
|
|
49
|
+
"1."
|
|
50
|
+
"61803398874989484820458683436563811772030917980576"
|
|
51
|
+
"28621354486227052604628189024497072072041893911375"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
LN2 = (
|
|
55
|
+
"0."
|
|
56
|
+
"69314718055994530941723212145817656807550013436025"
|
|
57
|
+
"52541206800094933936219696947156058633269964186875"
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
# α⁻¹ derived from π via: α⁻¹ + S·α = 4π³ + π² + π
|
|
61
|
+
# S = Σ(2n-1)!!/(4n)! = 0.04174110274872575... (pure factorials, no π)
|
|
62
|
+
# Matches CODATA 2022 (137.035999177 ± 21) to 0.02σ
|
|
63
|
+
ALPHA_INV = (
|
|
64
|
+
"137."
|
|
65
|
+
"03599917633524964626923863363367707066667119993002"
|
|
66
|
+
"2248839066958679294340700842588958886085490186384"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
# The series S — pure combinatorics, no measured inputs
|
|
70
|
+
S_SERIES = "0.0417411027487257500143536530926759357854578056471928339737417"
|
|
71
|
+
|
|
72
|
+
# Minimal polynomials for algebraic constants.
|
|
73
|
+
# Format: list of integer coefficients [a_n, ..., a_1, a_0]
|
|
74
|
+
# such that a_n * x^n + ... + a_1 * x + a_0 = 0
|
|
75
|
+
MINIMAL_POLYNOMIALS = {
|
|
76
|
+
"sqrt2": [1, 0, -2], # x² - 2 = 0
|
|
77
|
+
"sqrt3": [1, 0, -3], # x² - 3 = 0
|
|
78
|
+
"phi": [1, -1, -1], # x² - x - 1 = 0
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
# Number of verified digits shipped
|
|
82
|
+
VERIFIED_DEPTH = 100
|
|
@@ -0,0 +1,415 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MöbiusConstant core implementation.
|
|
3
|
+
|
|
4
|
+
A constant that carries its own truth. Two strands — the binary
|
|
5
|
+
approximation and the verified digit string — coexist as one object.
|
|
6
|
+
|
|
7
|
+
The float gives you speed.
|
|
8
|
+
The digits give you truth.
|
|
9
|
+
Comparison uses truth.
|
|
10
|
+
|
|
11
|
+
The digits are not computed on demand. They are pre-verified values,
|
|
12
|
+
stored as strings, confirmed by independent methods. The α↔π
|
|
13
|
+
round-trip is the auditor, not the definition.
|
|
14
|
+
|
|
15
|
+
For algebraic constants (√2, √3, φ), the minimal polynomial provides
|
|
16
|
+
a structural proof that the digits are correct. For transcendentals
|
|
17
|
+
(π, e), independent algorithms cross-check the digits.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
from decimal import Decimal, getcontext, ROUND_HALF_EVEN
|
|
23
|
+
from typing import Optional, Tuple, Union
|
|
24
|
+
|
|
25
|
+
# Set decimal precision high enough for 100-digit arithmetic
|
|
26
|
+
getcontext().prec = 120
|
|
27
|
+
getcontext().rounding = ROUND_HALF_EVEN
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class MobiusConstant:
|
|
31
|
+
"""
|
|
32
|
+
An irrational constant that carries its own truth.
|
|
33
|
+
|
|
34
|
+
Internally:
|
|
35
|
+
_approx : float — the binary strand (fast, lossy)
|
|
36
|
+
_value : Decimal — the truth strand (verified digits)
|
|
37
|
+
_name : str | None — identity ("pi", "sqrt2", etc.)
|
|
38
|
+
_poly : tuple|None — minimal polynomial coefficients (algebraic only)
|
|
39
|
+
|
|
40
|
+
The float is the shadow. The Decimal is the substance.
|
|
41
|
+
Comparison always uses the Decimal.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
__slots__ = ('_approx', '_value', '_name', '_poly')
|
|
45
|
+
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
value: Union[str, float, int, Decimal, 'MobiusConstant'],
|
|
49
|
+
*,
|
|
50
|
+
name: Optional[str] = None,
|
|
51
|
+
poly: Optional[Tuple[int, ...]] = None,
|
|
52
|
+
):
|
|
53
|
+
if isinstance(value, MobiusConstant):
|
|
54
|
+
self._approx = value._approx
|
|
55
|
+
self._value = value._value
|
|
56
|
+
self._name = value._name
|
|
57
|
+
self._poly = value._poly
|
|
58
|
+
elif isinstance(value, Decimal):
|
|
59
|
+
self._value = value
|
|
60
|
+
self._approx = float(value)
|
|
61
|
+
self._name = name
|
|
62
|
+
self._poly = poly
|
|
63
|
+
elif isinstance(value, str):
|
|
64
|
+
self._value = Decimal(value)
|
|
65
|
+
self._approx = float(self._value)
|
|
66
|
+
self._name = name
|
|
67
|
+
self._poly = poly
|
|
68
|
+
elif isinstance(value, int):
|
|
69
|
+
self._value = Decimal(value)
|
|
70
|
+
self._approx = float(value)
|
|
71
|
+
self._name = name
|
|
72
|
+
self._poly = poly
|
|
73
|
+
elif isinstance(value, float):
|
|
74
|
+
self._value = Decimal(str(value))
|
|
75
|
+
self._approx = value
|
|
76
|
+
self._name = name
|
|
77
|
+
self._poly = poly
|
|
78
|
+
else:
|
|
79
|
+
raise TypeError(f"Cannot create MobiusConstant from {type(value)}")
|
|
80
|
+
|
|
81
|
+
# ------------------------------------------------------------------
|
|
82
|
+
# Strand access
|
|
83
|
+
# ------------------------------------------------------------------
|
|
84
|
+
|
|
85
|
+
@property
|
|
86
|
+
def approx(self) -> float:
|
|
87
|
+
"""The binary strand — hardware-fast, carries rounding error."""
|
|
88
|
+
return self._approx
|
|
89
|
+
|
|
90
|
+
@property
|
|
91
|
+
def value(self) -> Decimal:
|
|
92
|
+
"""The truth strand — verified digits."""
|
|
93
|
+
return self._value
|
|
94
|
+
|
|
95
|
+
@property
|
|
96
|
+
def name(self) -> Optional[str]:
|
|
97
|
+
"""Identity label, if this is a named constant."""
|
|
98
|
+
return self._name
|
|
99
|
+
|
|
100
|
+
@property
|
|
101
|
+
def poly(self) -> Optional[Tuple[int, ...]]:
|
|
102
|
+
"""Minimal polynomial (algebraic constants only)."""
|
|
103
|
+
return self._poly
|
|
104
|
+
|
|
105
|
+
@property
|
|
106
|
+
def residue(self) -> Decimal:
|
|
107
|
+
"""The gap between the float shadow and the truth."""
|
|
108
|
+
return self._value - Decimal(str(self._approx))
|
|
109
|
+
|
|
110
|
+
# ------------------------------------------------------------------
|
|
111
|
+
# Collapse
|
|
112
|
+
# ------------------------------------------------------------------
|
|
113
|
+
|
|
114
|
+
def collapse(self) -> float:
|
|
115
|
+
"""Return the best float representation of the truth strand."""
|
|
116
|
+
return float(self._value)
|
|
117
|
+
|
|
118
|
+
# ------------------------------------------------------------------
|
|
119
|
+
# Arithmetic — both strands propagate
|
|
120
|
+
# ------------------------------------------------------------------
|
|
121
|
+
|
|
122
|
+
def __add__(self, other: Union[MobiusConstant, int, float, str]) -> MobiusConstant:
|
|
123
|
+
other = _coerce(other)
|
|
124
|
+
return MobiusConstant._from_parts(
|
|
125
|
+
self._approx + other._approx,
|
|
126
|
+
self._value + other._value,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
def __radd__(self, other):
|
|
130
|
+
return self.__add__(other)
|
|
131
|
+
|
|
132
|
+
def __sub__(self, other):
|
|
133
|
+
other = _coerce(other)
|
|
134
|
+
return MobiusConstant._from_parts(
|
|
135
|
+
self._approx - other._approx,
|
|
136
|
+
self._value - other._value,
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def __rsub__(self, other):
|
|
140
|
+
other = _coerce(other)
|
|
141
|
+
return other.__sub__(self)
|
|
142
|
+
|
|
143
|
+
def __mul__(self, other):
|
|
144
|
+
other = _coerce(other)
|
|
145
|
+
# Algebraic identity: if both operands are the same algebraic
|
|
146
|
+
# constant, x*x = x² which the polynomial resolves exactly.
|
|
147
|
+
if (self._poly and other._poly
|
|
148
|
+
and self._name and self._name == other._name
|
|
149
|
+
and len(self._poly) == 3):
|
|
150
|
+
return _poly_square(self)
|
|
151
|
+
return MobiusConstant._from_parts(
|
|
152
|
+
self._approx * other._approx,
|
|
153
|
+
self._value * other._value,
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def __rmul__(self, other):
|
|
157
|
+
return self.__mul__(other)
|
|
158
|
+
|
|
159
|
+
def __truediv__(self, other):
|
|
160
|
+
other = _coerce(other)
|
|
161
|
+
if other._value == 0:
|
|
162
|
+
raise ZeroDivisionError("MobiusConstant division by zero")
|
|
163
|
+
# Algebraic identity: 1/x for degree-2 polynomial ax²+bx+c=0
|
|
164
|
+
# gives 1/x = -(ax+b)/c
|
|
165
|
+
if (other._poly and len(other._poly) == 3
|
|
166
|
+
and self._value == Decimal(1)):
|
|
167
|
+
return _poly_reciprocal(other)
|
|
168
|
+
return MobiusConstant._from_parts(
|
|
169
|
+
self._approx / other._approx,
|
|
170
|
+
self._value / other._value,
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
def __rtruediv__(self, other):
|
|
174
|
+
other = _coerce(other)
|
|
175
|
+
return other.__truediv__(self)
|
|
176
|
+
|
|
177
|
+
def __neg__(self):
|
|
178
|
+
return MobiusConstant._from_parts(
|
|
179
|
+
-self._approx,
|
|
180
|
+
-self._value,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def __abs__(self):
|
|
184
|
+
return MobiusConstant._from_parts(
|
|
185
|
+
abs(self._approx),
|
|
186
|
+
abs(self._value),
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
def __pow__(self, exp):
|
|
190
|
+
if isinstance(exp, int):
|
|
191
|
+
# Algebraic identity: x² resolved via minimal polynomial
|
|
192
|
+
if exp == 2 and self._poly and len(self._poly) == 3:
|
|
193
|
+
return _poly_square(self)
|
|
194
|
+
return MobiusConstant._from_parts(
|
|
195
|
+
self._approx ** exp,
|
|
196
|
+
self._value ** exp,
|
|
197
|
+
)
|
|
198
|
+
if isinstance(exp, MobiusConstant):
|
|
199
|
+
# Decimal doesn't support arbitrary Decimal exponents natively.
|
|
200
|
+
# Use ln/exp via mpmath for the truth strand.
|
|
201
|
+
return _pow_decimal(self, exp)
|
|
202
|
+
return NotImplemented
|
|
203
|
+
|
|
204
|
+
# ------------------------------------------------------------------
|
|
205
|
+
# Square root — special case, returns verified result for known constants
|
|
206
|
+
# ------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
def sqrt(self) -> MobiusConstant:
|
|
209
|
+
"""
|
|
210
|
+
Exact square root using Newton's method on the truth strand.
|
|
211
|
+
For integer inputs, this produces the algebraic constant (e.g. √2).
|
|
212
|
+
"""
|
|
213
|
+
return _sqrt_decimal(self)
|
|
214
|
+
|
|
215
|
+
# ------------------------------------------------------------------
|
|
216
|
+
# Comparison — the truth strand governs, always
|
|
217
|
+
# ------------------------------------------------------------------
|
|
218
|
+
|
|
219
|
+
def __eq__(self, other):
|
|
220
|
+
if isinstance(other, (int, float)):
|
|
221
|
+
other = _coerce(other)
|
|
222
|
+
if not isinstance(other, MobiusConstant):
|
|
223
|
+
return NotImplemented
|
|
224
|
+
return self._value == other._value
|
|
225
|
+
|
|
226
|
+
def __ne__(self, other):
|
|
227
|
+
if isinstance(other, (int, float)):
|
|
228
|
+
other = _coerce(other)
|
|
229
|
+
if not isinstance(other, MobiusConstant):
|
|
230
|
+
return NotImplemented
|
|
231
|
+
return self._value != other._value
|
|
232
|
+
|
|
233
|
+
def __lt__(self, other):
|
|
234
|
+
other = _coerce(other)
|
|
235
|
+
return self._value < other._value
|
|
236
|
+
|
|
237
|
+
def __le__(self, other):
|
|
238
|
+
other = _coerce(other)
|
|
239
|
+
return self._value <= other._value
|
|
240
|
+
|
|
241
|
+
def __gt__(self, other):
|
|
242
|
+
other = _coerce(other)
|
|
243
|
+
return self._value > other._value
|
|
244
|
+
|
|
245
|
+
def __ge__(self, other):
|
|
246
|
+
other = _coerce(other)
|
|
247
|
+
return self._value >= other._value
|
|
248
|
+
|
|
249
|
+
# ------------------------------------------------------------------
|
|
250
|
+
# Display
|
|
251
|
+
# ------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
def __repr__(self):
|
|
254
|
+
if self._name:
|
|
255
|
+
return f"MC({self._name})"
|
|
256
|
+
# Show enough digits to be useful, not all 100
|
|
257
|
+
s = str(self._value)
|
|
258
|
+
if len(s) > 22:
|
|
259
|
+
s = s[:20] + "..."
|
|
260
|
+
return f"MC('{s}')"
|
|
261
|
+
|
|
262
|
+
def __str__(self):
|
|
263
|
+
return str(self._value)
|
|
264
|
+
|
|
265
|
+
def __float__(self):
|
|
266
|
+
return float(self._value)
|
|
267
|
+
|
|
268
|
+
def __int__(self):
|
|
269
|
+
return int(self._value)
|
|
270
|
+
|
|
271
|
+
def __hash__(self):
|
|
272
|
+
return hash(self._value)
|
|
273
|
+
|
|
274
|
+
# ------------------------------------------------------------------
|
|
275
|
+
# Diagnostic
|
|
276
|
+
# ------------------------------------------------------------------
|
|
277
|
+
|
|
278
|
+
def diagnose(self) -> dict:
|
|
279
|
+
"""Return both strands, the residue, and identity info."""
|
|
280
|
+
return {
|
|
281
|
+
"binary_strand": self._approx,
|
|
282
|
+
"truth_strand": str(self._value),
|
|
283
|
+
"residue": str(self.residue),
|
|
284
|
+
"name": self._name,
|
|
285
|
+
"poly": self._poly,
|
|
286
|
+
"collapsed": self.collapse(),
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
# ------------------------------------------------------------------
|
|
290
|
+
# Internal factory
|
|
291
|
+
# ------------------------------------------------------------------
|
|
292
|
+
|
|
293
|
+
@classmethod
|
|
294
|
+
def _from_parts(cls, approx: float, value: Decimal, name=None, poly=None):
|
|
295
|
+
obj = cls.__new__(cls)
|
|
296
|
+
obj._approx = approx
|
|
297
|
+
obj._value = value
|
|
298
|
+
obj._name = name
|
|
299
|
+
obj._poly = poly
|
|
300
|
+
return obj
|
|
301
|
+
|
|
302
|
+
|
|
303
|
+
# ======================================================================
|
|
304
|
+
# Module-level helpers
|
|
305
|
+
# ======================================================================
|
|
306
|
+
|
|
307
|
+
def _coerce(value) -> MobiusConstant:
|
|
308
|
+
"""Convert a raw value into a MobiusConstant."""
|
|
309
|
+
if isinstance(value, MobiusConstant):
|
|
310
|
+
return value
|
|
311
|
+
return MobiusConstant(value)
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
def _poly_square(mc: MobiusConstant) -> MobiusConstant:
|
|
315
|
+
"""
|
|
316
|
+
Compute x² using the minimal polynomial identity.
|
|
317
|
+
|
|
318
|
+
For a degree-2 polynomial ax² + bx + c = 0:
|
|
319
|
+
x² = -(bx + c) / a
|
|
320
|
+
|
|
321
|
+
This is exact by construction — the polynomial IS the truth.
|
|
322
|
+
√2² = 2, φ² = φ + 1, etc.
|
|
323
|
+
"""
|
|
324
|
+
a, b, c = [Decimal(coef) for coef in mc._poly]
|
|
325
|
+
# x² = -(b*x + c) / a
|
|
326
|
+
result_value = -(b * mc._value + c) / a
|
|
327
|
+
result_approx = -(float(b) * mc._approx + float(c)) / float(a)
|
|
328
|
+
return MobiusConstant._from_parts(result_approx, result_value)
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
def _poly_reciprocal(mc: MobiusConstant) -> MobiusConstant:
|
|
332
|
+
"""
|
|
333
|
+
Compute 1/x using the minimal polynomial identity.
|
|
334
|
+
|
|
335
|
+
For a degree-2 polynomial ax² + bx + c = 0:
|
|
336
|
+
ax² + bx + c = 0 → x(ax + b) = -c → 1/x = -(ax + b) / c
|
|
337
|
+
|
|
338
|
+
This is exact by construction.
|
|
339
|
+
1/φ = φ − 1, etc.
|
|
340
|
+
"""
|
|
341
|
+
a, b, c = [Decimal(coef) for coef in mc._poly]
|
|
342
|
+
# 1/x = -(a*x + b) / c
|
|
343
|
+
result_value = -(a * mc._value + b) / c
|
|
344
|
+
result_approx = -(float(a) * mc._approx + float(b)) / float(c)
|
|
345
|
+
return MobiusConstant._from_parts(result_approx, result_value)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _sqrt_decimal(mc: MobiusConstant) -> MobiusConstant:
|
|
349
|
+
"""
|
|
350
|
+
Compute square root to full Decimal precision via Newton's method.
|
|
351
|
+
Pure arithmetic on the truth strand — no floats consulted.
|
|
352
|
+
"""
|
|
353
|
+
import math
|
|
354
|
+
x = mc._value
|
|
355
|
+
|
|
356
|
+
if x < 0:
|
|
357
|
+
raise ValueError("Cannot take square root of negative MobiusConstant")
|
|
358
|
+
if x == 0:
|
|
359
|
+
return MobiusConstant(0)
|
|
360
|
+
|
|
361
|
+
# Newton's method: x_{n+1} = (x_n + S/x_n) / 2
|
|
362
|
+
# Start from float approximation
|
|
363
|
+
guess = Decimal(str(math.sqrt(float(x))))
|
|
364
|
+
two = Decimal(2)
|
|
365
|
+
for _ in range(200): # quadratic convergence
|
|
366
|
+
new_guess = (guess + x / guess) / two
|
|
367
|
+
if new_guess == guess:
|
|
368
|
+
break
|
|
369
|
+
guess = new_guess
|
|
370
|
+
|
|
371
|
+
# If the input is an integer or exact rational, tag the result
|
|
372
|
+
# with its minimal polynomial so that squaring is exact.
|
|
373
|
+
# sqrt(n) has minimal polynomial x² - n = 0 → poly = (1, 0, -n)
|
|
374
|
+
int_val = None
|
|
375
|
+
if x == int(x):
|
|
376
|
+
int_val = int(x)
|
|
377
|
+
# Check if it's a perfect square first
|
|
378
|
+
isqrt = int(Decimal(int_val).sqrt())
|
|
379
|
+
if isqrt * isqrt == int_val:
|
|
380
|
+
return MobiusConstant(isqrt)
|
|
381
|
+
|
|
382
|
+
result = MobiusConstant._from_parts(
|
|
383
|
+
math.sqrt(mc._approx),
|
|
384
|
+
guess,
|
|
385
|
+
poly=(1, 0, -int_val) if int_val is not None else None,
|
|
386
|
+
)
|
|
387
|
+
return result
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def _pow_decimal(base: MobiusConstant, exp: MobiusConstant) -> MobiusConstant:
|
|
391
|
+
"""
|
|
392
|
+
General exponentiation for non-integer exponents.
|
|
393
|
+
Uses float for speed strand, mpmath for truth strand.
|
|
394
|
+
"""
|
|
395
|
+
from mpmath import mp, mpf, power, nstr
|
|
396
|
+
|
|
397
|
+
old_dps = mp.dps
|
|
398
|
+
mp.dps = 120
|
|
399
|
+
try:
|
|
400
|
+
base_mp = mpf(str(base._value))
|
|
401
|
+
exp_mp = mpf(str(exp._value))
|
|
402
|
+
result_mp = power(base_mp, exp_mp)
|
|
403
|
+
result_str = nstr(result_mp, 100)
|
|
404
|
+
finally:
|
|
405
|
+
mp.dps = old_dps
|
|
406
|
+
|
|
407
|
+
return MobiusConstant._from_parts(
|
|
408
|
+
base._approx ** exp._approx,
|
|
409
|
+
Decimal(result_str),
|
|
410
|
+
)
|
|
411
|
+
|
|
412
|
+
|
|
413
|
+
def sqrt(value) -> MobiusConstant:
|
|
414
|
+
"""Module-level sqrt: exact square root via the truth strand."""
|
|
415
|
+
return _coerce(value).sqrt()
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
"""Tests for MöbiusConstant — exact irrational constants."""
|
|
2
|
+
|
|
3
|
+
import math
|
|
4
|
+
import pytest
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
from mobius_constant import (
|
|
7
|
+
MobiusConstant, MC, sqrt,
|
|
8
|
+
Pi, Euler, Sqrt2, Sqrt3, Phi, Ln2,
|
|
9
|
+
Alpha_inv, Alpha, VERIFIED_DEPTH,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# =====================================================================
|
|
14
|
+
# THE PROOF: sqrt(2) ** 2 == 2
|
|
15
|
+
# =====================================================================
|
|
16
|
+
|
|
17
|
+
class TestTheProof:
|
|
18
|
+
"""The problem that started it all — irrational identity failures."""
|
|
19
|
+
|
|
20
|
+
def test_ieee754_sqrt2_squared_fails(self):
|
|
21
|
+
"""IEEE 754 gets this wrong."""
|
|
22
|
+
assert math.sqrt(2) ** 2 != 2 # 2.0000000000000004
|
|
23
|
+
|
|
24
|
+
def test_mobius_sqrt2_squared(self):
|
|
25
|
+
"""MöbiusConstant gets it right."""
|
|
26
|
+
assert Sqrt2 ** 2 == 2
|
|
27
|
+
|
|
28
|
+
def test_ieee754_sqrt3_squared_fails(self):
|
|
29
|
+
assert math.sqrt(3) ** 2 != 3
|
|
30
|
+
|
|
31
|
+
def test_mobius_sqrt3_squared(self):
|
|
32
|
+
assert Sqrt3 ** 2 == 3
|
|
33
|
+
|
|
34
|
+
def test_sqrt2_times_sqrt2(self):
|
|
35
|
+
"""√2 × √2 = 2 exactly."""
|
|
36
|
+
assert Sqrt2 * Sqrt2 == 2
|
|
37
|
+
|
|
38
|
+
def test_sqrt3_times_sqrt3(self):
|
|
39
|
+
"""√3 × √3 = 3 exactly."""
|
|
40
|
+
assert Sqrt3 * Sqrt3 == 3
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# =====================================================================
|
|
44
|
+
# PI IDENTITIES
|
|
45
|
+
# =====================================================================
|
|
46
|
+
|
|
47
|
+
class TestPiIdentities:
|
|
48
|
+
"""π arithmetic that IEEE 754 gets wrong."""
|
|
49
|
+
|
|
50
|
+
def test_pi_plus_pi_equals_two_pi(self):
|
|
51
|
+
assert Pi + Pi == Pi * 2
|
|
52
|
+
|
|
53
|
+
def test_pi_minus_pi_is_zero(self):
|
|
54
|
+
assert Pi - Pi == 0
|
|
55
|
+
|
|
56
|
+
def test_pi_div_pi_is_one(self):
|
|
57
|
+
assert Pi / Pi == 1
|
|
58
|
+
|
|
59
|
+
def test_two_pi_div_two_is_pi(self):
|
|
60
|
+
result = (Pi * 2) / MC(2)
|
|
61
|
+
assert result == Pi
|
|
62
|
+
|
|
63
|
+
def test_pi_times_one_is_pi(self):
|
|
64
|
+
assert Pi * 1 == Pi
|
|
65
|
+
|
|
66
|
+
def test_pi_squared_consistency(self):
|
|
67
|
+
"""Pi² computed two ways must agree."""
|
|
68
|
+
a = Pi * Pi
|
|
69
|
+
b = Pi ** 2
|
|
70
|
+
assert a == b
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
# =====================================================================
|
|
74
|
+
# GOLDEN RATIO IDENTITIES
|
|
75
|
+
# =====================================================================
|
|
76
|
+
|
|
77
|
+
class TestPhiIdentities:
|
|
78
|
+
"""The golden ratio satisfies x² = x + 1."""
|
|
79
|
+
|
|
80
|
+
def test_ieee754_phi_identity_passes_by_luck(self):
|
|
81
|
+
"""IEEE 754 gets φ² = φ + 1 right by cancellation luck."""
|
|
82
|
+
phi_f = (1 + math.sqrt(5)) / 2
|
|
83
|
+
assert phi_f ** 2 == phi_f + 1 # luck, not construction
|
|
84
|
+
|
|
85
|
+
def test_mobius_phi_identity(self):
|
|
86
|
+
"""φ² = φ + 1 exactly."""
|
|
87
|
+
assert Phi ** 2 == Phi + 1
|
|
88
|
+
|
|
89
|
+
def test_phi_minus_one_reciprocal(self):
|
|
90
|
+
"""1/φ = φ - 1 exactly."""
|
|
91
|
+
assert MC(1) / Phi == Phi - MC(1)
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
# =====================================================================
|
|
95
|
+
# SQRT FUNCTION
|
|
96
|
+
# =====================================================================
|
|
97
|
+
|
|
98
|
+
class TestSqrt:
|
|
99
|
+
"""Module-level sqrt produces exact results."""
|
|
100
|
+
|
|
101
|
+
def test_sqrt_4(self):
|
|
102
|
+
assert sqrt(4) == 2
|
|
103
|
+
|
|
104
|
+
def test_sqrt_9(self):
|
|
105
|
+
assert sqrt(9) == 3
|
|
106
|
+
|
|
107
|
+
def test_sqrt_1(self):
|
|
108
|
+
assert sqrt(1) == 1
|
|
109
|
+
|
|
110
|
+
def test_sqrt_0(self):
|
|
111
|
+
assert sqrt(0) == 0
|
|
112
|
+
|
|
113
|
+
def test_sqrt_of_mc(self):
|
|
114
|
+
"""sqrt of a MobiusConstant."""
|
|
115
|
+
result = sqrt(MC(2))
|
|
116
|
+
# result² should equal 2
|
|
117
|
+
assert result ** 2 == 2
|
|
118
|
+
|
|
119
|
+
def test_sqrt_negative_raises(self):
|
|
120
|
+
with pytest.raises(ValueError):
|
|
121
|
+
sqrt(-1)
|
|
122
|
+
|
|
123
|
+
def test_sqrt_product(self):
|
|
124
|
+
"""√2² × √8² = 2 × 8 = 16."""
|
|
125
|
+
s2 = sqrt(MC(2))
|
|
126
|
+
s8 = sqrt(MC(8))
|
|
127
|
+
# Each squares exactly via polynomial identity
|
|
128
|
+
assert s2 ** 2 * (s8 ** 2) == MC(16)
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
# =====================================================================
|
|
132
|
+
# ALPHA CONSISTENCY
|
|
133
|
+
# =====================================================================
|
|
134
|
+
|
|
135
|
+
class TestAlpha:
|
|
136
|
+
"""The fine-structure constant cross-checks."""
|
|
137
|
+
|
|
138
|
+
def test_alpha_times_alpha_inv(self):
|
|
139
|
+
"""α × α⁻¹ = 1."""
|
|
140
|
+
result = Alpha * Alpha_inv
|
|
141
|
+
assert result == 1
|
|
142
|
+
|
|
143
|
+
def test_alpha_inv_positive(self):
|
|
144
|
+
assert Alpha_inv > 137
|
|
145
|
+
|
|
146
|
+
def test_alpha_inv_less_than_138(self):
|
|
147
|
+
assert Alpha_inv < 138
|
|
148
|
+
|
|
149
|
+
def test_alpha_inv_starts_correctly(self):
|
|
150
|
+
"""First 12 digits match CODATA 2022."""
|
|
151
|
+
s = str(Alpha_inv.value)
|
|
152
|
+
assert s.startswith("137.035999")
|
|
153
|
+
|
|
154
|
+
def test_verified_depth(self):
|
|
155
|
+
assert VERIFIED_DEPTH == 100
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# =====================================================================
|
|
159
|
+
# CONSTRUCTION
|
|
160
|
+
# =====================================================================
|
|
161
|
+
|
|
162
|
+
class TestConstruction:
|
|
163
|
+
"""Every input path produces the correct truth strand."""
|
|
164
|
+
|
|
165
|
+
def test_from_string(self):
|
|
166
|
+
n = MC("3.14")
|
|
167
|
+
assert n.value == Decimal("3.14")
|
|
168
|
+
|
|
169
|
+
def test_from_int(self):
|
|
170
|
+
n = MC(42)
|
|
171
|
+
assert n.value == Decimal(42)
|
|
172
|
+
assert n.approx == 42.0
|
|
173
|
+
|
|
174
|
+
def test_from_float(self):
|
|
175
|
+
n = MC(0.5)
|
|
176
|
+
assert n.value == Decimal("0.5")
|
|
177
|
+
|
|
178
|
+
def test_from_decimal(self):
|
|
179
|
+
n = MC(Decimal("1.41421356"))
|
|
180
|
+
assert n.value == Decimal("1.41421356")
|
|
181
|
+
|
|
182
|
+
def test_from_mobius_constant(self):
|
|
183
|
+
a = MC("3.14")
|
|
184
|
+
b = MC(a)
|
|
185
|
+
assert b.value == a.value
|
|
186
|
+
|
|
187
|
+
def test_invalid_type_raises(self):
|
|
188
|
+
with pytest.raises(TypeError):
|
|
189
|
+
MC([1, 2, 3])
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
# =====================================================================
|
|
193
|
+
# ARITHMETIC
|
|
194
|
+
# =====================================================================
|
|
195
|
+
|
|
196
|
+
class TestArithmetic:
|
|
197
|
+
"""Both strands propagate through every operation."""
|
|
198
|
+
|
|
199
|
+
def test_addition(self):
|
|
200
|
+
result = MC("1.5") + MC("2.5")
|
|
201
|
+
assert result == 4
|
|
202
|
+
|
|
203
|
+
def test_subtraction(self):
|
|
204
|
+
result = MC("5.0") - MC("2.0")
|
|
205
|
+
assert result == 3
|
|
206
|
+
|
|
207
|
+
def test_multiplication(self):
|
|
208
|
+
result = MC("3.0") * MC("4.0")
|
|
209
|
+
assert result == 12
|
|
210
|
+
|
|
211
|
+
def test_division(self):
|
|
212
|
+
result = MC("10") / MC("4")
|
|
213
|
+
assert result == MC("2.5")
|
|
214
|
+
|
|
215
|
+
def test_negation(self):
|
|
216
|
+
assert (-MC("5")).value == Decimal("-5")
|
|
217
|
+
|
|
218
|
+
def test_abs(self):
|
|
219
|
+
assert abs(MC("-7")).value == Decimal("7")
|
|
220
|
+
|
|
221
|
+
def test_power_int(self):
|
|
222
|
+
assert (MC("3") ** 3) == 27
|
|
223
|
+
|
|
224
|
+
def test_division_by_zero(self):
|
|
225
|
+
with pytest.raises(ZeroDivisionError):
|
|
226
|
+
MC("1") / MC("0")
|
|
227
|
+
|
|
228
|
+
def test_radd(self):
|
|
229
|
+
result = 1 + MC("0.5")
|
|
230
|
+
assert result == MC("1.5")
|
|
231
|
+
|
|
232
|
+
def test_rsub(self):
|
|
233
|
+
result = 10 - MC("3")
|
|
234
|
+
assert result == 7
|
|
235
|
+
|
|
236
|
+
def test_rmul(self):
|
|
237
|
+
result = 3 * MC("2.5")
|
|
238
|
+
assert result == MC("7.5")
|
|
239
|
+
|
|
240
|
+
def test_rtruediv(self):
|
|
241
|
+
result = 10 / MC("4")
|
|
242
|
+
assert result == MC("2.5")
|
|
243
|
+
|
|
244
|
+
|
|
245
|
+
# =====================================================================
|
|
246
|
+
# COMPARISON — TRUTH GOVERNS
|
|
247
|
+
# =====================================================================
|
|
248
|
+
|
|
249
|
+
class TestComparison:
|
|
250
|
+
"""Equality and ordering use the truth strand, not the float."""
|
|
251
|
+
|
|
252
|
+
def test_equality(self):
|
|
253
|
+
assert MC("3.14") == MC("3.14")
|
|
254
|
+
|
|
255
|
+
def test_inequality(self):
|
|
256
|
+
assert MC("3.14") != MC("3.15")
|
|
257
|
+
|
|
258
|
+
def test_less_than(self):
|
|
259
|
+
assert MC("1") < MC("2")
|
|
260
|
+
|
|
261
|
+
def test_greater_than(self):
|
|
262
|
+
assert Pi > MC(3)
|
|
263
|
+
|
|
264
|
+
def test_less_equal(self):
|
|
265
|
+
assert MC("3") <= MC("3")
|
|
266
|
+
|
|
267
|
+
def test_greater_equal(self):
|
|
268
|
+
assert MC("3") >= MC("3")
|
|
269
|
+
|
|
270
|
+
def test_hash_consistency(self):
|
|
271
|
+
"""Equal MöbiusConstants must have equal hashes."""
|
|
272
|
+
a = MC("5") + MC("5")
|
|
273
|
+
b = MC("10")
|
|
274
|
+
assert hash(a) == hash(b)
|
|
275
|
+
|
|
276
|
+
def test_usable_as_dict_key(self):
|
|
277
|
+
d = {MC("10"): "found"}
|
|
278
|
+
key = MC("5") + MC("5")
|
|
279
|
+
assert d[key] == "found"
|
|
280
|
+
|
|
281
|
+
def test_cross_type_int(self):
|
|
282
|
+
assert MC(5) == 5
|
|
283
|
+
|
|
284
|
+
def test_cross_type_float(self):
|
|
285
|
+
assert MC(0.5) == 0.5
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
# =====================================================================
|
|
289
|
+
# STRAND ANATOMY
|
|
290
|
+
# =====================================================================
|
|
291
|
+
|
|
292
|
+
class TestStrands:
|
|
293
|
+
"""The residue must be the gap between float and truth."""
|
|
294
|
+
|
|
295
|
+
def test_residue_of_pi(self):
|
|
296
|
+
"""Pi's float is not exactly π. The residue captures the gap."""
|
|
297
|
+
r = Pi.residue
|
|
298
|
+
assert r != 0 # float can't hold 100 digits
|
|
299
|
+
|
|
300
|
+
def test_residue_of_integer_is_zero(self):
|
|
301
|
+
n = MC(1)
|
|
302
|
+
assert n.residue == Decimal("0")
|
|
303
|
+
|
|
304
|
+
def test_collapse_returns_float(self):
|
|
305
|
+
assert isinstance(Pi.collapse(), float)
|
|
306
|
+
|
|
307
|
+
def test_diagnose_returns_dict(self):
|
|
308
|
+
d = Pi.diagnose()
|
|
309
|
+
assert "binary_strand" in d
|
|
310
|
+
assert "truth_strand" in d
|
|
311
|
+
assert "residue" in d
|
|
312
|
+
assert d["name"] == "pi"
|
|
313
|
+
|
|
314
|
+
def test_named_constant_repr(self):
|
|
315
|
+
assert "pi" in repr(Pi)
|
|
316
|
+
|
|
317
|
+
def test_unnamed_repr(self):
|
|
318
|
+
r = repr(MC("3.14159"))
|
|
319
|
+
assert "MC(" in r
|
|
320
|
+
|
|
321
|
+
|
|
322
|
+
# =====================================================================
|
|
323
|
+
# THE FAMOUS FAILURES — IRRATIONAL EDITION
|
|
324
|
+
# =====================================================================
|
|
325
|
+
|
|
326
|
+
class TestFamousFailures:
|
|
327
|
+
"""Every classic IEEE 754 irrational failure, corrected."""
|
|
328
|
+
|
|
329
|
+
def test_sqrt2_squared(self):
|
|
330
|
+
"""√2² = 2. IEEE gets 2.0000000000000004."""
|
|
331
|
+
assert Sqrt2 ** 2 == 2
|
|
332
|
+
|
|
333
|
+
def test_sqrt3_squared(self):
|
|
334
|
+
"""√3² = 3."""
|
|
335
|
+
assert Sqrt3 ** 2 == 3
|
|
336
|
+
|
|
337
|
+
def test_phi_squared_minus_phi(self):
|
|
338
|
+
"""φ² − φ = 1."""
|
|
339
|
+
assert Phi ** 2 - Phi == 1
|
|
340
|
+
|
|
341
|
+
def test_sqrt2_times_sqrt8_equals_4(self):
|
|
342
|
+
"""√2² × √8² = 16."""
|
|
343
|
+
s8 = sqrt(MC(8))
|
|
344
|
+
assert Sqrt2 ** 2 * (s8 ** 2) == MC(16)
|
|
345
|
+
|
|
346
|
+
def test_sqrt_product_identity(self):
|
|
347
|
+
"""√a² × √b² = a × b. The polynomial resolves each side."""
|
|
348
|
+
s2 = sqrt(MC(2))
|
|
349
|
+
s3 = sqrt(MC(3))
|
|
350
|
+
# √2² = 2, √3² = 3 (each exact via polynomial), product = 6
|
|
351
|
+
assert s2 ** 2 * (s3 ** 2) == MC(6)
|
|
352
|
+
# √6² = 6 (exact via polynomial)
|
|
353
|
+
s6 = sqrt(MC(6))
|
|
354
|
+
assert s6 ** 2 == MC(6)
|
|
355
|
+
|
|
356
|
+
def test_catastrophic_subtraction(self):
|
|
357
|
+
"""π − π = 0 exactly, even though floats can't hold π."""
|
|
358
|
+
assert Pi - Pi == 0
|
|
359
|
+
|
|
360
|
+
def test_alpha_inv_in_range(self):
|
|
361
|
+
"""α⁻¹ is between 137 and 138."""
|
|
362
|
+
assert MC(137) < Alpha_inv < MC(138)
|
|
363
|
+
|
|
364
|
+
def test_integer_sqrt_exact(self):
|
|
365
|
+
"""√4 = 2, √9 = 3, √16 = 4 — exact integers."""
|
|
366
|
+
assert sqrt(MC(4)) == 2
|
|
367
|
+
assert sqrt(MC(9)) == 3
|
|
368
|
+
assert sqrt(MC(16)) == 4
|
|
369
|
+
assert sqrt(MC(100)) == 10
|