numly 0.2.0__tar.gz → 0.2.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numly
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Convert numbers across numeral systems — Roman, Chinese, Greek, Egyptian, Arabic-Indic and more.
5
5
  Home-page: https://github.com/TheMadrasTechie/numly
6
6
  Author: TheMadrasTechie
@@ -0,0 +1,97 @@
1
+ """
2
+ numly
3
+ ~~~~~
4
+ A Python library for working with numbers across numeral systems.
5
+
6
+ Supported systems
7
+ -----------------
8
+ decimal : Standard base-10
9
+ roman : Roman numerals (I V X L C D M)
10
+ arabic_indic : Eastern Arabic digits (٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩)
11
+ chinese : Traditional Chinese (零 一 二 三 四 … 万)
12
+ greek : Greek alphabetic (Α Β Γ … Ω + ͵ for thousands)
13
+ egyptian : Egyptian hieroglyphs (𓏺 𓎆 𓍢 𓆼 𓂭 𓆐 𓁨)
14
+ tamil : Ancient Tamil numerals (௧ ௨ ௩ … ௰ ௱ ௲)
15
+ babylonian : Babylonian cuneiform (𒁹 𒌋 — base 60)
16
+ mayan : Mayan vigesimal (𝋠 𝋡 𝋢 … 𝋳 — base 20)
17
+ binary : Base-2
18
+ octal : Base-8
19
+ hex : Base-16
20
+ words : English words (Western & Indian systems)
21
+
22
+ Quick start
23
+ -----------
24
+ import numly
25
+
26
+ numly.to_roman(2024) # 'MMXXIV'
27
+ numly.to_chinese(42) # '四十二'
28
+ numly.to_tamil(42) # '௪௰௨'
29
+ numly.to_babylonian(42) # '𒌋𒌋𒌋𒌋𒁹𒁹'
30
+ numly.to_mayan(42) # '𝋂 𝋂'
31
+ numly.to_binary(42) # '101010'
32
+ numly.to_hex(255) # 'FF'
33
+ numly.to_words(1_234_567) # 'one million two hundred...'
34
+ numly.to_words(1_234_567, 'indian') # 'twelve lakh thirty four thousand...'
35
+ numly.convert("MMXXIV", "roman", "greek") # '͵ΒΚΔʹ'
36
+ numly.to_all(42) # dict of every system
37
+ """
38
+
39
+ __version__ = "0.2.0"
40
+ __author__ = "TheMadrasTechie"
41
+ __license__ = "MIT"
42
+
43
+ # ── numeral systems ────────────────────────────────────────────────────────
44
+ from numly.roman import to_roman, from_roman, is_valid as is_valid_roman
45
+ from numly.arabic_indic import to_arabic_indic, from_arabic_indic, is_valid as is_valid_arabic_indic
46
+ from numly.chinese import to_chinese, from_chinese, is_valid as is_valid_chinese
47
+ from numly.greek import to_greek, from_greek, is_valid as is_valid_greek
48
+ from numly.egyptian import to_egyptian, from_egyptian, is_valid as is_valid_egyptian
49
+ from numly.egyptian import symbol_breakdown
50
+ from numly.tamil import to_tamil, from_tamil, is_valid as is_valid_tamil
51
+ from numly.babylonian import to_babylonian, from_babylonian, is_valid as is_valid_babylonian
52
+ from numly.mayan import to_mayan, from_mayan, is_valid as is_valid_mayan
53
+ from numly.mayan import to_mayan_text
54
+
55
+ # ── base conversions ───────────────────────────────────────────────────────
56
+ from numly.base import (
57
+ to_binary, from_binary,
58
+ to_octal, from_octal,
59
+ to_hex, from_hex,
60
+ to_base, from_base,
61
+ )
62
+
63
+ # ── english words ──────────────────────────────────────────────────────────
64
+ from numly.words import to_words, to_words_western, to_words_indian
65
+
66
+ # ── universal converter ────────────────────────────────────────────────────
67
+ from numly.convert import convert, to_all, SYSTEMS, supported_systems
68
+
69
+ __all__ = [
70
+ # roman
71
+ "to_roman", "from_roman", "is_valid_roman",
72
+ # arabic-indic
73
+ "to_arabic_indic", "from_arabic_indic", "is_valid_arabic_indic",
74
+ # chinese
75
+ "to_chinese", "from_chinese", "is_valid_chinese",
76
+ # greek
77
+ "to_greek", "from_greek", "is_valid_greek",
78
+ # egyptian
79
+ "to_egyptian", "from_egyptian", "is_valid_egyptian", "symbol_breakdown",
80
+ # tamil
81
+ "to_tamil", "from_tamil", "is_valid_tamil",
82
+ # babylonian
83
+ "to_babylonian", "from_babylonian", "is_valid_babylonian",
84
+ # mayan
85
+ "to_mayan", "from_mayan", "is_valid_mayan", "to_mayan_text",
86
+ # base conversions
87
+ "to_binary", "from_binary",
88
+ "to_octal", "from_octal",
89
+ "to_hex", "from_hex",
90
+ "to_base", "from_base",
91
+ # words
92
+ "to_words", "to_words_western", "to_words_indian",
93
+ # universal
94
+ "convert", "to_all", "SYSTEMS", "supported_systems",
95
+ # meta
96
+ "__version__",
97
+ ]
@@ -0,0 +1,157 @@
1
+ """
2
+ numly.babylonian
3
+ ~~~~~~~~~~~~~~~~
4
+ Convert integers to Babylonian cuneiform numerals and back.
5
+
6
+ Babylonian mathematics used a base-60 (sexagesimal) positional system.
7
+ Within each "digit" (0–59), values are written additively:
8
+ 𒁹 = 1 (vertical wedge) repeated up to 9×
9
+ 𒌋 = 10 (Winkelhaken / corner) repeated up to 5×
10
+
11
+ Digits are separated by a space. The system has no true zero
12
+ (a blank position is represented by the placeholder 𒑱).
13
+
14
+ Unicode block: Cuneiform (U+12000–U+123FF)
15
+
16
+ Range: 1 – 216,000 (60³ — three sexagesimal digits)
17
+
18
+ Examples
19
+ --------
20
+ >>> from numly.babylonian import to_babylonian, from_babylonian
21
+ >>> to_babylonian(42)
22
+ '𒌋𒌋𒌋𒌋𒁹𒁹'
23
+ >>> to_babylonian(61)
24
+ '𒁹 𒁹'
25
+ >>> from_babylonian('𒁹 𒁹')
26
+ 61
27
+ """
28
+
29
+ _ONE = "𒁹" # U+12079 CUNEIFORM SIGN DIŠ (1)
30
+ _TEN = "𒌋" # U+1230B CUNEIFORM SIGN U (10)
31
+ _ZERO = "𒑱" # U+12471 CUNEIFORM PUNCTUATION MARK STACKED DISHES (placeholder 0)
32
+ _SEP = " " # digit separator
33
+
34
+ SYSTEM = "babylonian"
35
+ BASE = 60
36
+ MIN, MAX = 1, BASE ** 3 # 1 – 216,000
37
+
38
+
39
+ def _encode_digit(n: int) -> str:
40
+ """Encode one sexagesimal digit (0–59) to cuneiform."""
41
+ if n == 0:
42
+ return _ZERO
43
+ tens = n // 10
44
+ ones = n % 10
45
+ return _TEN * tens + _ONE * ones
46
+
47
+
48
+ def _decode_digit(s: str) -> int:
49
+ """Decode one sexagesimal digit string to integer."""
50
+ if s == _ZERO:
51
+ return 0
52
+ result = 0
53
+ for ch in s:
54
+ if ch == _TEN:
55
+ result += 10
56
+ elif ch == _ONE:
57
+ result += 1
58
+ else:
59
+ raise ValueError(f"Unknown cuneiform character: {ch!r}")
60
+ return result
61
+
62
+
63
+ def to_babylonian(num: int) -> str:
64
+ """
65
+ Convert an integer to a Babylonian cuneiform numeral string.
66
+
67
+ Args:
68
+ num: Positive integer between 1 and 216,000.
69
+
70
+ Returns:
71
+ Cuneiform string with sexagesimal digits separated by spaces,
72
+ e.g. '𒌋𒌋𒌋𒌋𒁹𒁹' (42) or '𒁹 𒁹' (61).
73
+
74
+ Raises:
75
+ TypeError: If num is not an int.
76
+ ValueError: If num is outside 1–216,000.
77
+
78
+ Examples:
79
+ >>> to_babylonian(1)
80
+ '𒁹'
81
+ >>> to_babylonian(10)
82
+ '𒌋'
83
+ >>> to_babylonian(42)
84
+ '𒌋𒌋𒌋𒌋𒁹𒁹'
85
+ >>> to_babylonian(60)
86
+ '𒁹 𒑱'
87
+ >>> to_babylonian(61)
88
+ '𒁹 𒁹'
89
+ >>> to_babylonian(3600)
90
+ '𒁹 𒑱 𒑱'
91
+ """
92
+ if not isinstance(num, int):
93
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
94
+ if not MIN <= num <= MAX:
95
+ raise ValueError(f"Babylonian numerals support {MIN}–{MAX:,}, got {num}")
96
+
97
+ digits = []
98
+ n = num
99
+ while n:
100
+ digits.append(_encode_digit(n % BASE))
101
+ n //= BASE
102
+
103
+ return _SEP.join(reversed(digits))
104
+
105
+
106
+ def from_babylonian(s: str) -> int:
107
+ """
108
+ Convert a Babylonian cuneiform numeral string to an integer.
109
+
110
+ Args:
111
+ s: Cuneiform string with digits separated by spaces,
112
+ e.g. '𒁹 𒁹'.
113
+
114
+ Returns:
115
+ Integer value.
116
+
117
+ Raises:
118
+ TypeError: If s is not a str.
119
+ ValueError: If s contains unknown characters or is empty.
120
+
121
+ Examples:
122
+ >>> from_babylonian('𒌋𒌋𒌋𒌋𒁹𒁹')
123
+ 42
124
+ >>> from_babylonian('𒁹 𒁹')
125
+ 61
126
+ >>> from_babylonian('𒁹 𒑱 𒑱')
127
+ 3600
128
+ """
129
+ if not isinstance(s, str):
130
+ raise TypeError(f"Expected str, got {type(s).__name__!r}")
131
+ s = s.strip()
132
+ if not s:
133
+ raise ValueError("Empty string")
134
+
135
+ parts = s.split(_SEP)
136
+ result = 0
137
+ for part in parts:
138
+ result = result * BASE + _decode_digit(part)
139
+ return result
140
+
141
+
142
+ def is_valid(s: str) -> bool:
143
+ """Check whether a string is a valid Babylonian cuneiform numeral."""
144
+ try:
145
+ from_babylonian(s)
146
+ return True
147
+ except (TypeError, ValueError):
148
+ return False
149
+
150
+
151
+ # ── quick demo ─────────────────────────────────────────────────────────────
152
+ if __name__ == "__main__":
153
+ tests = [1, 10, 42, 59, 60, 61, 600, 3600, 3661, 216_000]
154
+ for n in tests:
155
+ r = to_babylonian(n)
156
+ back = from_babylonian(r)
157
+ print(f"{n:<8} → {r:<30} → {back}")
@@ -0,0 +1,284 @@
1
+ """
2
+ numly.base
3
+ ~~~~~~~~~~
4
+ Convert integers to Binary, Octal, and Hexadecimal and back.
5
+
6
+ binary : base-2 (digits: 0 1)
7
+ octal : base-8 (digits: 0–7)
8
+ hex : base-16 (digits: 0–9 A–F)
9
+
10
+ Range: 0 – unlimited (all three support arbitrarily large integers)
11
+
12
+ Examples
13
+ --------
14
+ >>> from numly.base import to_binary, to_octal, to_hex
15
+ >>> to_binary(42)
16
+ '101010'
17
+ >>> to_octal(42)
18
+ '52'
19
+ >>> to_hex(42)
20
+ '2A'
21
+ """
22
+
23
+ SYSTEM = "base"
24
+
25
+
26
+ # ── Binary ─────────────────────────────────────────────────────────────────
27
+
28
+ def to_binary(num: int, prefix: bool = False) -> str:
29
+ """
30
+ Convert an integer to a binary string.
31
+
32
+ Args:
33
+ num: Non-negative integer.
34
+ prefix: If True, prepend '0b'. Default False.
35
+
36
+ Returns:
37
+ Binary string, e.g. '101010'.
38
+
39
+ Examples:
40
+ >>> to_binary(0)
41
+ '0'
42
+ >>> to_binary(42)
43
+ '101010'
44
+ >>> to_binary(42, prefix=True)
45
+ '0b101010'
46
+ >>> to_binary(255)
47
+ '11111111'
48
+ """
49
+ if not isinstance(num, int):
50
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
51
+ if num < 0:
52
+ raise ValueError("Negative numbers are not supported")
53
+ result = bin(num)[2:]
54
+ return ("0b" + result) if prefix else result
55
+
56
+
57
+ def from_binary(s: str) -> int:
58
+ """
59
+ Convert a binary string to an integer.
60
+
61
+ Args:
62
+ s: Binary string, with or without '0b' prefix.
63
+
64
+ Returns:
65
+ Integer value.
66
+
67
+ Examples:
68
+ >>> from_binary('101010')
69
+ 42
70
+ >>> from_binary('0b101010')
71
+ 42
72
+ """
73
+ if not isinstance(s, str):
74
+ raise TypeError(f"Expected str, got {type(s).__name__!r}")
75
+ s = s.strip().lower().removeprefix("0b")
76
+ if not s:
77
+ raise ValueError("Empty string")
78
+ try:
79
+ return int(s, 2)
80
+ except ValueError:
81
+ raise ValueError(f"Invalid binary string: {s!r}")
82
+
83
+
84
+ # ── Octal ──────────────────────────────────────────────────────────────────
85
+
86
+ def to_octal(num: int, prefix: bool = False) -> str:
87
+ """
88
+ Convert an integer to an octal string.
89
+
90
+ Args:
91
+ num: Non-negative integer.
92
+ prefix: If True, prepend '0o'. Default False.
93
+
94
+ Returns:
95
+ Octal string, e.g. '52'.
96
+
97
+ Examples:
98
+ >>> to_octal(0)
99
+ '0'
100
+ >>> to_octal(42)
101
+ '52'
102
+ >>> to_octal(42, prefix=True)
103
+ '0o52'
104
+ >>> to_octal(255)
105
+ '377'
106
+ """
107
+ if not isinstance(num, int):
108
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
109
+ if num < 0:
110
+ raise ValueError("Negative numbers are not supported")
111
+ result = oct(num)[2:]
112
+ return ("0o" + result) if prefix else result
113
+
114
+
115
+ def from_octal(s: str) -> int:
116
+ """
117
+ Convert an octal string to an integer.
118
+
119
+ Args:
120
+ s: Octal string, with or without '0o' prefix.
121
+
122
+ Returns:
123
+ Integer value.
124
+
125
+ Examples:
126
+ >>> from_octal('52')
127
+ 42
128
+ >>> from_octal('0o52')
129
+ 42
130
+ """
131
+ if not isinstance(s, str):
132
+ raise TypeError(f"Expected str, got {type(s).__name__!r}")
133
+ s = s.strip().lower().removeprefix("0o")
134
+ if not s:
135
+ raise ValueError("Empty string")
136
+ try:
137
+ return int(s, 8)
138
+ except ValueError:
139
+ raise ValueError(f"Invalid octal string: {s!r}")
140
+
141
+
142
+ # ── Hexadecimal ────────────────────────────────────────────────────────────
143
+
144
+ def to_hex(num: int, prefix: bool = False, upper: bool = True) -> str:
145
+ """
146
+ Convert an integer to a hexadecimal string.
147
+
148
+ Args:
149
+ num: Non-negative integer.
150
+ prefix: If True, prepend '0x'. Default False.
151
+ upper: If True (default), use uppercase A–F.
152
+
153
+ Returns:
154
+ Hexadecimal string, e.g. '2A'.
155
+
156
+ Examples:
157
+ >>> to_hex(0)
158
+ '0'
159
+ >>> to_hex(42)
160
+ '2A'
161
+ >>> to_hex(255)
162
+ 'FF'
163
+ >>> to_hex(255, prefix=True)
164
+ '0xFF'
165
+ >>> to_hex(255, upper=False)
166
+ 'ff'
167
+ """
168
+ if not isinstance(num, int):
169
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
170
+ if num < 0:
171
+ raise ValueError("Negative numbers are not supported")
172
+ result = hex(num)[2:]
173
+ result = result.upper() if upper else result.lower()
174
+ return ("0x" + result) if prefix else result
175
+
176
+
177
+ def from_hex(s: str) -> int:
178
+ """
179
+ Convert a hexadecimal string to an integer.
180
+
181
+ Args:
182
+ s: Hex string, with or without '0x' prefix (case-insensitive).
183
+
184
+ Returns:
185
+ Integer value.
186
+
187
+ Examples:
188
+ >>> from_hex('2A')
189
+ 42
190
+ >>> from_hex('0xff')
191
+ 255
192
+ >>> from_hex('FF')
193
+ 255
194
+ """
195
+ if not isinstance(s, str):
196
+ raise TypeError(f"Expected str, got {type(s).__name__!r}")
197
+ s = s.strip().lower().removeprefix("0x")
198
+ if not s:
199
+ raise ValueError("Empty string")
200
+ try:
201
+ return int(s, 16)
202
+ except ValueError:
203
+ raise ValueError(f"Invalid hexadecimal string: {s!r}")
204
+
205
+
206
+ # ── Generic base converter ─────────────────────────────────────────────────
207
+
208
+ def to_base(num: int, base: int) -> str:
209
+ """
210
+ Convert an integer to any base between 2 and 36.
211
+
212
+ Args:
213
+ num: Non-negative integer.
214
+ base: Target base (2–36).
215
+
216
+ Returns:
217
+ String representation in the given base.
218
+
219
+ Examples:
220
+ >>> to_base(42, 2)
221
+ '101010'
222
+ >>> to_base(42, 16)
223
+ '2A'
224
+ >>> to_base(255, 36)
225
+ '73'
226
+ """
227
+ if not isinstance(num, int):
228
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
229
+ if num < 0:
230
+ raise ValueError("Negative numbers are not supported")
231
+ if not 2 <= base <= 36:
232
+ raise ValueError(f"Base must be between 2 and 36, got {base}")
233
+ if num == 0:
234
+ return "0"
235
+
236
+ digits = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ"
237
+ result = ""
238
+ while num:
239
+ result = digits[num % base] + result
240
+ num //= base
241
+ return result
242
+
243
+
244
+ def from_base(s: str, base: int) -> int:
245
+ """
246
+ Convert a string in any base (2–36) to an integer.
247
+
248
+ Args:
249
+ s: String representation.
250
+ base: Source base (2–36).
251
+
252
+ Returns:
253
+ Integer value.
254
+
255
+ Examples:
256
+ >>> from_base('101010', 2)
257
+ 42
258
+ >>> from_base('2A', 16)
259
+ 42
260
+ """
261
+ if not isinstance(s, str):
262
+ raise TypeError(f"Expected str, got {type(s).__name__!r}")
263
+ try:
264
+ return int(s.strip(), base)
265
+ except ValueError:
266
+ raise ValueError(f"Invalid base-{base} string: {s!r}")
267
+
268
+
269
+ # ── quick demo ─────────────────────────────────────────────────────────────
270
+ if __name__ == "__main__":
271
+ tests = [0, 1, 10, 42, 255, 1024, 65535]
272
+
273
+ print(f"{'Decimal':<10} {'Binary':<20} {'Octal':<10} {'Hex'}")
274
+ print("─" * 55)
275
+ for n in tests:
276
+ print(f"{n:<10} {to_binary(n):<20} {to_octal(n):<10} {to_hex(n)}")
277
+
278
+ print()
279
+ print("Round-trip checks:")
280
+ for n in tests:
281
+ assert from_binary(to_binary(n)) == n
282
+ assert from_octal(to_octal(n)) == n
283
+ assert from_hex(to_hex(n)) == n
284
+ print(" All passed ✓")
@@ -0,0 +1,202 @@
1
+ """
2
+ numly.mayan
3
+ ~~~~~~~~~~~
4
+ Convert integers to Mayan numerals and back.
5
+
6
+ The Mayan vigesimal (base-20) system uses three symbols:
7
+ • (dot) = 1
8
+ — (bar) = 5
9
+ 𝋠 (shell) = 0 (one of the earliest uses of zero in history)
10
+
11
+ Within each "digit" (0–19):
12
+ dots stack vertically above bars
13
+ maximum: 3 bars (15) + 4 dots (4) = 19
14
+
15
+ Digits are written top-to-bottom (highest power first).
16
+ In this text representation, digits are separated by | .
17
+
18
+ Unicode: The Mayan Numerals block (U+1D2C0–U+1D2FF) has combined
19
+ glyphs 𝋠 (0) through 𝋳 (19). numly uses these for compact output.
20
+
21
+ Range: 0 – 7,999 (20³ − 1; three vigesimal digits)
22
+
23
+ Examples
24
+ --------
25
+ >>> from numly.mayan import to_mayan, from_mayan
26
+ >>> to_mayan(0)
27
+ '𝋠'
28
+ >>> to_mayan(19)
29
+ '𝋳'
30
+ >>> to_mayan(20)
31
+ '𝋡 𝋠'
32
+ >>> to_mayan(42)
33
+ '𝋢 𝋡'
34
+ """
35
+
36
+ # ── Mayan Numerals Unicode block U+1D2C0–U+1D2E3 ──────────────────────────
37
+ # Each code point represents one complete Mayan digit 0–19.
38
+
39
+ _GLYPHS = [chr(0x1D2C0 + i) for i in range(20)] # 𝋠 𝋡 𝋢 … 𝋳
40
+ _GLYPH_VAL = {g: i for i, g in enumerate(_GLYPHS)}
41
+
42
+ _SEP = " " # digit separator in multi-digit numbers
43
+
44
+ SYSTEM = "mayan"
45
+ BASE = 20
46
+ MIN, MAX = 0, BASE ** 3 - 1 # 0 – 7,999
47
+
48
+
49
+ def to_mayan(num: int) -> str:
50
+ """
51
+ Convert an integer to a Mayan numeral string.
52
+
53
+ Uses the Unicode Mayan Numerals block (U+1D2C0–U+1D2E3).
54
+ Multi-digit numbers separate vigesimal digits with a space.
55
+
56
+ Args:
57
+ num: Non-negative integer between 0 and 7,999.
58
+
59
+ Returns:
60
+ Mayan numeral string, e.g. '𝋢 𝋡' for 42.
61
+
62
+ Raises:
63
+ TypeError: If num is not an int.
64
+ ValueError: If num is outside 0–7,999.
65
+
66
+ Examples:
67
+ >>> to_mayan(0)
68
+ '𝋠'
69
+ >>> to_mayan(1)
70
+ '𝋡'
71
+ >>> to_mayan(5)
72
+ '𝋥'
73
+ >>> to_mayan(19)
74
+ '𝋳'
75
+ >>> to_mayan(20)
76
+ '𝋡 𝋠'
77
+ >>> to_mayan(42)
78
+ '𝋢 𝋡'
79
+ >>> to_mayan(400)
80
+ '𝋡 𝋠 𝋠'
81
+ """
82
+ if not isinstance(num, int):
83
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
84
+ if not MIN <= num <= MAX:
85
+ raise ValueError(f"Mayan numerals support {MIN}–{MAX}, got {num}")
86
+
87
+ if num == 0:
88
+ return _GLYPHS[0]
89
+
90
+ digits = []
91
+ n = num
92
+ while n:
93
+ digits.append(_GLYPHS[n % BASE])
94
+ n //= BASE
95
+
96
+ return _SEP.join(reversed(digits))
97
+
98
+
99
+ def from_mayan(s: str) -> int:
100
+ """
101
+ Convert a Mayan numeral string to an integer.
102
+
103
+ Args:
104
+ s: Mayan numeral string with digits separated by spaces,
105
+ e.g. '𝋢 𝋡'.
106
+
107
+ Returns:
108
+ Integer value.
109
+
110
+ Raises:
111
+ TypeError: If s is not a str.
112
+ ValueError: If s contains unknown characters.
113
+
114
+ Examples:
115
+ >>> from_mayan('𝋠')
116
+ 0
117
+ >>> from_mayan('𝋳')
118
+ 19
119
+ >>> from_mayan('𝋡 𝋠')
120
+ 20
121
+ >>> from_mayan('𝋢 𝋡')
122
+ 42
123
+ """
124
+ if not isinstance(s, str):
125
+ raise TypeError(f"Expected str, got {type(s).__name__!r}")
126
+ s = s.strip()
127
+ if not s:
128
+ raise ValueError("Empty string")
129
+
130
+ parts = s.split(_SEP)
131
+ result = 0
132
+ for part in parts:
133
+ if part not in _GLYPH_VAL:
134
+ raise ValueError(f"Unknown Mayan glyph: {part!r}")
135
+ result = result * BASE + _GLYPH_VAL[part]
136
+ return result
137
+
138
+
139
+ def to_mayan_text(num: int) -> str:
140
+ """
141
+ Convert an integer to a human-readable Mayan text representation.
142
+
143
+ Uses dots (•), bars (━), and shell (◎) for maximum compatibility.
144
+
145
+ Args:
146
+ num: Non-negative integer between 0 and 7,999.
147
+
148
+ Returns:
149
+ Text representation string, e.g. '••━ | •' for 42.
150
+
151
+ Examples:
152
+ >>> to_mayan_text(0)
153
+ '◎'
154
+ >>> to_mayan_text(7)
155
+ '••━'
156
+ >>> to_mayan_text(19)
157
+ '••••━━━'
158
+ >>> to_mayan_text(42)
159
+ '••━ | •'
160
+ """
161
+ if not isinstance(num, int):
162
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
163
+ if not MIN <= num <= MAX:
164
+ raise ValueError(f"Mayan numerals support {MIN}–{MAX}, got {num}")
165
+
166
+ def _digit_text(d: int) -> str:
167
+ if d == 0:
168
+ return "◎"
169
+ bars = d // 5
170
+ dots = d % 5
171
+ return "•" * dots + "━" * bars
172
+
173
+ if num == 0:
174
+ return "◎"
175
+
176
+ digits = []
177
+ n = num
178
+ while n:
179
+ digits.append(_digit_text(n % BASE))
180
+ n //= BASE
181
+
182
+ return " | ".join(reversed(digits))
183
+
184
+
185
+ def is_valid(s: str) -> bool:
186
+ """Check whether a string is a valid Mayan numeral."""
187
+ try:
188
+ val = from_mayan(s)
189
+ return to_mayan(val) == s
190
+ except (TypeError, ValueError):
191
+ return False
192
+
193
+
194
+ # ── quick demo ─────────────────────────────────────────────────────────────
195
+ if __name__ == "__main__":
196
+ tests = [0, 1, 5, 7, 19, 20, 42, 100, 400, 7999]
197
+ print(f"{'n':<8} {'Unicode':<20} {'Text (dots/bars)'}")
198
+ print("─" * 50)
199
+ for n in tests:
200
+ u = to_mayan(n)
201
+ t = to_mayan_text(n)
202
+ print(f"{n:<8} {u:<20} {t}")
@@ -0,0 +1,189 @@
1
+ """
2
+ numly.tamil
3
+ ~~~~~~~~~~~
4
+ Convert integers to Ancient Tamil numerals and back.
5
+
6
+ Tamil numerals use a positional-additive system with dedicated symbols
7
+ for 1–9, 10, 100, and 1000.
8
+
9
+ Unicode block: Tamil (U+0BE6–U+0BF2)
10
+
11
+ ௦ = 0 ௧ = 1 ௨ = 2 ௩ = 3 ௪ = 4
12
+ ௫ = 5 ௬ = 6 ௭ = 7 ௮ = 8 ௯ = 9
13
+ ௰ = 10 ௱ = 100 ௲ = 1000
14
+
15
+ Range: 0 – 9,999
16
+
17
+ Examples
18
+ --------
19
+ >>> from numly.tamil import to_tamil, from_tamil
20
+ >>> to_tamil(42)
21
+ '௪௰௨'
22
+ >>> from_tamil('௪௰௨')
23
+ 42
24
+ """
25
+
26
+ # ── symbol tables ──────────────────────────────────────────────────────────
27
+
28
+ _DIGITS = "௦௧௨௩௪௫௬௭௮௯" # 0–9 (U+0BE6 – U+0BEF)
29
+ _TEN = "௰" # 10 (U+0BF0)
30
+ _HUNDRED = "௱" # 100 (U+0BF1)
31
+ _THOUSAND = "௲" # 1000 (U+0BF2)
32
+
33
+ _CHAR_VALUE = {ch: i for i, ch in enumerate(_DIGITS)}
34
+ _CHAR_VALUE[_TEN] = 10
35
+ _CHAR_VALUE[_HUNDRED] = 100
36
+ _CHAR_VALUE[_THOUSAND] = 1000
37
+
38
+ SYSTEM = "tamil"
39
+ MIN, MAX = 0, 9_999
40
+
41
+
42
+ # ── encode ─────────────────────────────────────────────────────────────────
43
+
44
+ def to_tamil(num: int) -> str:
45
+ """
46
+ Convert an integer to an Ancient Tamil numeral string.
47
+
48
+ Tamil numerals use multiplier notation:
49
+ 42 → ௪ (4) × ௰ (10) + ௨ (2) → '௪௰௨'
50
+ 300 → ௩ (3) × ௱ (100) → '௩௱'
51
+ 1500→ ௧ (1) × ௲ (1000) + ௫ × ௱ → '௧௲௫௱'
52
+
53
+ Args:
54
+ num: Integer between 0 and 9,999.
55
+
56
+ Returns:
57
+ Tamil numeral string, e.g. '௪௰௨'.
58
+
59
+ Raises:
60
+ TypeError: If num is not an int.
61
+ ValueError: If num is outside 0–9999.
62
+
63
+ Examples:
64
+ >>> to_tamil(0)
65
+ '௦'
66
+ >>> to_tamil(7)
67
+ '௭'
68
+ >>> to_tamil(10)
69
+ '௰'
70
+ >>> to_tamil(42)
71
+ '௪௰௨'
72
+ >>> to_tamil(100)
73
+ '௱'
74
+ >>> to_tamil(999)
75
+ '௯௱௯௰௯'
76
+ >>> to_tamil(1234)
77
+ '௧௲௨௱௩௰௪'
78
+ """
79
+ if not isinstance(num, int):
80
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
81
+ if num == 0:
82
+ return _DIGITS[0]
83
+ if not 1 <= num <= MAX:
84
+ raise ValueError(f"Tamil numerals support 0–{MAX}, got {num}")
85
+
86
+ result = ""
87
+
88
+ # thousands
89
+ t = num // 1000
90
+ if t:
91
+ result += (_DIGITS[t] if t > 1 else "") + _THOUSAND
92
+ num %= 1000
93
+
94
+ # hundreds
95
+ h = num // 100
96
+ if h:
97
+ result += (_DIGITS[h] if h > 1 else "") + _HUNDRED
98
+ num %= 100
99
+
100
+ # tens
101
+ d = num // 10
102
+ if d:
103
+ result += (_DIGITS[d] if d > 1 else "") + _TEN
104
+ num %= 10
105
+
106
+ # ones
107
+ if num:
108
+ result += _DIGITS[num]
109
+
110
+ return result
111
+
112
+
113
+ # ── decode ─────────────────────────────────────────────────────────────────
114
+
115
+ def from_tamil(s: str) -> int:
116
+ """
117
+ Convert an Ancient Tamil numeral string to an integer.
118
+
119
+ Args:
120
+ s: Tamil numeral string, e.g. '௪௰௨'.
121
+
122
+ Returns:
123
+ Integer value.
124
+
125
+ Raises:
126
+ TypeError: If s is not a str.
127
+ ValueError: If s contains unknown characters or is empty.
128
+
129
+ Examples:
130
+ >>> from_tamil('௦')
131
+ 0
132
+ >>> from_tamil('௪௰௨')
133
+ 42
134
+ >>> from_tamil('௧௲௨௱௩௰௪')
135
+ 1234
136
+ """
137
+ if not isinstance(s, str):
138
+ raise TypeError(f"Expected str, got {type(s).__name__!r}")
139
+ s = s.strip()
140
+ if not s:
141
+ raise ValueError("Empty string")
142
+ if s == _DIGITS[0]:
143
+ return 0
144
+
145
+ for ch in s:
146
+ if ch not in _CHAR_VALUE:
147
+ raise ValueError(f"Unknown Tamil numeral character: {ch!r}")
148
+
149
+ result = 0
150
+ pending = 0 # digit waiting to be multiplied by a unit
151
+
152
+ for ch in s:
153
+ v = _CHAR_VALUE[ch]
154
+ if v in (10, 100, 1000):
155
+ # multiplier — pending digit × unit (1 if no pending digit)
156
+ result += (pending if pending else 1) * v
157
+ pending = 0
158
+ else:
159
+ # plain digit
160
+ pending = v
161
+
162
+ result += pending # any trailing ones digit
163
+ return result
164
+
165
+
166
+ def is_valid(s: str) -> bool:
167
+ """
168
+ Check whether a string is a valid Ancient Tamil numeral.
169
+
170
+ Examples:
171
+ >>> is_valid('௪௰௨')
172
+ True
173
+ >>> is_valid('hello')
174
+ False
175
+ """
176
+ try:
177
+ val = from_tamil(s)
178
+ return to_tamil(val) == s
179
+ except (TypeError, ValueError):
180
+ return False
181
+
182
+
183
+ # ── quick demo ─────────────────────────────────────────────────────────────
184
+ if __name__ == "__main__":
185
+ tests = [0, 1, 7, 10, 11, 42, 100, 110, 999, 1000, 1234, 9999]
186
+ for n in tests:
187
+ r = to_tamil(n)
188
+ back = from_tamil(r)
189
+ print(f"{n:<8} → {r:<12} → {back}")
@@ -0,0 +1,207 @@
1
+ """
2
+ numly.words
3
+ ~~~~~~~~~~~
4
+ Convert integers to English words in both Western and Indian systems.
5
+
6
+ Western system : thousand, million, billion, trillion …
7
+ Indian system : thousand, lakh, crore …
8
+
9
+ Range
10
+ -----
11
+ Western : 0 – 999,999,999,999,999 (up to 999 trillion)
12
+ Indian : 0 – 99,99,99,99,99,999 (up to 99 lakh crore)
13
+
14
+ Examples
15
+ --------
16
+ >>> from numly.words import to_words_western, to_words_indian
17
+ >>> to_words_western(1234567)
18
+ 'one million two hundred thirty four thousand five hundred sixty seven'
19
+ >>> to_words_indian(1234567)
20
+ 'twelve lakh thirty four thousand five hundred sixty seven'
21
+ """
22
+
23
+ # ── lookup tables ──────────────────────────────────────────────────────────
24
+
25
+ _ONES = [
26
+ "", "one", "two", "three", "four", "five", "six", "seven", "eight", "nine",
27
+ "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen",
28
+ "seventeen", "eighteen", "nineteen",
29
+ ]
30
+
31
+ _TENS = [
32
+ "", "", "twenty", "thirty", "forty", "fifty",
33
+ "sixty", "seventy", "eighty", "ninety",
34
+ ]
35
+
36
+ SYSTEM = "words"
37
+
38
+
39
+ # ── internal helpers ───────────────────────────────────────────────────────
40
+
41
+ def _below_1000(n: int) -> str:
42
+ """Convert an integer 0–999 to words (no leading/trailing spaces)."""
43
+ if n == 0:
44
+ return ""
45
+ if n < 20:
46
+ return _ONES[n]
47
+ if n < 100:
48
+ rest = _ONES[n % 10]
49
+ return _TENS[n // 10] + (" " + rest if rest else "")
50
+ rest = _below_1000(n % 100)
51
+ return _ONES[n // 100] + " hundred" + (" " + rest if rest else "")
52
+
53
+
54
+ # ── Western system ─────────────────────────────────────────────────────────
55
+
56
+ _WESTERN_SCALE = [
57
+ (1_000_000_000_000, "trillion"),
58
+ (1_000_000_000, "billion"),
59
+ (1_000_000, "million"),
60
+ (1_000, "thousand"),
61
+ ]
62
+
63
+
64
+ def to_words_western(num: int) -> str:
65
+ """
66
+ Convert an integer to English words using the Western scale.
67
+
68
+ Uses: thousand → million → billion → trillion
69
+
70
+ Args:
71
+ num: Integer between 0 and 999,999,999,999,999.
72
+
73
+ Returns:
74
+ English words string, e.g. 'one million two hundred thirty four thousand'.
75
+
76
+ Raises:
77
+ TypeError: If num is not an int.
78
+ ValueError: If num is negative or out of range.
79
+
80
+ Examples:
81
+ >>> to_words_western(0)
82
+ 'zero'
83
+ >>> to_words_western(42)
84
+ 'forty two'
85
+ >>> to_words_western(1001)
86
+ 'one thousand one'
87
+ >>> to_words_western(1_000_000)
88
+ 'one million'
89
+ >>> to_words_western(1_234_567)
90
+ 'one million two hundred thirty four thousand five hundred sixty seven'
91
+ >>> to_words_western(1_000_000_000)
92
+ 'one billion'
93
+ """
94
+ if not isinstance(num, int):
95
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
96
+ if num < 0:
97
+ raise ValueError("Negative numbers are not supported")
98
+ if num > 999_999_999_999_999:
99
+ raise ValueError("Number too large; maximum is 999,999,999,999,999")
100
+ if num == 0:
101
+ return "zero"
102
+
103
+ parts = []
104
+ for scale, name in _WESTERN_SCALE:
105
+ if num >= scale:
106
+ parts.append(_below_1000(num // scale) + " " + name)
107
+ num %= scale
108
+ if num > 0:
109
+ parts.append(_below_1000(num))
110
+
111
+ return " ".join(parts)
112
+
113
+
114
+ # ── Indian system ──────────────────────────────────────────────────────────
115
+
116
+ _INDIAN_SCALE = [
117
+ (10_00_00_000, "crore"),
118
+ (1_00_000, "lakh"),
119
+ (1_000, "thousand"),
120
+ ]
121
+
122
+
123
+ def to_words_indian(num: int) -> str:
124
+ """
125
+ Convert an integer to English words using the Indian numbering scale.
126
+
127
+ Uses: thousand → lakh → crore
128
+
129
+ Args:
130
+ num: Integer between 0 and 9,99,99,99,99,99,999.
131
+
132
+ Returns:
133
+ English words string using Indian scale, e.g. 'twelve lakh thirty four thousand'.
134
+
135
+ Raises:
136
+ TypeError: If num is not an int.
137
+ ValueError: If num is negative or out of range.
138
+
139
+ Examples:
140
+ >>> to_words_indian(0)
141
+ 'zero'
142
+ >>> to_words_indian(42)
143
+ 'forty two'
144
+ >>> to_words_indian(100_000)
145
+ 'one lakh'
146
+ >>> to_words_indian(1_234_567)
147
+ 'twelve lakh thirty four thousand five hundred sixty seven'
148
+ >>> to_words_indian(10_000_000)
149
+ 'one crore'
150
+ >>> to_words_indian(10_000_000_000)
151
+ 'one thousand crore'
152
+ """
153
+ if not isinstance(num, int):
154
+ raise TypeError(f"Expected int, got {type(num).__name__!r}")
155
+ if num < 0:
156
+ raise ValueError("Negative numbers are not supported")
157
+ if num == 0:
158
+ return "zero"
159
+
160
+ parts = []
161
+ for scale, name in _INDIAN_SCALE:
162
+ if num >= scale:
163
+ chunk = num // scale
164
+ parts.append(_below_1000(chunk) + " " + name)
165
+ num %= scale
166
+ if num > 0:
167
+ parts.append(_below_1000(num))
168
+
169
+ return " ".join(parts)
170
+
171
+
172
+ def to_words(num: int, system: str = "western") -> str:
173
+ """
174
+ Convert an integer to English words.
175
+
176
+ Args:
177
+ num: Integer to convert.
178
+ system: 'western' (default) or 'indian'.
179
+
180
+ Returns:
181
+ English words string.
182
+
183
+ Examples:
184
+ >>> to_words(1_234_567, "western")
185
+ 'one million two hundred thirty four thousand five hundred sixty seven'
186
+ >>> to_words(1_234_567, "indian")
187
+ 'twelve lakh thirty four thousand five hundred sixty seven'
188
+ """
189
+ system = system.lower().strip()
190
+ if system == "western":
191
+ return to_words_western(num)
192
+ if system == "indian":
193
+ return to_words_indian(num)
194
+ raise ValueError(f"Unknown system {system!r}. Choose 'western' or 'indian'.")
195
+
196
+
197
+ # ── quick demo ─────────────────────────────────────────────────────────────
198
+ if __name__ == "__main__":
199
+ tests = [0, 1, 13, 42, 100, 999, 1_000, 10_000, 1_00_000,
200
+ 1_234_567, 10_000_000, 1_000_000_000, 1_234_567_890]
201
+
202
+ print(f"{'Number':<20} {'Western':<55} {'Indian'}")
203
+ print("─" * 120)
204
+ for n in tests:
205
+ w = to_words_western(n)
206
+ i = to_words_indian(n)
207
+ print(f"{n:<20} {w:<55} {i}")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: numly
3
- Version: 0.2.0
3
+ Version: 0.2.1
4
4
  Summary: Convert numbers across numeral systems — Roman, Chinese, Greek, Egyptian, Arabic-Indic and more.
5
5
  Home-page: https://github.com/TheMadrasTechie/numly
6
6
  Author: TheMadrasTechie
@@ -3,11 +3,16 @@ setup.cfg
3
3
  setup.py
4
4
  numly/__init__.py
5
5
  numly/arabic_indic.py
6
+ numly/babylonian.py
7
+ numly/base.py
6
8
  numly/chinese.py
7
9
  numly/convert.py
8
10
  numly/egyptian.py
9
11
  numly/greek.py
12
+ numly/mayan.py
10
13
  numly/roman.py
14
+ numly/tamil.py
15
+ numly/words.py
11
16
  numly.egg-info/PKG-INFO
12
17
  numly.egg-info/SOURCES.txt
13
18
  numly.egg-info/dependency_links.txt
@@ -5,7 +5,7 @@ with open("README.md", "r", encoding="utf-8") as f:
5
5
 
6
6
  setup(
7
7
  name="numly",
8
- version="0.2.0",
8
+ version="0.2.1",
9
9
  packages=find_packages(),
10
10
  install_requires=[],
11
11
  author="TheMadrasTechie",
@@ -1,64 +0,0 @@
1
- """
2
- numly
3
- ~~~~~
4
- A Python library for working with numbers across numeral systems.
5
-
6
- Supported systems
7
- -----------------
8
- decimal : Standard base-10
9
- roman : Roman numerals (I V X L C D M)
10
- arabic_indic : Eastern Arabic digits (٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩)
11
- chinese : Traditional Chinese (零 一 二 三 四 … 万)
12
- greek : Greek alphabetic (Α Β Γ … Ω + ͵ for thousands)
13
- egyptian : Egyptian hieroglyphs (𓏺 𓎆 𓍢 𓆼 𓂭 𓆐 𓁨)
14
-
15
- Quick start
16
- -----------
17
- import numly
18
-
19
- numly.to_roman(2024) # 'MMXXIV'
20
- numly.to_chinese(42) # '四十二'
21
- numly.convert("MMXXIV", "roman", "greek") # '͵ΒΚΔʹ'
22
- numly.to_all(42) # dict of every system
23
-
24
- Individual modules
25
- ------------------
26
- from numly.roman import to_roman, from_roman
27
- from numly.arabic_indic import to_arabic_indic, from_arabic_indic
28
- from numly.chinese import to_chinese, from_chinese
29
- from numly.greek import to_greek, from_greek
30
- from numly.egyptian import to_egyptian, from_egyptian, symbol_breakdown
31
- from numly.convert import convert, to_all, SYSTEMS
32
- """
33
-
34
- __version__ = "0.1.1"
35
- __author__ = "numly contributors"
36
- __license__ = "MIT"
37
-
38
- # ── individual converters ──────────────────────────────────────────────────
39
- from numly.roman import to_roman, from_roman, is_valid as is_valid_roman
40
- from numly.arabic_indic import to_arabic_indic, from_arabic_indic, is_valid as is_valid_arabic_indic
41
- from numly.chinese import to_chinese, from_chinese, is_valid as is_valid_chinese
42
- from numly.greek import to_greek, from_greek, is_valid as is_valid_greek
43
- from numly.egyptian import to_egyptian, from_egyptian, is_valid as is_valid_egyptian
44
- from numly.egyptian import symbol_breakdown
45
-
46
- # ── universal converter ────────────────────────────────────────────────────
47
- from numly.convert import convert, to_all, SYSTEMS, supported_systems
48
-
49
- __all__ = [
50
- # roman
51
- "to_roman", "from_roman", "is_valid_roman",
52
- # arabic-indic
53
- "to_arabic_indic", "from_arabic_indic", "is_valid_arabic_indic",
54
- # chinese
55
- "to_chinese", "from_chinese", "is_valid_chinese",
56
- # greek
57
- "to_greek", "from_greek", "is_valid_greek",
58
- # egyptian
59
- "to_egyptian", "from_egyptian", "is_valid_egyptian", "symbol_breakdown",
60
- # universal
61
- "convert", "to_all", "SYSTEMS", "supported_systems",
62
- # meta
63
- "__version__",
64
- ]
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes