absfuyu 5.6.1__py3-none-any.whl → 6.1.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of absfuyu might be problematic. Click here for more details.
- absfuyu/__init__.py +5 -3
- absfuyu/__main__.py +2 -2
- absfuyu/cli/__init__.py +13 -2
- absfuyu/cli/audio_group.py +98 -0
- absfuyu/cli/color.py +2 -2
- absfuyu/cli/config_group.py +2 -2
- absfuyu/cli/do_group.py +2 -2
- absfuyu/cli/game_group.py +20 -2
- absfuyu/cli/tool_group.py +68 -4
- absfuyu/config/__init__.py +3 -3
- absfuyu/core/__init__.py +10 -6
- absfuyu/core/baseclass.py +104 -34
- absfuyu/core/baseclass2.py +43 -2
- absfuyu/core/decorator.py +2 -2
- absfuyu/core/docstring.py +4 -2
- absfuyu/core/dummy_cli.py +3 -3
- absfuyu/core/dummy_func.py +2 -2
- absfuyu/dxt/__init__.py +2 -2
- absfuyu/dxt/base_type.py +93 -0
- absfuyu/dxt/dictext.py +188 -6
- absfuyu/dxt/dxt_support.py +2 -2
- absfuyu/dxt/intext.py +72 -4
- absfuyu/dxt/listext.py +495 -23
- absfuyu/dxt/strext.py +2 -2
- absfuyu/extra/__init__.py +2 -2
- absfuyu/extra/audio/__init__.py +8 -0
- absfuyu/extra/audio/_util.py +57 -0
- absfuyu/extra/audio/convert.py +192 -0
- absfuyu/extra/audio/lossless.py +281 -0
- absfuyu/extra/beautiful.py +2 -2
- absfuyu/extra/da/__init__.py +39 -3
- absfuyu/extra/da/dadf.py +436 -29
- absfuyu/extra/da/dadf_base.py +2 -2
- absfuyu/extra/da/df_func.py +89 -5
- absfuyu/extra/da/mplt.py +2 -2
- absfuyu/extra/ggapi/__init__.py +8 -0
- absfuyu/extra/ggapi/gdrive.py +223 -0
- absfuyu/extra/ggapi/glicense.py +148 -0
- absfuyu/extra/ggapi/glicense_df.py +186 -0
- absfuyu/extra/ggapi/gsheet.py +88 -0
- absfuyu/extra/img/__init__.py +30 -0
- absfuyu/extra/img/converter.py +402 -0
- absfuyu/extra/img/dup_check.py +291 -0
- absfuyu/extra/pdf.py +4 -6
- absfuyu/extra/rclone.py +253 -0
- absfuyu/extra/xml.py +90 -0
- absfuyu/fun/__init__.py +2 -20
- absfuyu/fun/rubik.py +2 -2
- absfuyu/fun/tarot.py +2 -2
- absfuyu/game/__init__.py +2 -2
- absfuyu/game/game_stat.py +2 -2
- absfuyu/game/schulte.py +78 -0
- absfuyu/game/sudoku.py +2 -2
- absfuyu/game/tictactoe.py +2 -2
- absfuyu/game/wordle.py +6 -4
- absfuyu/general/__init__.py +2 -2
- absfuyu/general/content.py +2 -2
- absfuyu/general/human.py +2 -2
- absfuyu/general/resrel.py +213 -0
- absfuyu/general/shape.py +3 -8
- absfuyu/general/tax.py +344 -0
- absfuyu/logger.py +806 -59
- absfuyu/numbers/__init__.py +13 -0
- absfuyu/numbers/number_to_word.py +321 -0
- absfuyu/numbers/shorten_number.py +303 -0
- absfuyu/numbers/time_duration.py +217 -0
- absfuyu/pkg_data/__init__.py +2 -2
- absfuyu/pkg_data/deprecated.py +2 -2
- absfuyu/pkg_data/logo.py +1462 -0
- absfuyu/sort.py +4 -4
- absfuyu/tools/__init__.py +2 -2
- absfuyu/tools/checksum.py +119 -4
- absfuyu/tools/converter.py +2 -2
- absfuyu/tools/generator.py +24 -7
- absfuyu/tools/inspector.py +2 -2
- absfuyu/tools/keygen.py +2 -2
- absfuyu/tools/obfuscator.py +2 -2
- absfuyu/tools/passwordlib.py +2 -2
- absfuyu/tools/shutdownizer.py +3 -8
- absfuyu/tools/sw.py +213 -10
- absfuyu/tools/web.py +10 -13
- absfuyu/typings.py +5 -8
- absfuyu/util/__init__.py +31 -2
- absfuyu/util/api.py +7 -4
- absfuyu/util/cli.py +119 -0
- absfuyu/util/gui.py +91 -0
- absfuyu/util/json_method.py +2 -2
- absfuyu/util/lunar.py +2 -2
- absfuyu/util/package.py +124 -0
- absfuyu/util/path.py +313 -4
- absfuyu/util/performance.py +2 -2
- absfuyu/util/shorten_number.py +206 -13
- absfuyu/util/text_table.py +2 -2
- absfuyu/util/zipped.py +2 -2
- absfuyu/version.py +22 -19
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/METADATA +37 -8
- absfuyu-6.1.2.dist-info/RECORD +105 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/WHEEL +1 -1
- absfuyu/extra/data_analysis.py +0 -21
- absfuyu-5.6.1.dist-info/RECORD +0 -79
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/entry_points.txt +0 -0
- {absfuyu-5.6.1.dist-info → absfuyu-6.1.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Numbers
|
|
3
|
+
----------------
|
|
4
|
+
Numbers related
|
|
5
|
+
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
from absfuyu.numbers.number_to_word import NumberToWords
|
|
12
|
+
from absfuyu.numbers.shorten_number import Decimal
|
|
13
|
+
from absfuyu.numbers.time_duration import Duration
|
|
@@ -0,0 +1,321 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Number to word
|
|
3
|
+
-----------------------
|
|
4
|
+
Convert number to word
|
|
5
|
+
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = ["NumberToWords"]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
# Library
|
|
16
|
+
# ---------------------------------------------------------------------------
|
|
17
|
+
from abc import ABC, abstractmethod
|
|
18
|
+
from decimal import Decimal
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
# Class
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
class NumberToWordsBase(ABC):
|
|
24
|
+
"""
|
|
25
|
+
Base abstract class for Number to words
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def convert(self, number: int) -> str:
|
|
30
|
+
"""
|
|
31
|
+
Convert number to words
|
|
32
|
+
|
|
33
|
+
Parameters
|
|
34
|
+
----------
|
|
35
|
+
number : int
|
|
36
|
+
Number to convert to
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
str
|
|
41
|
+
Number in word form
|
|
42
|
+
"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class NumberToWordsEN(NumberToWordsBase):
|
|
47
|
+
"""
|
|
48
|
+
Number to words - English
|
|
49
|
+
|
|
50
|
+
Support up to 10^120
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
_ones = [
|
|
54
|
+
"zero",
|
|
55
|
+
"one",
|
|
56
|
+
"two",
|
|
57
|
+
"three",
|
|
58
|
+
"four",
|
|
59
|
+
"five",
|
|
60
|
+
"six",
|
|
61
|
+
"seven",
|
|
62
|
+
"eight",
|
|
63
|
+
"nine",
|
|
64
|
+
"ten",
|
|
65
|
+
"eleven",
|
|
66
|
+
"twelve",
|
|
67
|
+
"thirteen",
|
|
68
|
+
"fourteen",
|
|
69
|
+
"fifteen",
|
|
70
|
+
"sixteen",
|
|
71
|
+
"seventeen",
|
|
72
|
+
"eighteen",
|
|
73
|
+
"nineteen",
|
|
74
|
+
]
|
|
75
|
+
|
|
76
|
+
_tens = ["", "", "twenty", "thirty", "forty", "fifty", "sixty", "seventy", "eighty", "ninety"]
|
|
77
|
+
|
|
78
|
+
_scales = [
|
|
79
|
+
"", # 10^0
|
|
80
|
+
"thousand", # 10^3
|
|
81
|
+
"million", # 10^6
|
|
82
|
+
"billion", # 10^9
|
|
83
|
+
"trillion", # 10^12
|
|
84
|
+
"quadrillion", # 10^15
|
|
85
|
+
"quintillion",
|
|
86
|
+
"sextillion",
|
|
87
|
+
"septillion",
|
|
88
|
+
"octillion",
|
|
89
|
+
"nonillion",
|
|
90
|
+
"decillion",
|
|
91
|
+
"undecillion",
|
|
92
|
+
"duodecillion",
|
|
93
|
+
"tredecillion",
|
|
94
|
+
"quattuordecillion",
|
|
95
|
+
"quindecillion",
|
|
96
|
+
"sexdecillion",
|
|
97
|
+
"septendecillion",
|
|
98
|
+
"octodecillion",
|
|
99
|
+
"novemdecillion",
|
|
100
|
+
"vigintillion",
|
|
101
|
+
"unvigintillion",
|
|
102
|
+
"duovigintillion",
|
|
103
|
+
"tresvigintillion",
|
|
104
|
+
"quattuorvigintillion",
|
|
105
|
+
"quinvigintillion",
|
|
106
|
+
"sesvigintillion",
|
|
107
|
+
"septemvigintillion",
|
|
108
|
+
"octovigintillion",
|
|
109
|
+
"novemvigintillion",
|
|
110
|
+
"trigintillion",
|
|
111
|
+
"untrigintillion",
|
|
112
|
+
"duotrigintillion",
|
|
113
|
+
"trestrigintillion",
|
|
114
|
+
"quattuortrigintillion",
|
|
115
|
+
"quintrigintillion",
|
|
116
|
+
"sestrigintillion",
|
|
117
|
+
"septentrigintillion",
|
|
118
|
+
"octotrigintillion",
|
|
119
|
+
"noventrigintillion",
|
|
120
|
+
]
|
|
121
|
+
|
|
122
|
+
def convert(self, number) -> str:
|
|
123
|
+
dec = Decimal(str(number))
|
|
124
|
+
|
|
125
|
+
if dec == 0:
|
|
126
|
+
return "zero"
|
|
127
|
+
|
|
128
|
+
sign = ""
|
|
129
|
+
if dec < 0:
|
|
130
|
+
sign = "minus "
|
|
131
|
+
dec = abs(dec)
|
|
132
|
+
|
|
133
|
+
integer_part = int(dec)
|
|
134
|
+
fractional_part = dec - integer_part
|
|
135
|
+
|
|
136
|
+
words = sign + self._convert_integer(integer_part)
|
|
137
|
+
|
|
138
|
+
if fractional_part != 0:
|
|
139
|
+
frac_digits = str(fractional_part).split(".")[1]
|
|
140
|
+
frac_words = " ".join(self._ones[int(d)] for d in frac_digits)
|
|
141
|
+
words += f" point {frac_words}"
|
|
142
|
+
|
|
143
|
+
return words
|
|
144
|
+
|
|
145
|
+
def _convert_integer(self, number: int) -> str:
|
|
146
|
+
if number == 0:
|
|
147
|
+
return "zero"
|
|
148
|
+
|
|
149
|
+
parts = []
|
|
150
|
+
scale_index = 0
|
|
151
|
+
|
|
152
|
+
try:
|
|
153
|
+
while number > 0:
|
|
154
|
+
number, group = divmod(number, 1000)
|
|
155
|
+
if group > 0:
|
|
156
|
+
text = self._under_1000(group)
|
|
157
|
+
scale = self._scales[scale_index]
|
|
158
|
+
parts.append(f"{text} {scale}".strip())
|
|
159
|
+
scale_index += 1
|
|
160
|
+
except IndexError:
|
|
161
|
+
raise ValueError("Number to large")
|
|
162
|
+
|
|
163
|
+
return " ".join(reversed(parts))
|
|
164
|
+
|
|
165
|
+
def _under_1000(self, number: int) -> str:
|
|
166
|
+
hundreds, rest = divmod(number, 100)
|
|
167
|
+
parts = []
|
|
168
|
+
|
|
169
|
+
if hundreds > 0:
|
|
170
|
+
parts.append(f"{self._ones[hundreds]} hundred")
|
|
171
|
+
|
|
172
|
+
if rest > 0:
|
|
173
|
+
if hundreds > 0:
|
|
174
|
+
parts.append("and")
|
|
175
|
+
parts.append(self._under_100(rest))
|
|
176
|
+
|
|
177
|
+
return " ".join(parts)
|
|
178
|
+
|
|
179
|
+
def _under_100(self, number: int) -> str:
|
|
180
|
+
if number < 20:
|
|
181
|
+
return self._ones[number]
|
|
182
|
+
|
|
183
|
+
tens, unit = divmod(number, 10)
|
|
184
|
+
if unit == 0:
|
|
185
|
+
return self._tens[tens]
|
|
186
|
+
|
|
187
|
+
return f"{self._tens[tens]} {self._ones[unit]}"
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class NumberToWordsVI(NumberToWordsBase):
|
|
191
|
+
"""
|
|
192
|
+
Number to words - Vietnamese
|
|
193
|
+
|
|
194
|
+
Support:
|
|
195
|
+
- Scale infinitely
|
|
196
|
+
- Decimal number
|
|
197
|
+
"""
|
|
198
|
+
|
|
199
|
+
_ones = ["không", "một", "hai", "ba", "bốn", "năm", "sáu", "bảy", "tám", "chín"]
|
|
200
|
+
|
|
201
|
+
def convert(self, number: int | float) -> str:
|
|
202
|
+
dec = Decimal(str(number))
|
|
203
|
+
|
|
204
|
+
if dec == 0:
|
|
205
|
+
return "không"
|
|
206
|
+
|
|
207
|
+
sign = ""
|
|
208
|
+
if dec < 0:
|
|
209
|
+
sign = "âm "
|
|
210
|
+
dec = abs(dec)
|
|
211
|
+
|
|
212
|
+
integer_part = int(dec)
|
|
213
|
+
fractional_part = dec - integer_part
|
|
214
|
+
|
|
215
|
+
words = sign + self._convert_integer(integer_part)
|
|
216
|
+
|
|
217
|
+
if fractional_part != 0:
|
|
218
|
+
frac_digits = str(fractional_part).split(".")[1]
|
|
219
|
+
frac_words = " ".join(self._ones[int(d)] for d in frac_digits)
|
|
220
|
+
words += f" phẩy {frac_words}"
|
|
221
|
+
|
|
222
|
+
return words
|
|
223
|
+
|
|
224
|
+
def _convert_integer(self, number: int) -> str:
|
|
225
|
+
parts = []
|
|
226
|
+
index = 0
|
|
227
|
+
|
|
228
|
+
while number > 0:
|
|
229
|
+
number, group = divmod(number, 1000)
|
|
230
|
+
if group > 0:
|
|
231
|
+
text = self._under_1000(group)
|
|
232
|
+
scale = self._scale_name(index)
|
|
233
|
+
parts.append(f"{text} {scale}".strip())
|
|
234
|
+
index += 1
|
|
235
|
+
|
|
236
|
+
return " ".join(reversed(parts))
|
|
237
|
+
|
|
238
|
+
def _scale_name(self, index: int) -> str:
|
|
239
|
+
if index == 0:
|
|
240
|
+
return ""
|
|
241
|
+
|
|
242
|
+
base = ["", "nghìn", "triệu"]
|
|
243
|
+
pos = index % 3
|
|
244
|
+
level = index // 3
|
|
245
|
+
|
|
246
|
+
scale = base[pos]
|
|
247
|
+
if level > 0:
|
|
248
|
+
scale = f"{scale} {'tỷ ' * level}".strip()
|
|
249
|
+
|
|
250
|
+
return scale.strip()
|
|
251
|
+
|
|
252
|
+
def _under_1000(self, number: int) -> str:
|
|
253
|
+
hundreds, rest = divmod(number, 100)
|
|
254
|
+
parts = []
|
|
255
|
+
|
|
256
|
+
if hundreds > 0:
|
|
257
|
+
parts.append(f"{self._ones[hundreds]} trăm")
|
|
258
|
+
|
|
259
|
+
if rest > 0:
|
|
260
|
+
if hundreds > 0 and rest < 10:
|
|
261
|
+
parts.append("lẻ")
|
|
262
|
+
parts.append(self._under_100(rest))
|
|
263
|
+
|
|
264
|
+
return " ".join(parts)
|
|
265
|
+
|
|
266
|
+
def _under_100(self, number: int) -> str:
|
|
267
|
+
if number < 10:
|
|
268
|
+
return self._ones[number]
|
|
269
|
+
|
|
270
|
+
tens, unit = divmod(number, 10)
|
|
271
|
+
|
|
272
|
+
if tens == 1:
|
|
273
|
+
if unit == 0:
|
|
274
|
+
return "mười"
|
|
275
|
+
if unit == 5:
|
|
276
|
+
return "mười lăm"
|
|
277
|
+
return f"mười {self._ones[unit]}"
|
|
278
|
+
|
|
279
|
+
result = f"{self._ones[tens]} mươi"
|
|
280
|
+
|
|
281
|
+
if unit == 0:
|
|
282
|
+
return result
|
|
283
|
+
if unit == 1:
|
|
284
|
+
return f"{result} mốt"
|
|
285
|
+
if unit == 5:
|
|
286
|
+
return f"{result} lăm"
|
|
287
|
+
|
|
288
|
+
return f"{result} {self._ones[unit]}"
|
|
289
|
+
|
|
290
|
+
|
|
291
|
+
# Main
|
|
292
|
+
class NumberToWords:
|
|
293
|
+
_languages: dict[str, NumberToWordsBase] = {
|
|
294
|
+
"en": NumberToWordsEN,
|
|
295
|
+
"vi": NumberToWordsVI,
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
def __init__(self, lang: str = "en"):
|
|
299
|
+
self.set_language(lang)
|
|
300
|
+
|
|
301
|
+
def set_language(self, lang: str) -> None:
|
|
302
|
+
if lang not in self._languages:
|
|
303
|
+
raise ValueError(f"Unsupported language: {lang}")
|
|
304
|
+
# self._converter = self._languages[lang]()
|
|
305
|
+
self._converter: NumberToWordsBase = self._languages.get(lang, NumberToWordsEN)()
|
|
306
|
+
|
|
307
|
+
def convert(self, number: int) -> str:
|
|
308
|
+
"""
|
|
309
|
+
Convert number to words
|
|
310
|
+
|
|
311
|
+
Parameters
|
|
312
|
+
----------
|
|
313
|
+
number : int
|
|
314
|
+
Number to convert to
|
|
315
|
+
|
|
316
|
+
Returns
|
|
317
|
+
-------
|
|
318
|
+
str
|
|
319
|
+
Number in word form
|
|
320
|
+
"""
|
|
321
|
+
return self._converter.convert(number)
|
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Absfuyu: Shorten number
|
|
3
|
+
-----------------------
|
|
4
|
+
Short number base on suffixes
|
|
5
|
+
|
|
6
|
+
Version: 6.1.1
|
|
7
|
+
Date updated: 30/12/2025 (dd/mm/yyyy)
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
# Module level
|
|
11
|
+
# ---------------------------------------------------------------------------
|
|
12
|
+
__all__ = [
|
|
13
|
+
"UnitSuffixFactory",
|
|
14
|
+
"CommonUnitSuffixesFactory",
|
|
15
|
+
"Decimal",
|
|
16
|
+
"shorten_number",
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
# Library
|
|
21
|
+
# ---------------------------------------------------------------------------
|
|
22
|
+
from collections.abc import Callable
|
|
23
|
+
from dataclasses import dataclass, field
|
|
24
|
+
from functools import wraps
|
|
25
|
+
from typing import Annotated, NamedTuple, ParamSpec, Self, TypeVar
|
|
26
|
+
|
|
27
|
+
from absfuyu.core import versionadded
|
|
28
|
+
|
|
29
|
+
# Type
|
|
30
|
+
# ---------------------------------------------------------------------------
|
|
31
|
+
P = ParamSpec("P") # Parameter type
|
|
32
|
+
N = TypeVar("N", int, float) # Number type
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
# Class - Decimal
|
|
36
|
+
# ---------------------------------------------------------------------------
|
|
37
|
+
@versionadded("4.1.0")
|
|
38
|
+
class UnitSuffixFactory(NamedTuple):
|
|
39
|
+
base: int
|
|
40
|
+
short_name: list[str]
|
|
41
|
+
full_name: list[str]
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@versionadded("4.1.0")
|
|
45
|
+
class CommonUnitSuffixesFactory:
|
|
46
|
+
NUMBER = UnitSuffixFactory(
|
|
47
|
+
1000,
|
|
48
|
+
[
|
|
49
|
+
"",
|
|
50
|
+
"K",
|
|
51
|
+
"M",
|
|
52
|
+
"B",
|
|
53
|
+
"T",
|
|
54
|
+
"Qa",
|
|
55
|
+
"Qi",
|
|
56
|
+
"Sx",
|
|
57
|
+
"Sp",
|
|
58
|
+
"Oc",
|
|
59
|
+
"No",
|
|
60
|
+
"Dc",
|
|
61
|
+
"Ud",
|
|
62
|
+
"Dd",
|
|
63
|
+
"Td",
|
|
64
|
+
"Qad",
|
|
65
|
+
"Qid",
|
|
66
|
+
"Sxd",
|
|
67
|
+
"Spd",
|
|
68
|
+
"Ocd",
|
|
69
|
+
"Nod",
|
|
70
|
+
"Vg",
|
|
71
|
+
"Uvg",
|
|
72
|
+
"Dvg",
|
|
73
|
+
"Tvg",
|
|
74
|
+
"Qavg",
|
|
75
|
+
"Qivg",
|
|
76
|
+
"Sxvg",
|
|
77
|
+
"Spvg",
|
|
78
|
+
"Ovg",
|
|
79
|
+
"Nvg",
|
|
80
|
+
"Tg",
|
|
81
|
+
"Utg",
|
|
82
|
+
"Dtg",
|
|
83
|
+
"Ttg",
|
|
84
|
+
"Qatg",
|
|
85
|
+
"Qitg",
|
|
86
|
+
"Sxtg",
|
|
87
|
+
"Sptg",
|
|
88
|
+
"Otg",
|
|
89
|
+
"Ntg",
|
|
90
|
+
],
|
|
91
|
+
[
|
|
92
|
+
"", # < Thousand
|
|
93
|
+
"Thousand",
|
|
94
|
+
"Million",
|
|
95
|
+
"Billion", # 1e9
|
|
96
|
+
"Trillion",
|
|
97
|
+
"Quadrillion",
|
|
98
|
+
"Quintillion",
|
|
99
|
+
"Sextillion",
|
|
100
|
+
"Septillion",
|
|
101
|
+
"Octillion",
|
|
102
|
+
"Nonillion",
|
|
103
|
+
"Decillion", # 1e33
|
|
104
|
+
"Undecillion",
|
|
105
|
+
"Duodecillion",
|
|
106
|
+
"Tredecillion",
|
|
107
|
+
"Quattuordecillion",
|
|
108
|
+
"Quindecillion",
|
|
109
|
+
"Sexdecillion",
|
|
110
|
+
"Septendecillion",
|
|
111
|
+
"Octodecillion",
|
|
112
|
+
"Novemdecillion",
|
|
113
|
+
"Vigintillion", # 1e63
|
|
114
|
+
"Unvigintillion",
|
|
115
|
+
"Duovigintillion",
|
|
116
|
+
"Tresvigintillion",
|
|
117
|
+
"Quattuorvigintillion",
|
|
118
|
+
"Quinvigintillion",
|
|
119
|
+
"Sesvigintillion",
|
|
120
|
+
"Septemvigintillion",
|
|
121
|
+
"Octovigintillion",
|
|
122
|
+
"Novemvigintillion",
|
|
123
|
+
"Trigintillion", # 1e93
|
|
124
|
+
"Untrigintillion",
|
|
125
|
+
"Duotrigintillion",
|
|
126
|
+
"Trestrigintillion",
|
|
127
|
+
"Quattuortrigintillion",
|
|
128
|
+
"Quintrigintillion",
|
|
129
|
+
"Sestrigintillion",
|
|
130
|
+
"Septentrigintillion",
|
|
131
|
+
"Octotrigintillion",
|
|
132
|
+
"Noventrigintillion", # 1e120
|
|
133
|
+
],
|
|
134
|
+
)
|
|
135
|
+
DATA_SIZE = UnitSuffixFactory(
|
|
136
|
+
1024,
|
|
137
|
+
["b", "Kb", "MB", "GB", "TB", "PB", "EB", "ZB", "YB", "BB"],
|
|
138
|
+
[
|
|
139
|
+
"byte",
|
|
140
|
+
"kilobyte",
|
|
141
|
+
"megabyte",
|
|
142
|
+
"gigabyte",
|
|
143
|
+
"terabyte",
|
|
144
|
+
"petabyte",
|
|
145
|
+
"exabyte",
|
|
146
|
+
"zetabyte",
|
|
147
|
+
"yottabyte",
|
|
148
|
+
"brontobyte",
|
|
149
|
+
],
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
@dataclass
|
|
154
|
+
@versionadded("4.1.0")
|
|
155
|
+
class Decimal:
|
|
156
|
+
"""
|
|
157
|
+
Shorten large number
|
|
158
|
+
|
|
159
|
+
Parameters
|
|
160
|
+
----------
|
|
161
|
+
original_value : int | float
|
|
162
|
+
Value to shorten
|
|
163
|
+
|
|
164
|
+
base : int
|
|
165
|
+
Short by base (must be > 0)
|
|
166
|
+
|
|
167
|
+
suffixes : list[str]
|
|
168
|
+
List of suffixes to use (ascending order)
|
|
169
|
+
|
|
170
|
+
factory : UnitSuffixFactory | None
|
|
171
|
+
``UnitSuffixFactory`` to use
|
|
172
|
+
(will overwrite ``base`` and ``suffixes``)
|
|
173
|
+
|
|
174
|
+
suffix_full_name : bool
|
|
175
|
+
Use suffix full name (available with ``UnitSuffixFactory``), by default ``False``
|
|
176
|
+
|
|
177
|
+
Returns
|
|
178
|
+
-------
|
|
179
|
+
Decimal
|
|
180
|
+
Decimal instance
|
|
181
|
+
"""
|
|
182
|
+
|
|
183
|
+
original_value: int | float = field(repr=False)
|
|
184
|
+
base: Annotated[int, "positive", "not_zero"] = field(repr=False, default=1000)
|
|
185
|
+
suffixes: list[str] = field(repr=False, default_factory=list)
|
|
186
|
+
factory: UnitSuffixFactory | None = field(repr=False, default=None)
|
|
187
|
+
suffix_full_name: bool = field(repr=False, default=False)
|
|
188
|
+
# Post init
|
|
189
|
+
value: int | float = field(init=False)
|
|
190
|
+
suffix: str = field(init=False)
|
|
191
|
+
|
|
192
|
+
def __post_init__(self) -> None:
|
|
193
|
+
self.base = max(1, self.base) # Make sure that base >= 1
|
|
194
|
+
self._get_factory()
|
|
195
|
+
self.value, self.suffix = self._convert_decimal()
|
|
196
|
+
|
|
197
|
+
def __str__(self) -> str:
|
|
198
|
+
return self.to_text().strip()
|
|
199
|
+
|
|
200
|
+
@classmethod
|
|
201
|
+
def number(cls, value: int | float, suffix_full_name: bool = False) -> Self:
|
|
202
|
+
"""Decimal for normal large number"""
|
|
203
|
+
return cls(
|
|
204
|
+
value,
|
|
205
|
+
factory=CommonUnitSuffixesFactory.NUMBER,
|
|
206
|
+
suffix_full_name=suffix_full_name,
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
@classmethod
|
|
210
|
+
def data_size(cls, value: int | float, suffix_full_name: bool = False) -> Self:
|
|
211
|
+
"""Decimal for data size"""
|
|
212
|
+
return cls(
|
|
213
|
+
value,
|
|
214
|
+
factory=CommonUnitSuffixesFactory.DATA_SIZE,
|
|
215
|
+
suffix_full_name=suffix_full_name,
|
|
216
|
+
)
|
|
217
|
+
|
|
218
|
+
@staticmethod
|
|
219
|
+
def scientific_short(value: int | float) -> str:
|
|
220
|
+
"""Short number in scientific format"""
|
|
221
|
+
return f"{value:.2e}"
|
|
222
|
+
|
|
223
|
+
def _get_factory(self) -> None:
|
|
224
|
+
if self.factory is not None:
|
|
225
|
+
self.base = self.factory.base
|
|
226
|
+
self.suffixes = self.factory.full_name if self.suffix_full_name else self.factory.short_name
|
|
227
|
+
|
|
228
|
+
def _convert_decimal(self) -> tuple[float, str]:
|
|
229
|
+
"""Convert to smaller number"""
|
|
230
|
+
suffix = self.suffixes[0] if len(self.suffixes) > 0 else ""
|
|
231
|
+
unit = 1
|
|
232
|
+
for i, suffix in enumerate(self.suffixes):
|
|
233
|
+
unit = self.base**i
|
|
234
|
+
if self.original_value < unit * self.base:
|
|
235
|
+
break
|
|
236
|
+
output = self.original_value / unit
|
|
237
|
+
return output, suffix
|
|
238
|
+
|
|
239
|
+
def to_text(self, decimal: int = 2, *, separator: str = " ", float_only: bool = True) -> str:
|
|
240
|
+
"""
|
|
241
|
+
Convert to string
|
|
242
|
+
|
|
243
|
+
Parameters
|
|
244
|
+
----------
|
|
245
|
+
decimal : int, optional
|
|
246
|
+
Round up to which decimal, by default ``2``
|
|
247
|
+
|
|
248
|
+
separator : str, optional
|
|
249
|
+
Character between value and suffix, by default ``" "``
|
|
250
|
+
|
|
251
|
+
float_only : bool, optional
|
|
252
|
+
Returns value as <float> instead of <int> when ``decimal = 0``, by default ``True``
|
|
253
|
+
|
|
254
|
+
Returns
|
|
255
|
+
-------
|
|
256
|
+
str
|
|
257
|
+
Decimal string
|
|
258
|
+
"""
|
|
259
|
+
val = self.value.__round__(decimal)
|
|
260
|
+
formatted_value = f"{val:,}"
|
|
261
|
+
if not float_only and decimal == 0:
|
|
262
|
+
formatted_value = f"{int(val):,}"
|
|
263
|
+
return f"{formatted_value}{separator}{self.suffix}"
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
# Decorator
|
|
267
|
+
# ---------------------------------------------------------------------------
|
|
268
|
+
@versionadded("5.0.0")
|
|
269
|
+
def shorten_number(f: Callable[P, N]) -> Callable[P, Decimal]:
|
|
270
|
+
"""
|
|
271
|
+
Shorten the number value by name
|
|
272
|
+
|
|
273
|
+
Parameters
|
|
274
|
+
----------
|
|
275
|
+
f : Callable[P, N]
|
|
276
|
+
Function that return ``int`` or ``float``
|
|
277
|
+
|
|
278
|
+
Returns
|
|
279
|
+
-------
|
|
280
|
+
Callable[P, Decimal]
|
|
281
|
+
Function that return ``Decimal``
|
|
282
|
+
|
|
283
|
+
|
|
284
|
+
Usage
|
|
285
|
+
-----
|
|
286
|
+
Use this as a decorator (``@shorten_number``)
|
|
287
|
+
|
|
288
|
+
Example:
|
|
289
|
+
--------
|
|
290
|
+
>>> import random
|
|
291
|
+
>>> @shorten_number
|
|
292
|
+
>>> def big_num() -> int:
|
|
293
|
+
... random.randint(100000000, 10000000000)
|
|
294
|
+
>>> big_num()
|
|
295
|
+
4.20 B
|
|
296
|
+
"""
|
|
297
|
+
|
|
298
|
+
@wraps(f)
|
|
299
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> Decimal:
|
|
300
|
+
value = Decimal.number(f(*args, **kwargs))
|
|
301
|
+
return value
|
|
302
|
+
|
|
303
|
+
return wrapper
|