From 29482e9624209dafb4c75dea0f3cce03484640f9 Mon Sep 17 00:00:00 2001 From: kokotatan <199421981+kokotatan@users.noreply.github.com> Date: Sat, 3 Oct 2026 20:21:44 +0900 Subject: [PATCH] Normalize scientific mantissas after rounding carries --- babel/numbers.py | 12 ++++++++++++ tests/test_numbers.py | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+) diff --git a/babel/numbers.py b/babel/numbers.py index b782521e5..fb195fa39 100644 --- a/babel/numbers.py +++ b/babel/numbers.py @@ -1519,6 +1519,18 @@ def apply( # Render scientific notation. if self.exp_prec: + if value.is_finite(): + rounded = value.quantize(get_decimal_quantum(frac_prec[1])) + # Rounding can add a leading digit to the mantissa. Shift it + # back to the pattern's required width and adjust the exponent. + if rounded.adjusted() > value.adjusted(): + value = rounded.scaleb(-1) + if exp_sign == get_minus_sign_symbol(locale, numbering_system=numbering_system): + exp -= 1 + if exp == 0: + exp_sign = get_plus_sign_symbol(locale, numbering_system=numbering_system) if self.exp_plus else '' + else: + exp += 1 number = ''.join([ self._quantize_value(value, locale, frac_prec, group_separator, numbering_system=numbering_system), get_exponential_symbol(locale, numbering_system=numbering_system), diff --git a/tests/test_numbers.py b/tests/test_numbers.py index d20a18315..989e9fdb7 100644 --- a/tests/test_numbers.py +++ b/tests/test_numbers.py @@ -485,6 +485,47 @@ def test_format_scientific(): assert numbers.format_scientific(0.2, locale="ar_EG", numbering_system="default") == '2أس\u061c-1' +@pytest.mark.parametrize(('value', 'pattern', 'expected'), [ + ('9.99', '0.0E0', '1.0E1'), + ('0.999', '0.0E0', '1.0E0'), + ('0.0999', '0.0E0', '1.0E-1'), + ('9999', '0.0E0', '1.0E4'), + ('-9.99', '0.0E0', '-1.0E1'), + ('9.99', '0.0E+00', '1.0E+01'), + ('0.999', '0.0E+00', '1.0E+00'), + ('99.99', '00.0E0', '10.0E1'), + ('9.94', '0.0E0', '9.9E0'), + ('0', '0.0E0', '0.0E0'), + ('Infinity', '0.0E0', '\u221eE0'), +]) +def test_format_scientific_rounding_carry(value, pattern, expected): + assert numbers.format_scientific(decimal.Decimal(value), pattern, locale='en_US') == expected + + +@pytest.mark.parametrize(('rounding', 'expected'), [ + (decimal.ROUND_UP, '1.0E1'), + (decimal.ROUND_DOWN, '9.9E0'), +]) +def test_format_scientific_rounding_carry_context(rounding, expected): + with decimal.localcontext(rounding=rounding): + assert numbers.format_scientific(decimal.Decimal('9.99'), '0.0E0', locale='en_US') == expected + + +def test_format_scientific_rounding_carry_numbering_system(): + assert numbers.format_scientific( + decimal.Decimal('0.0999'), '0.0E0', locale='ar_EG', numbering_system='default', + ) == '1\u066b0\u0623\u0633\u061c-1' + assert numbers.format_scientific( + decimal.Decimal('0.999'), '0.0E+0', locale='ar_EG', numbering_system='default', + ) == '1\u066b0\u0623\u0633\u061c+0' + + +def test_format_scientific_rounding_carry_without_quantization(): + assert numbers.format_scientific( + decimal.Decimal('9.99'), '0.0E0', locale='en_US', decimal_quantization=False, + ) == '9.99E0' + + def test_default_scientific_format(): """Check the scientific format method auto-correct the rendering pattern in case of a missing fractional part.