|
31 | 31 | "9": "⁹", |
32 | 32 | "-": "⁻", |
33 | 33 | } |
| 34 | +_SUPERSCRIPT_TRANS = str.maketrans(_SUPERSCRIPT_MAP) |
| 35 | + |
| 36 | +_ORDINAL_SUFFIXES = ("th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th") |
| 37 | +_APNUMBER_WORDS = ( |
| 38 | + "zero", |
| 39 | + "one", |
| 40 | + "two", |
| 41 | + "three", |
| 42 | + "four", |
| 43 | + "five", |
| 44 | + "six", |
| 45 | + "seven", |
| 46 | + "eight", |
| 47 | + "nine", |
| 48 | +) |
34 | 49 |
|
35 | 50 |
|
36 | 51 | def _format_not_finite(value: float) -> str: |
@@ -91,35 +106,9 @@ def ordinal(value: NumberOrString, gender: str = "male") -> str: |
91 | 106 | value = int(value) |
92 | 107 | except (TypeError, ValueError): |
93 | 108 | return str(value) |
94 | | - if gender == "male": |
95 | | - t = ( |
96 | | - P_("0 (male)", "th"), |
97 | | - P_("1 (male)", "st"), |
98 | | - P_("2 (male)", "nd"), |
99 | | - P_("3 (male)", "rd"), |
100 | | - P_("4 (male)", "th"), |
101 | | - P_("5 (male)", "th"), |
102 | | - P_("6 (male)", "th"), |
103 | | - P_("7 (male)", "th"), |
104 | | - P_("8 (male)", "th"), |
105 | | - P_("9 (male)", "th"), |
106 | | - ) |
107 | | - else: |
108 | | - t = ( |
109 | | - P_("0 (female)", "th"), |
110 | | - P_("1 (female)", "st"), |
111 | | - P_("2 (female)", "nd"), |
112 | | - P_("3 (female)", "rd"), |
113 | | - P_("4 (female)", "th"), |
114 | | - P_("5 (female)", "th"), |
115 | | - P_("6 (female)", "th"), |
116 | | - P_("7 (female)", "th"), |
117 | | - P_("8 (female)", "th"), |
118 | | - P_("9 (female)", "th"), |
119 | | - ) |
120 | | - if value % 100 in (11, 12, 13): # special case |
121 | | - return f"{value}{t[0]}" |
122 | | - return f"{value}{t[value % 10]}" |
| 109 | + gender = "male" if gender == "male" else "female" |
| 110 | + digit = 0 if value % 100 in (11, 12, 13) else value % 10 |
| 111 | + return f"{value}{P_(f'{digit} ({gender})', _ORDINAL_SUFFIXES[digit])}" |
123 | 112 |
|
124 | 113 |
|
125 | 114 | def intcomma(value: NumberOrString, ndigits: int | None = None) -> str: |
@@ -177,17 +166,12 @@ def intcomma(value: NumberOrString, ndigits: int | None = None) -> str: |
177 | 166 | return str(value) |
178 | 167 |
|
179 | 168 | if ndigits is not None: |
180 | | - orig = "{0:.{1}f}".format(value, ndigits) |
| 169 | + result = f"{value:,.{ndigits}f}" |
181 | 170 | else: |
182 | | - orig = str(value) |
183 | | - orig = orig.replace(".", decimal_sep) |
184 | | - import re |
185 | | - |
186 | | - while True: |
187 | | - new = re.sub(r"^(-?\d+)(\d{3})", rf"\g<1>{thousands_sep}\g<2>", orig) |
188 | | - if orig == new: |
189 | | - return new |
190 | | - orig = new |
| 171 | + result = f"{value:,}" |
| 172 | + if thousands_sep != "," or decimal_sep != ".": |
| 173 | + result = result.translate(str.maketrans(",.", thousands_sep + decimal_sep)) |
| 174 | + return result |
191 | 175 |
|
192 | 176 |
|
193 | 177 | powers = [10**x for x in (3, 6, 9, 12, 15, 18, 21, 24, 27, 30, 33, 100)] |
@@ -319,18 +303,7 @@ def apnumber(value: NumberOrString) -> str: |
319 | 303 | return str(value) |
320 | 304 | if not 0 <= value < 10: |
321 | 305 | return str(value) |
322 | | - return ( |
323 | | - _("zero"), |
324 | | - _("one"), |
325 | | - _("two"), |
326 | | - _("three"), |
327 | | - _("four"), |
328 | | - _("five"), |
329 | | - _("six"), |
330 | | - _("seven"), |
331 | | - _("eight"), |
332 | | - _("nine"), |
333 | | - )[value] |
| 306 | + return _(_APNUMBER_WORDS[value]) |
334 | 307 |
|
335 | 308 |
|
336 | 309 | def fractional(value: NumberOrString) -> str: |
@@ -436,14 +409,12 @@ def scientific(value: NumberOrString, precision: int = 2) -> str: |
436 | 409 | return _format_not_finite(value) |
437 | 410 | except (ValueError, TypeError): |
438 | 411 | return str(value) |
439 | | - fmt = f"{{:.{str(int(precision))}e}}" |
| 412 | + fmt = f"{{:.{int(precision)}e}}" |
440 | 413 | n = fmt.format(value) |
441 | 414 | part1, part2 = n.split("e") |
442 | | - # Remove redundant leading '+' or '0's (preserving the last '0' for 10⁰). |
443 | | - import re |
444 | | - |
445 | | - part2 = re.sub(r"^\+?(\-?)0*(.+)$", r"\1\2", part2) |
446 | | - return part1 + " x 10" + "".join([_SUPERSCRIPT_MAP[char] for char in part2]) |
| 415 | + # Normalise exponent: int() strips the "+" sign and leading zeros, |
| 416 | + # while preserving "-" and a single "0" for 10⁰. |
| 417 | + return part1 + " x 10" + str(int(part2)).translate(_SUPERSCRIPT_TRANS) |
447 | 418 |
|
448 | 419 |
|
449 | 420 | def clamp( |
|
0 commit comments