Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I locale-format a python Decimal and preserve its precision?

Tags:

python

decimal

How can I format a Decimal using a locale?

To describe by examples, I'm trying to define a function f such that in the US English locale:

  • f(Decimal('5000.00')) == '5,000.00'

  • f(Decimal('1234567.000000')) == '1,234,567.000000'

Some things that don't work:

  • f = str doesn't use locale; f(Decimal('5000.00')) == '5000.00'

  • f = lambda d: locale.format('%f', d) doesn't preserve the decimal precision; f(Decimal('5000.00')) == '5000.000000'

  • f = lambda d: locale.format('%.2f', d) uses a fixed precision, which isn't what I'm after; f(Decimal('1234567.000000')) == '1234567.00'

like image 966
Chris Martin Avatar asked Aug 10 '15 02:08

Chris Martin


People also ask

How do you format decimals in Python?

To format decimals, we will use str. format(number) where a string is '{0:. 3g}' and it will format string with a number. Also, it will display the number with 1 number before the decimal and up to 2 numbers after the decimal.

How do you format a float to two decimal places in Python?

Use str. format() with “{:. 2f}” as string and float as a number to display 2 decimal places in Python. Call print and it will display the float with 2 decimal places in the console.


2 Answers

Reading through the source for the decimal module, Decimal.__format__ provides full PEP 3101 support, and all you have to do is select the correct presentation type. In this case, you want the :n type. According PEP 3101 spec, this :n has the following properties:

'n' - Number. This is the same as 'g', except that it uses the current locale setting to insert the appropriate number separator characters.

This is simpler than other answers, and avoids the float precision issue in my original answer (preserved below):

>>> import locale
>>> from decimal import Decimal
>>> 
>>> def f(d):
...     return '{0:n}'.format(d)
... 
>>> 
>>> locale.setlocale(locale.LC_ALL, 'en_us')
'en_us'
>>> print f(Decimal('5000.00'))
5,000.00
>>> print f(Decimal('1234567.000000'))
1,234,567.000000
>>> print f(Decimal('123456700000000.123'))
123,456,700,000,000.123
>>> locale.setlocale(locale.LC_ALL, 'no_no')
'no_no'
>>> print f(Decimal('5000.00'))
5.000,00
>>> print f(Decimal('1234567.000000'))
1.234.567,000000
>>> print f(Decimal('123456700000000.123'))
123.456.700.000.000,123

Original, wrong answer

You can just tell the format string to use as much precision as is included in the decimal itself and use the locale formatter:

def locale_format(d):
    return locale.format('%%0.%df' % (-d.as_tuple().exponent), d, grouping=True)

Note that works if you've got a decimal which corresponds to a real number, but doesn't work correctly if the decimal is NaN or +Inf or something like that. If those are possibilities in your input, you'd need to account for them in the format method.

>>> locale.setlocale(locale.LC_ALL, 'en_US')
'en_US'
>>> locale_format(Decimal('1234567.000000'))
'1,234,567.000000'
>>> locale_format(Decimal('5000.00'))
'5,000.00'
>>> locale.setlocale(locale.LC_ALL, 'no_no')
'no_no'
>>> locale_format(Decimal('1234567.000000'))
'1.234.567,000000'
>>> locale_format(Decimal('5000.00'))
'5.000,00'
>>> locale_format(Decimal('NaN'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in locale_format
TypeError: bad operand type for unary -: 'str'
like image 62
RecursivelyIronic Avatar answered Oct 06 '22 01:10

RecursivelyIronic


A modern Python approach with Python 3.5+ would be:

>>> import locale
>>> locale.setlocale(locale.LC_NUMERIC, "de_DE")
>>> f'{Decimal("5000.00"):n}'
'5.000,00'
>>> locale.setlocale(locale.LC_NUMERIC, "en_US")
'en_US'
>>> f'{Decimal("5000.00"):n}'
'5,000.00'

F-strings support advanced formatting. Hence, there is no need for extra f function. Babel for Python will give you a bit more convenience:

>>> from babel import numbers
>>> numbers.format_decimal(.2345, locale='en_US')
'0.234'
>>> numbers.format_decimal(.2345, locale='de_DE')
'0,234'
 
like image 26
oz123 Avatar answered Oct 05 '22 23:10

oz123