How do I convert a currency string to a floating point number in Python?
Asked Answered
C

10

65

I have some strings representing numbers with specific currency format, for example:

money="$6,150,593.22"

I want to convert this string into the number

6150593.22

What is the best way to achieve this?

Campeche answered 7/12, 2011 at 20:16 Comment(1)
Maybe code.google.com/p/python-money ?Windshield
G
89

Try this:

from re import sub
from decimal import Decimal

money = '$6,150,593.22'
value = Decimal(sub(r'[^\d.]', '', money))

This has some advantages since it uses Decimal instead of float (which is better for representing currency) and it also avoids any locale issues by not hard-coding a specific currency symbol.

Gonfalonier answered 7/12, 2011 at 20:28 Comment(3)
value = Decimal(sub(r'[^\d\-.]', '', money)) to preserve minus sign on negative values.Wallenstein
Please note that not all localizations use period as decimal separator and thus this is a gross simplification that will cause problems with a global audience.Tuscarora
'[^(\d,.)]' works for more localesEssie
E
18

If your locale is set properly you can use locale.atof, but you will still need to strip off the '$' manually:

>>> import locale
>>> locale.setlocale(locale.LC_ALL, 'en_US.UTF8')
'en_US.UTF8'
>>> money = "$6,150,593.22"
>>> locale.atof(money.strip("$"))
6150593.2199999997
Emmet answered 7/12, 2011 at 20:24 Comment(3)
+1 for locale.atof, but for financial applications float is obviously not the best choice.Agamete
it works with en_US localization. But for example. es_MX one gives a invalid literal for float(): 6,150,593.22 error...Campeche
@JavierNovoaC. this solution only works for en_US localeEssie
L
10

I found the babel package very helpful to work around

It makes it easy to parse a number in a localized rendition:

>>> babel.numbers.parse_decimal('1,024.64', locale='en')                                                                                                                           
Decimal('1024.64')
>>> babel.numbers.parse_decimal('1.024,64', locale='de')
Decimal('1024.64')
>>>

You can use babel.numbers.get_currency_symbol('USD') to strip pre/suffixes without hardcoding them.

Hth, dtk

Luann answered 23/11, 2018 at 16:36 Comment(0)
C
9

For a solution without hardcoding the currency position or symbol:

