Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/humanize/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
clamp,
fractional,
intcomma,
intsuffix,
intword,
ordinal,
scientific,
Expand Down Expand Up @@ -37,6 +38,7 @@
"deactivate",
"fractional",
"intcomma",
"intsuffix",
"intword",
"naturaldate",
"naturalday",
Expand Down
69 changes: 69 additions & 0 deletions src/humanize/number.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,75 @@ def intword(value, format="%.1f"):
return str(value)


suffix_powers = [10**x for x in (3, 6, 9, 12, 15, 18, 21, 24, 27)]
suffix_human_powers = (
NS_("k", "k"),
NS_("M", "M"),
NS_("G", "G"),
NS_("T", "T"),
NS_("P", "P"),
NS_("E", "E"),
NS_("Z", "Z"),
NS_("Y", "Y"),
)


def intsuffix(value, format="%.1f"):
"""Converts a large integer to a friendly text representation with only suffix.

Works best for numbers over 1 million. For example, 1_000_000 becomes "1.0 M",
1200000 becomes "1.2 M" and "1_200_000_000" becomes "1.2 T". Supports up
to decillion (33 digits) and googol (100 digits).

Examples:
```pycon
>>> intsuffix("100")
'100'
>>> intsuffix("12400")
'12.4 k'
>>> intsuffix("1000000")
'1.0 M'
>>> intsuffix(1_200_000_000)
'1.2 G'
>>> intsuffix(None) is None
True
>>> intsuffix("1234000", "%0.3f")
'1.234 M'

```
Args:
value (int, float, str): Integer to convert.
format (str): To change the number of decimal or general format of the number
portion.

Returns:
str: Friendly text representation as a string, unless the value passed could not
be coaxed into an `int`.
"""
try:
value = int(value)
except (TypeError, ValueError):
return value

if value < suffix_powers[0]:
return str(value)
for ordinal, power in enumerate(suffix_powers[1:], 1):
if value < power:
chopped = value / float(suffix_powers[ordinal - 1])
if float(format % chopped) == float(10**3):
chopped = value / float(suffix_powers[ordinal])
singular, plural = suffix_human_powers[ordinal]
return (
" ".join([format, _ngettext(singular, plural, math.ceil(chopped))])
) % chopped
else:
singular, plural = suffix_human_powers[ordinal - 1]
return (
" ".join([format, _ngettext(singular, plural, math.ceil(chopped))])
) % chopped
return str(value)


def apnumber(value):
"""Converts an integer to Associated Press style.

Expand Down
36 changes: 36 additions & 0 deletions tests/test_number.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,42 @@ def test_intword(test_args, expected):
assert humanize.intword(*test_args) == expected


def test_intsuffix_powers():
# make sure that suffix_powers & suffix_human_powers have the same number of items
assert len(number.powers) == len(number.human_powers)


@pytest.mark.parametrize(
"test_args, expected",
[
(["100"], "100"),
(["1000"], "1.0 k"),
(["12400"], "12.4 k"),
(["12490"], "12.5 k"),
(["1000000"], "1.0 M"),
(["1200000"], "1.2 M"),
(["1290000"], "1.3 M"),
(["999999999"], "1.0 G"),
(["1000000000"], "1.0 G"),
(["2000000000"], "2.0 G"),
(["999999999999"], "1.0 T"),
(["1000000000000"], "1.0 T"),
(["6000000000000"], "6.0 T"),
(["999999999999999"], "1.0 P"),
(["1000000000000000"], "1.0 P"),
(["1300000000000000"], "1.3 P"),
(["1400000000000000000"], "1.4 E"),
(["3500000000000000000000"], "3.5 Z"),
(["3600000000000000000000000"], "3.6 Y"),
([None], None),
(["1230000", "%0.2f"], "1.23 M"),
([10**101], "1" + "0" * 101),
],
)
def test_intsuffix(test_args, expected):
assert humanize.intsuffix(*test_args) == expected


@pytest.mark.parametrize(
"test_input, expected",
[
Expand Down