Skip to content

Fix directional rounding (ROUND_CEILING/ROUND_FLOOR) for negative numbers#1286

Open
vineethsaivs wants to merge 1 commit into
python-babel:masterfrom
vineethsaivs:fix/round-ceiling-floor-negative
Open

Fix directional rounding (ROUND_CEILING/ROUND_FLOOR) for negative numbers#1286
vineethsaivs wants to merge 1 commit into
python-babel:masterfrom
vineethsaivs:fix/round-ceiling-floor-negative

Conversation

@vineethsaivs

Copy link
Copy Markdown

Overview

Fixes #1096.

ROUND_CEILING and ROUND_FLOOR produced the wrong result when formatting a
negative number:

from babel.numbers import decimal, format_decimal

with decimal.localcontext(decimal.Context(rounding=decimal.ROUND_CEILING)):
    print(format_decimal(decimal.Decimal("-100.75"), format="#", locale="en_US"))
# before: -101
# after:  -100   (ceil(-100.75) == -100)

Root cause

NumberPattern.apply() separates the sign from the value up front
(value = abs(value).normalize()), formats the magnitude, and renders the sign
separately through the pattern's affixes. Because the sign was already gone by
the time rounding happened, the directional rounding modes (ROUND_CEILING,
ROUND_FLOOR) were applied to the magnitude and therefore rounded toward the
wrong infinity for negative numbers. Effectively ROUND_CEILING behaved like
ROUND_UP and ROUND_FLOOR like ROUND_DOWN for negatives.

This affected every rounding site that operates on the magnitude:
_quantize_value (plain and fractional patterns, e.g. # and #,##0.00, plus
the scientific-notation mantissa) and _format_significant (significant-digits
patterns such as @@).

Fix

Round using the value's original sign in both sites, then format the magnitude
(the sign is still rendered via the affixes, so output formatting is otherwise
unchanged). The sign-symmetric modes (ROUND_HALF_*, ROUND_UP, ROUND_DOWN,
ROUND_05UP) and all positive-number formatting are unaffected; verified
against decimal.Decimal.quantize on the signed value across every rounding
mode.

Tests

Added test_format_decimal_negative_directional_rounding (parametrized over the
rounding modes) and test_format_decimal_negative_directional_rounding_significant.
Both fail on master for the directional modes and pass with this change. The
full tests/test_numbers.py suite passes (137 tests).

ROUND_CEILING and ROUND_FLOOR produced the wrong result when formatting a
negative number. NumberPattern.apply() strips the sign before any rounding
(it formats the magnitude and renders the sign via the pattern affixes), so
the directional rounding modes rounded the magnitude and ended up rounding
toward the wrong infinity. For example, with ROUND_CEILING, format_decimal
of -100.75 returned -101 instead of -100 (ceil(-100.75) == -100).

Round using the value's original sign in both rounding sites,
_quantize_value and _format_significant, then format the magnitude. The
sign-symmetric modes (ROUND_HALF_*, ROUND_UP, ROUND_DOWN, ROUND_05UP) and
all positive-number formatting are unaffected.

Fixes python-babel#1096.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Wrong rounding when using decimal.ROUND_CEILING

1 participant