raw_price = "17,30 €"
import locale
locale.setlocale(locale.LC_ALL, 'fr_FR.UTF8')
conv = locale.localeconv()
raw_numbers = raw_price.strip(conv['currency_symbol'])
amount = locale.atof(raw_numbers)
Corpora answered 25/9, 2017 at 17:47 Comment(1)
conv['currency_symbol'].decode('utf-8') fails for me (" 'str' object has no attribute 'decode'), but this works without the decode.Euthanasia
R
6

Expanding to include negative numbers in parentheses:

In [1]: import locale, string

In [2]: from decimal import Decimal

In [3]: n = ['$1,234.56','-$1,234.56','($1,234.56)', '$ -1,234.56']

In [4]: tbl = string.maketrans('(','-')

In [5]: %timeit -n10000 [locale.atof( x.translate(tbl, '$)')) for x in n]
10000 loops, best of 3: 31.9 æs per loop

In [6]: %timeit -n10000 [Decimal( x.translate(tbl, '$,)')) for x in n]
10000 loops, best of 3: 21 æs per loop

In [7]: %timeit -n10000 [float( x.replace('(','-').translate(None, '$,)')) for x in n]
10000 loops, best of 3: 3.49 æs per loop

In [8]: %timeit -n10000 [float( x.translate(tbl, '$,)')) for x in n]
10000 loops, best of 3: 2.19 æs per loop

Note that commas must be stripped from float()/Decimal(). Either replace() or translate() w/ a translation table can be used to convert the opening ( to -, translate is slightly faster. float() is fastest by 10-15x, but lacks precision and could present locale issues. Decimal() has precision and is 50% faster than locale.atof(), but also has locale issues. locale.atof() is the slowest, but most general.

Edit: new str.translate API (characters mapped to None moved from str.translate function to the translation table)

In [1]: import locale, string
        from decimal import Decimal

        locale.setlocale(locale.LC_ALL, '')

        n = ['$1,234.56','-$1,234.56','($1,234.56)', '$ -1,234.56']

In [2]: tbl = str.maketrans('(', '-', '$)')
        %timeit -n10000 [locale.atof( x.translate(tbl)) for x in n]
18 µs ± 296 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [3]: tbl2 = str.maketrans('(', '-', '$,)')
        %timeit -n10000 [Decimal( x.translate(tbl2)) for x in n]
3.77 µs ± 50.8 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [4]: %timeit -n10000 [float( x.translate(tbl2)) for x in n]
3.13 µs ± 66.3 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)

In [5]: tbl3 = str.maketrans('', '', '$,)')
        %timeit -n10000 [float( x.replace('(','-').translate(tbl3)) for x in n]
3.51 µs ± 84.8 ns per loop (mean ± std. dev. of 7 runs, 10000 loops each)
Rawls answered 16/10, 2014 at 19:28 Comment(0)
E
3

Expanding on @Andrew Clark answer

For other locales different than en_US:

>>> import re
>>> import locale
>>> locale.setlocale(locale.LC_NUMERIC, 'pt_BR.UTF8') # this is for atof()
'pt_BR.UTF8'
>>> locale.setlocale(locale.LC_MONETARY, 'pt_BR.UTF8') # this is for currency()
'pt_BR.UTF8'
>>> curr = locale.currency(6150593.22, grouping = True)
>>> curr
'R$ 6.150.593,22'
>>> re.sub('[^(\d,.)]', '', curr)
'15,00'
>>> locale.atof(re.sub('[^(\d,.)]', '', curr))
6150593.22
>>> 6150593.22 == locale.atof(re.sub('[^(\d,.)]', '', locale.currency(6150593.22, grouping = True)))
True

The obligatory reminder: The appropriate Python type for currency is Decimal, not floating points.

Essie answered 16/7, 2022 at 7:17 Comment(0)
V
0

I made this function a few years ago to solve the same problem.

def money(number):
    number = number.strip('$')
    try:
        [num,dec]=number.rsplit('.')
        dec = int(dec)
        aside = str(dec)
        x = int('1'+'0'*len(aside))
        price = float(dec)/x
        num = num.replace(',','')
        num = int(num)
        price = num + price
    except:
        price = int(number)
    return price
Venezuela answered 1/7, 2016 at 18:2 Comment(1)
never, ever use a bare except like this, you'll prevent the use of CTRL-C amongst other things.Arrow
I
0

this function has convert turkish price format to decimal number.

money = '1.234,75'
def make_decimal(string):
    result = 0
    if string:
        [num, dec] = string.rsplit(',')
        result += int(num.replace('.', ''))
        result += (int(dec) / 100)
    return result
print(make_decimal(money))
1234.75
Inoperative answered 27/2, 2020 at 13:35 Comment(1)
Thanks for you answer, but it does not work for this case: make_decimal("942,695") # returns 948.95. You can have a loot at my solution which works for Turkish price format as well.Satirist
P
0

Simplest way I found, without hard-coding on messing with currency detection, also uses the Decimal type which avoids issues with the float type:

>>> from decimal import Decimal
>>> money="$6,150,593.22"
>>> amount = Decimal("".join(d for d in money if d.isdigit() or d == '.'))
>>> amount
Decimal('6150593.22')

credit: https://www.reddit.com/r/learnpython/comments/2248mp/how_to_format_currency_without_currency_sign/cgjd1o4?utm_source=share&utm_medium=web2x
Prejudge answered 10/7, 2020 at 17:58 Comment(0)
S
0

I'll provide my solution, hoping it would help someone who face problems with not just , but also ..

def process_currency_adaptive(currency_string: str, decimal_sep_char: str) -> float:
    """
    Converts the currency string to common float format:
        Format: 
            ######.### 
        Example: 
            6150593.22
    """
    # Get rid of currency symbol
    currency_symbols = ["$", "€", "£", "₺"]
    
    # Replace any occurrence of currency symbol with empty string
    for symbol in currency_symbols:
        currency_string = currency_string.replace(symbol, "")
    
    
    if decimal_sep_char == ",":
        triple_sep_char = "."
    elif decimal_sep_char == ".":
        triple_sep_char = ","
    else:
        raise ValueError("Invalid decimal separator character: {}".format(decimal_sep_char))

    # Get rid of the triple separator
    currency_string = currency_string.replace(triple_sep_char, "")
    
    # There should be only one decimal_sep_char.
    if currency_string.count(decimal_sep_char) != 1:
        print("Error: Invalid currency format with value: {}".format(currency_string))
        raise ValueError
    
    return float(currency_string.replace(decimal_sep_char, "."))

# test process_currency
print(process_currency_adaptive("942,695", decimal_sep_char=","))  # 942.695
print(process_currency_adaptive("$6,150,593.22", decimal_sep_char="."))  # 6150593.22        
Satirist answered 13/3, 2022 at 19:48 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.