diff --git a/babel/numbers.py b/babel/numbers.py index 01f1ca70e..2240c65d5 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -1099,7 +1099,7 @@ def parse_decimal( 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, numbering_system=numbering_system) - if string != proper and string.rstrip('0') != (proper + decimal_symbol): + if string != proper and proper != _remove_trailing_zeros_after_decimal(string, decimal_symbol): try: parsed_alt = decimal.Decimal(string.replace(decimal_symbol, '') .replace(group_symbol, '.')) @@ -1131,6 +1131,41 @@ def parse_decimal( return parsed +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: + decimal_part = decimal_part.rstrip("0") + if decimal_part: + return integer_part + decimal_symbol + decimal_part + return integer_part + + return string + + PREFIX_END = r'[^0-9@#.,]' NUMBER_TOKEN = r'[0-9@#.,E+]' diff --git a/tests/test_numbers.py b/tests/test_numbers.py index 5d9830bd5..d89592a0e 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -195,7 +195,6 @@ def test_can_parse_decimals(self): with pytest.raises(numbers.UnsupportedNumberingSystemError): numbers.parse_decimal('2,109,998', locale='de', numbering_system="unknown") - def test_parse_decimal_strict_mode(self): # Numbers with a misplaced grouping symbol should be rejected with pytest.raises(numbers.NumberFormatError) as info: @@ -221,8 +220,19 @@ 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' + assert str(numbers.parse_decimal('3,400.0000', locale='en_US', strict=True)) == '3400.0000' + # Numbers with a grouping symbol and no decimal part should be accepted + assert str(numbers.parse_decimal('3,800', locale='en_US', strict=True)) == '3800' # Numbers without any grouping symbol should be accepted assert str(numbers.parse_decimal('2000.1', locale='en_US', strict=True)) == '2000.1' + # Numbers without any grouping symbol and no decimal should be accepted + assert str(numbers.parse_decimal('2580', locale='en_US', strict=True)) == '2580' # High precision numbers should be accepted assert str(numbers.parse_decimal('5,000001', locale='fr', strict=True)) == '5.000001'