Skip to content

Commit

Permalink
fix(numbers): correctly determine malformed decimals
Browse files Browse the repository at this point in the history
Signed-off-by: Olunusi Best <[email protected]>
  • Loading branch information
olunusib committed Nov 23, 2023
1 parent bf7b2ca commit 725bb57
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 1 deletion.
35 changes: 34 additions & 1 deletion babel/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -888,6 +888,39 @@ def parse_number(string: str, locale: Locale | str | None = LC_NUMERIC) -> int:
raise NumberFormatError(f"{string!r} is not a valid number") from ve


def _remove_trailing_zeros_after_decimal(string: str, decimal_symbol: str) -> str:
"""
Remove trailing zeros from the decimal part of a numeric string.
This function takes a string representing a numeric value and a decimal symbol.
It removes any trailing zeros that appear after the decimal symbol in the number.
If the decimal part becomes empty after removing trailing zeros, the decimal symbol
is also removed. If the string does not contain the decimal symbol, it is returned unchanged.
:param string: The numeric string from which to remove trailing zeros.
:type string: str
:param decimal_symbol: The symbol used to denote the decimal point.
:type decimal_symbol: str
:return: The numeric string with trailing zeros removed from its decimal part.
:rtype: str
Example:
>>> _remove_trailing_zeros_after_decimal("123.4500", ".")
'123.45'
>>> _remove_trailing_zeros_after_decimal("100.000", ".")
'100'
>>> _remove_trailing_zeros_after_decimal("100", ".")
'100'
"""
integer_part, _, decimal_part = string.partition(decimal_symbol)

if decimal_part:
stripped_part = decimal_part.rstrip("0")
return integer_part + (decimal_symbol + stripped_part if stripped_part else "")

return string


def parse_decimal(string: str, locale: Locale | str | None = LC_NUMERIC, strict: bool = False) -> decimal.Decimal:
"""Parse localized decimal string into a decimal.
Expand Down Expand Up @@ -944,7 +977,7 @@ def parse_decimal(string: str, locale: Locale | str | None = LC_NUMERIC, strict:
raise NumberFormatError(f"{string!r} is not a valid decimal number") from exc
if strict and group_symbol in string:
proper = format_decimal(parsed, locale=locale, decimal_quantization=False)
if string != proper and string.rstrip('0') != (proper + decimal_symbol):
if string != proper and _remove_trailing_zeros_after_decimal(string, decimal_symbol) != proper:
try:
parsed_alt = decimal.Decimal(string.replace(decimal_symbol, '')
.replace(group_symbol, '.'))
Expand Down
6 changes: 6 additions & 0 deletions tests/test_numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,12 @@ def test_parse_decimal_strict_mode(self):
assert str(numbers.parse_decimal('1.001', locale='de', strict=True)) == '1001'
# Trailing zeroes should be accepted
assert str(numbers.parse_decimal('3.00', locale='en_US', strict=True)) == '3.00'
# Numbers with a grouping symbol and no trailing zeroes should be accepted
assert str(numbers.parse_decimal('3,400.6', locale='en_US', strict=True)) == '3400.6'
# Numbers with a grouping symbol and trailing zeroes (not all zeroes after decimal) should be accepted
assert str(numbers.parse_decimal('3,400.60', locale='en_US', strict=True)) == '3400.60'
# Numbers with a grouping symbol and trailing zeroes (all zeroes after decimal) should be accepted
assert str(numbers.parse_decimal('3,400.00', locale='en_US', strict=True)) == '3400.00'
# Numbers without any grouping symbol should be accepted
assert str(numbers.parse_decimal('2000.1', locale='en_US', strict=True)) == '2000.1'
# High precision numbers should be accepted
Expand Down

0 comments on commit 725bb57

Please sign in to comment.