Skip to content

Commit

Permalink
feat: allow precision specification
Browse files Browse the repository at this point in the history
  • Loading branch information
olunusib committed Jan 20, 2024
1 parent 40e60a1 commit 48c2faa
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 7 deletions.
54 changes: 47 additions & 7 deletions babel/numbers.py
Original file line number Diff line number Diff line change
Expand Up @@ -520,6 +520,7 @@ def format_decimal(
format: str | NumberPattern | None = None,
locale: Locale | str | None = LC_NUMERIC,
decimal_quantization: bool = True,
precision: int | None = None,
group_separator: bool = True,
*,
numbering_system: Literal["default"] | str = "latn",
Expand Down Expand Up @@ -560,11 +561,19 @@ def format_decimal(
>>> format_decimal(12345.67, locale='en_US', group_separator=True)
u'12,345.67'
When you bypass locale truncation, you can specify precision:
>>> format_decimal(1.2346, locale='en_US', decimal_quantization=False, precision=2)
u'1.23'
>>> format_decimal(0.3, locale='en_US', decimal_quantization=False, precision=2)
u'0.30'
:param number: the number to format
:param format:
:param locale: the `Locale` object or locale identifier
:param decimal_quantization: Truncate and round high-precision numbers to
the format pattern. Defaults to `True`.
:param precision: Optionally specify precision when decimal_quantization is False.
:param group_separator: Boolean to switch group separator on/off in a locale's
number format.
:param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
Expand All @@ -576,7 +585,7 @@ def format_decimal(
format = locale.decimal_formats[format]
pattern = parse_pattern(format)
return pattern.apply(
number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator, numbering_system=numbering_system)
number, locale, decimal_quantization=decimal_quantization, precision=precision, group_separator=group_separator, numbering_system=numbering_system)


def format_compact_decimal(
Expand Down Expand Up @@ -674,6 +683,7 @@ def format_currency(
currency_digits: bool = True,
format_type: Literal["name", "standard", "accounting"] = "standard",
decimal_quantization: bool = True,
precision: int | None = None,
group_separator: bool = True,
*,
numbering_system: Literal["default"] | str = "latn",
Expand Down Expand Up @@ -755,6 +765,11 @@ def format_currency(
>>> format_currency(1099.9876, 'USD', locale='en_US', decimal_quantization=False)
u'$1,099.9876'
When you bypass locale truncation, you can specify precision:
>>> format_currency(1099.9876, 'USD', locale='en_US', decimal_quantization=False, precision=3)
u'$1,099.988'
:param number: the number to format
:param currency: the currency code
:param format: the format string to use
Expand All @@ -763,6 +778,7 @@ def format_currency(
:param format_type: the currency format type to use
:param decimal_quantization: Truncate and round high-precision numbers to
the format pattern. Defaults to `True`.
:param precision: Optionally specify precision when decimal_quantization is False.
:param group_separator: Boolean to switch group separator on/off in a locale's
number format.
:param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
Expand All @@ -772,7 +788,7 @@ def format_currency(
if format_type == 'name':
return _format_currency_long_name(number, currency, format=format,
locale=locale, currency_digits=currency_digits,
decimal_quantization=decimal_quantization, group_separator=group_separator,
decimal_quantization=decimal_quantization, precision=precision, group_separator=group_separator,
numbering_system=numbering_system)
locale = Locale.parse(locale)
if format:
Expand All @@ -785,7 +801,7 @@ def format_currency(

return pattern.apply(
number, locale, currency=currency, currency_digits=currency_digits,
decimal_quantization=decimal_quantization, group_separator=group_separator, numbering_system=numbering_system)
decimal_quantization=decimal_quantization, precision=precision, group_separator=group_separator, numbering_system=numbering_system)


def _format_currency_long_name(
Expand All @@ -796,6 +812,7 @@ def _format_currency_long_name(
currency_digits: bool = True,
format_type: Literal["name", "standard", "accounting"] = "standard",
decimal_quantization: bool = True,
precision: int | None = None,
group_separator: bool = True,
*,
numbering_system: Literal["default"] | str = "latn",
Expand Down Expand Up @@ -825,7 +842,7 @@ def _format_currency_long_name(

number_part = pattern.apply(
number, locale, currency=currency, currency_digits=currency_digits,
decimal_quantization=decimal_quantization, group_separator=group_separator, numbering_system=numbering_system)
decimal_quantization=decimal_quantization, precision=precision, group_separator=group_separator, numbering_system=numbering_system)

return unit_pattern.format(number_part, display_name)

Expand Down Expand Up @@ -887,6 +904,7 @@ def format_percent(
format: str | NumberPattern | None = None,
locale: Locale | str | None = LC_NUMERIC,
decimal_quantization: bool = True,
precision: int | None = None,
group_separator: bool = True,
*,
numbering_system: Literal["default"] | str = "latn",
Expand Down Expand Up @@ -922,11 +940,17 @@ def format_percent(
>>> format_percent(229291.1234, locale='pt_BR', group_separator=True)
u'22.929.112%'
When you bypass locale truncation, you can specify precision:
>>> format_percent(0.0111, locale='en_US', decimal_quantization=False, precision=3)
u'1.110%'
:param number: the percent number to format
:param format:
:param locale: the `Locale` object or locale identifier
:param decimal_quantization: Truncate and round high-precision numbers to
the format pattern. Defaults to `True`.
:param precision: Optionally specify precision when decimal_quantization is False.
:param group_separator: Boolean to switch group separator on/off in a locale's
number format.
:param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
Expand All @@ -938,7 +962,7 @@ def format_percent(
format = locale.percent_formats[None]
pattern = parse_pattern(format)
return pattern.apply(
number, locale, decimal_quantization=decimal_quantization, group_separator=group_separator,
number, locale, decimal_quantization=decimal_quantization, precision=precision, group_separator=group_separator,
numbering_system=numbering_system,
)

Expand All @@ -949,6 +973,7 @@ def format_scientific(
locale: Locale | str | None = LC_NUMERIC,
decimal_quantization: bool = True,
*,
precision: int | None = None,
numbering_system: Literal["default"] | str = "latn",
) -> str:
"""Return value formatted in scientific notation for a specific locale.
Expand All @@ -972,11 +997,17 @@ def format_scientific(
>>> format_scientific(1234.9876, u'#.##E0', locale='en_US', decimal_quantization=False)
u'1.2349876E3'
When you bypass locale truncation, you can specify precision:
>>> format_scientific(000.00100, locale='en_US', decimal_quantization=False, precision=3)
u'1.000E-3'
:param number: the number to format
:param format:
:param locale: the `Locale` object or locale identifier
:param decimal_quantization: Truncate and round high-precision numbers to
the format pattern. Defaults to `True`.
:param precision: Optionally specify precision when decimal_quantization is False.
:param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
The special value "default" will use the default numbering system of the locale.
:raise `UnsupportedNumberingSystemError`: If the numbering system is not supported by the locale.
Expand All @@ -986,7 +1017,7 @@ def format_scientific(
format = locale.scientific_formats[None]
pattern = parse_pattern(format)
return pattern.apply(
number, locale, decimal_quantization=decimal_quantization, numbering_system=numbering_system)
number, locale, decimal_quantization=decimal_quantization, precision=precision, numbering_system=numbering_system)


class NumberFormatError(ValueError):
Expand Down Expand Up @@ -1346,6 +1377,7 @@ def apply(
currency: str | None = None,
currency_digits: bool = True,
decimal_quantization: bool = True,
precision: int | None = None,
force_frac: tuple[int, int] | None = None,
group_separator: bool = True,
*,
Expand All @@ -1371,6 +1403,8 @@ def apply(
strictly matching the CLDR definition for
the locale.
:type decimal_quantization: bool
:param precision: Optionally specify precision when decimal_quantization is False.
:type precision: int|None
:param force_frac: DEPRECATED - a forced override for `self.frac_prec`
for a single formatting invocation.
:param numbering_system: The numbering system used for formatting number symbols. Defaults to "latn".
Expand Down Expand Up @@ -1407,14 +1441,20 @@ def apply(
else:
frac_prec = self.frac_prec

if decimal_quantization and precision is not None:
raise ValueError("To specify precision, decimal_quantization should be set to False.")

# Bump decimal precision to the natural precision of the number if it
# exceeds the one we're about to use. This adaptative precision is only
# triggered if the decimal quantization is disabled or if a scientific
# notation pattern has a missing mandatory fractional part (as in the
# default '#E0' pattern). This special case has been extensively
# discussed at https://github.com/python-babel/babel/pull/494#issuecomment-307649969 .
if not decimal_quantization or (self.exp_prec and frac_prec == (0, 0)):
frac_prec = (frac_prec[0], max([frac_prec[1], get_decimal_precision(value)]))
if not decimal_quantization and precision is not None:
frac_prec = (precision, precision)
else:
frac_prec = (frac_prec[0], max([frac_prec[1], get_decimal_precision(value)]))

# Render scientific notation.
if self.exp_prec:
Expand Down

0 comments on commit 48c2faa

Please sign in to comment.