Skip to content

Commit 946efcd

Browse files
olunusibakx
andauthored
Improve parsing of malformed decimals (#1042)
Signed-off-by: Olunusi Best <[email protected]> Co-authored-by: Aarni Koskela <[email protected]>
1 parent aca7663 commit 946efcd

File tree

2 files changed

+47
-2
lines changed

2 files changed

+47
-2
lines changed

babel/numbers.py

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1099,7 +1099,7 @@ def parse_decimal(
10991099
raise NumberFormatError(f"{string!r} is not a valid decimal number") from exc
11001100
if strict and group_symbol in string:
11011101
proper = format_decimal(parsed, locale=locale, decimal_quantization=False, numbering_system=numbering_system)
1102-
if string != proper and string.rstrip('0') != (proper + decimal_symbol):
1102+
if string != proper and proper != _remove_trailing_zeros_after_decimal(string, decimal_symbol):
11031103
try:
11041104
parsed_alt = decimal.Decimal(string.replace(decimal_symbol, '')
11051105
.replace(group_symbol, '.'))
@@ -1131,6 +1131,41 @@ def parse_decimal(
11311131
return parsed
11321132

11331133

1134+
def _remove_trailing_zeros_after_decimal(string: str, decimal_symbol: str) -> str:
1135+
"""
1136+
Remove trailing zeros from the decimal part of a numeric string.
1137+
1138+
This function takes a string representing a numeric value and a decimal symbol.
1139+
It removes any trailing zeros that appear after the decimal symbol in the number.
1140+
If the decimal part becomes empty after removing trailing zeros, the decimal symbol
1141+
is also removed. If the string does not contain the decimal symbol, it is returned unchanged.
1142+
1143+
:param string: The numeric string from which to remove trailing zeros.
1144+
:type string: str
1145+
:param decimal_symbol: The symbol used to denote the decimal point.
1146+
:type decimal_symbol: str
1147+
:return: The numeric string with trailing zeros removed from its decimal part.
1148+
:rtype: str
1149+
1150+
Example:
1151+
>>> _remove_trailing_zeros_after_decimal("123.4500", ".")
1152+
'123.45'
1153+
>>> _remove_trailing_zeros_after_decimal("100.000", ".")
1154+
'100'
1155+
>>> _remove_trailing_zeros_after_decimal("100", ".")
1156+
'100'
1157+
"""
1158+
integer_part, _, decimal_part = string.partition(decimal_symbol)
1159+
1160+
if decimal_part:
1161+
decimal_part = decimal_part.rstrip("0")
1162+
if decimal_part:
1163+
return integer_part + decimal_symbol + decimal_part
1164+
return integer_part
1165+
1166+
return string
1167+
1168+
11341169
PREFIX_END = r'[^0-9@#.,]'
11351170
NUMBER_TOKEN = r'[0-9@#.,E+]'
11361171

tests/test_numbers.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -195,7 +195,6 @@ def test_can_parse_decimals(self):
195195
with pytest.raises(numbers.UnsupportedNumberingSystemError):
196196
numbers.parse_decimal('2,109,998', locale='de', numbering_system="unknown")
197197

198-
199198
def test_parse_decimal_strict_mode(self):
200199
# Numbers with a misplaced grouping symbol should be rejected
201200
with pytest.raises(numbers.NumberFormatError) as info:
@@ -221,8 +220,19 @@ def test_parse_decimal_strict_mode(self):
221220
assert str(numbers.parse_decimal('1.001', locale='de', strict=True)) == '1001'
222221
# Trailing zeroes should be accepted
223222
assert str(numbers.parse_decimal('3.00', locale='en_US', strict=True)) == '3.00'
223+
# Numbers with a grouping symbol and no trailing zeroes should be accepted
224+
assert str(numbers.parse_decimal('3,400.6', locale='en_US', strict=True)) == '3400.6'
225+
# Numbers with a grouping symbol and trailing zeroes (not all zeroes after decimal) should be accepted
226+
assert str(numbers.parse_decimal('3,400.60', locale='en_US', strict=True)) == '3400.60'
227+
# Numbers with a grouping symbol and trailing zeroes (all zeroes after decimal) should be accepted
228+
assert str(numbers.parse_decimal('3,400.00', locale='en_US', strict=True)) == '3400.00'
229+
assert str(numbers.parse_decimal('3,400.0000', locale='en_US', strict=True)) == '3400.0000'
230+
# Numbers with a grouping symbol and no decimal part should be accepted
231+
assert str(numbers.parse_decimal('3,800', locale='en_US', strict=True)) == '3800'
224232
# Numbers without any grouping symbol should be accepted
225233
assert str(numbers.parse_decimal('2000.1', locale='en_US', strict=True)) == '2000.1'
234+
# Numbers without any grouping symbol and no decimal should be accepted
235+
assert str(numbers.parse_decimal('2580', locale='en_US', strict=True)) == '2580'
226236
# High precision numbers should be accepted
227237
assert str(numbers.parse_decimal('5,000001', locale='fr', strict=True)) == '5.000001'
228238

0 commit comments

Comments
 (0)