Skip to content
Open
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
23 changes: 16 additions & 7 deletions humanfriendly/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -185,13 +185,22 @@ def format_size(num_bytes, keep_width=False, binary=False):
>>> format_size(1000 ** 3 * 4)
'4 GB'
"""
for unit in reversed(disk_size_units):
if num_bytes >= unit.binary.divider and binary:
number = round_number(float(num_bytes) / unit.binary.divider, keep_width=keep_width)
return pluralize(number, unit.binary.symbol, unit.binary.symbol)
elif num_bytes >= unit.decimal.divider and not binary:
number = round_number(float(num_bytes) / unit.decimal.divider, keep_width=keep_width)
return pluralize(number, unit.decimal.symbol, unit.decimal.symbol)
base = 1024 if binary else 1000
ordered_units = list(disk_size_units)
for index in range(len(ordered_units) - 1, -1, -1):
unit = ordered_units[index]
side = unit.binary if binary else unit.decimal
if num_bytes >= side.divider:
number = round_number(float(num_bytes) / side.divider, keep_width=keep_width)
# The unit was chosen from the unrounded byte count, but rounding can
# push the mantissa up to `base` (e.g. 999999 bytes -> 999.999 KB,
# which rounds to "1000 KB"). When that happens, carry into the next
# larger unit so the result reads "1 MB" instead of "1000 KB".
if float(number) >= base and index < len(ordered_units) - 1:
side = (ordered_units[index + 1].binary if binary
else ordered_units[index + 1].decimal)
number = round_number(float(num_bytes) / side.divider, keep_width=keep_width)
return pluralize(number, side.symbol, side.symbol)
return pluralize(num_bytes, 'byte')


Expand Down
8 changes: 8 additions & 0 deletions humanfriendly/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,14 @@ def test_format_size(self):
self.assertEqual('1 YiB', format_size(1024 ** 8, binary=True))
self.assertEqual('45 KB', format_size(1000 * 45))
self.assertEqual('2.9 TB', format_size(1000 ** 4 * 2.9))
# Rounding must not leave the mantissa at or above the base while a
# larger unit is available: 999999 bytes is 999.999 KB, which rounds to
# 1000 KB and should carry into 1 MB (not render as "1000 KB").
self.assertEqual('1 MB', format_size(999999))
self.assertEqual('1 GB', format_size(999999999))
self.assertEqual('1 TB', format_size(999999999999))
self.assertEqual('1 MiB', format_size(1024 ** 2 - 1, binary=True))
self.assertEqual('1 GiB', format_size(1024 ** 3 - 1, binary=True))

def test_parse_size(self):
"""Test :func:`humanfriendly.parse_size()`."""
Expand Down