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
22 changes: 14 additions & 8 deletions babel/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,8 @@ def _get_compact_format(
if number.is_nan() or number.is_infinite():
return number, None
format = None
for magnitude in sorted([int(m) for m in compact_format["other"]], reverse=True):
magnitudes = sorted([int(m) for m in compact_format["other"]], reverse=True)
for index, magnitude in enumerate(magnitudes):
if abs(number) >= magnitude:
# check the pattern using "other" as the amount
format = compact_format["other"][str(magnitude)]
Expand All @@ -678,17 +679,22 @@ def _get_compact_format(
break
# otherwise, we need to divide the number by the magnitude but remove zeros
# equal to the number of 0's in the pattern minus 1
number = cast(
decimal.Decimal,
number / (magnitude // (10 ** (pattern.count("0") - 1))),
)
divisor = magnitude // (10 ** (pattern.count("0") - 1))
scaled = cast(decimal.Decimal, number / divisor)
# round to the number of fraction digits requested
rounded = round(number, fraction_digits)
rounded = round(scaled, fraction_digits)
# Rounding can carry the value up into the next magnitude, e.g.
# 999_999 would otherwise render as "1000K". The CLDR algorithm
# selects the pattern from the rounded value, so redo the lookup
# one magnitude up to get "1M" instead.
if index > 0 and abs(rounded) * divisor >= magnitudes[index - 1]:
bumped = decimal.Decimal(magnitudes[index - 1]).copy_sign(number)
return _get_compact_format(bumped, compact_format, locale, fraction_digits)
# if the remaining number is singular, use the singular format
plural_form = locale.plural_form(abs(number))
plural_form = locale.plural_form(abs(scaled))
if plural_form not in compact_format:
plural_form = "other"
if number == 1 and "1" in compact_format:
if scaled == 1 and "1" in compact_format:
plural_form = "1"
if str(magnitude) not in compact_format[plural_form]:
plural_form = "other" # fall back to other as the implicit default
Expand Down
14 changes: 14 additions & 0 deletions tests/test_numbers_format_decimal.py
Original file line number Diff line number Diff line change
Expand Up @@ -175,3 +175,17 @@ def test_compact():
assert numbers.format_compact_decimal(
12345, format_type="short", locale='ar_EG', fraction_digits=2, numbering_system='latn',
) == '12.34\xa0ألف'


def test_compact_rounding_carries_to_next_magnitude():
# Rounding can carry the value up into the next magnitude. The pattern has
# to be chosen from the rounded value, otherwise 999999 renders as the
# nonsensical "1000K" instead of "1M".
assert numbers.format_compact_decimal(999999, locale='en_US', format_type="short") == '1M'
assert numbers.format_compact_decimal(999999999, locale='en_US', format_type="short") == '1B'
assert numbers.format_compact_decimal(999999999999, locale='en_US', format_type="short") == '1T'
assert numbers.format_compact_decimal(-999999, locale='en_US', format_type="short") == '-1M'
assert numbers.format_compact_decimal(999999, locale='en_US', format_type="long") == '1 million'
# Values that do not carry over are unaffected.
assert numbers.format_compact_decimal(999499, locale='en_US', format_type="short") == '999K'
assert numbers.format_compact_decimal(999949, locale='en_US', format_type="short", fraction_digits=1) == '999.9K'