Skip to content

Commit 5a4fb28

Browse files
committed
Add percentage function (#277)
1 parent 392aef7 commit 5a4fb28

4 files changed

Lines changed: 90 additions & 1 deletion

File tree

‎README.md‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ If seconds are too large, set `minimum_unit` to milliseconds or microseconds:
169169

170170
```pycon
171171
>>> import humanize
172-
>>> humanize.fractional(1/3)
172+
>>> humanize.fractional(1 / 3)
173173
'1/3'
174174
>>> humanize.fractional(1.5)
175175
'1 1/2'
@@ -199,6 +199,18 @@ If seconds are too large, set `minimum_unit` to milliseconds or microseconds:
199199
'1 x 10⁰'
200200
```
201201

202+
### Percentages and ratios
203+
204+
```pycon
205+
>>> import humanize
206+
>>> humanize.percentage(50)
207+
'50%'
208+
>>> humanize.percentage(0.125, is_ratio=True, precision=1)
209+
'12.5%'
210+
>>> humanize.percentage(12.5, precision=2)
211+
'12.50%'
212+
```
213+
202214
## Localization
203215

204216
How to change locale at runtime:

‎src/humanize/__init__.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
intword,
2323
metric,
2424
ordinal,
25+
percentage,
2526
scientific,
2627
)
2728
from humanize.time import (
@@ -52,6 +53,7 @@
5253
"naturalsize",
5354
"naturaltime",
5455
"ordinal",
56+
"percentage",
5557
"precisedelta",
5658
"scientific",
5759
"thousands_separator",

‎src/humanize/number.py‎

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -601,3 +601,53 @@ def metric(value: float, unit: str = "", precision: int = 3) -> str:
601601
space = " "
602602

603603
return f"{value_}{space}{ordinal_}{unit}"
604+
605+
606+
def percentage(
607+
value: NumberOrString, *, is_ratio: bool = False, precision: int = 0
608+
) -> str:
609+
"""Return a human-readable percentage representation of a number.
610+
611+
Examples:
612+
```pycon
613+
>>> percentage(50)
614+
'50%'
615+
>>> percentage(12.5, precision=1)
616+
'12.5%'
617+
>>> percentage(0.125, is_ratio=True, precision=1)
618+
'12.5%'
619+
>>> percentage(1)
620+
'1%'
621+
>>> percentage(1, is_ratio=True)
622+
'100%'
623+
>>> percentage(-1.25, is_ratio=True, precision=1)
624+
'-125.0%'
625+
>>> percentage("foo")
626+
'foo'
627+
>>> percentage(None)
628+
'None'
629+
630+
```
631+
632+
Args:
633+
value (int, float, str): Number or string to format as percentage.
634+
is_ratio (bool): If True, value is treated as a ratio between 0 and 1
635+
and multiplied by 100. Defaults to False.
636+
precision (int): Number of decimal places. Defaults to 0.
637+
638+
Returns:
639+
str: Formatted percentage string.
640+
"""
641+
import math
642+
643+
# checking whether value is a number
644+
try:
645+
value = float(value)
646+
if not math.isfinite(value):
647+
return _format_not_finite(value)
648+
except (ValueError, TypeError):
649+
return str(value)
650+
651+
if is_ratio:
652+
value *= 100
653+
return f"{value:.{precision}f}%"

‎tests/test_number.py‎

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -377,3 +377,28 @@ def test_clamp(test_args: list[typing.Any], expected: str) -> None:
377377
)
378378
def test_metric(test_args: list[typing.Any], expected: str) -> None:
379379
assert humanize.metric(*test_args) == expected
380+
381+
382+
@pytest.mark.parametrize(
383+
"value, kwargs, expected",
384+
[
385+
(50, {}, "50%"),
386+
(0, {}, "0%"),
387+
(100, {}, "100%"),
388+
(0.5, {"is_ratio": True}, "50%"),
389+
(1, {"is_ratio": True}, "100%"),
390+
(12.5, {"precision": 1}, "12.5%"),
391+
(12.5, {"precision": 0}, "12%"),
392+
(0.125, {"is_ratio": True, "precision": 1}, "12.5%"),
393+
(-10, {}, "-10%"),
394+
(-0.125, {"is_ratio": True, "precision": 1}, "-12.5%"),
395+
("75", {}, "75%"),
396+
("foo", {}, "foo"),
397+
(None, {}, "None"),
398+
(math.nan, {}, "NaN"),
399+
(math.inf, {}, "+Inf"),
400+
(-math.inf, {}, "-Inf"),
401+
],
402+
)
403+
def test_percentage(value, kwargs, expected):
404+
assert humanize.percentage(value, **kwargs) == expected

0 commit comments

Comments
 (0)