160

I have a number of strings similar to Current Level: 13.4 db. and I would like to extract just the floating point number. I say floating and not decimal as it's sometimes whole. Can RegEx do this or is there a better way?

martineau
  • 119,623
  • 25
  • 170
  • 301
Ben Keating
  • 8,206
  • 9
  • 37
  • 37

7 Answers7

320

If your float is always expressed in decimal notation something like

>>> import re
>>> re.findall("\d+\.\d+", "Current Level: 13.4db.")
['13.4']

may suffice.

A more robust version would be:

>>> re.findall(r"[-+]?(?:\d*\.*\d+)", "Current Level: -13.2db or 14.2 or 3")
['-13.2', '14.2', '3']

If you want to validate user input, you could alternatively also check for a float by stepping to it directly:

user_input = "Current Level: 1e100 db"
for token in user_input.split():
    try:
        # if this succeeds, you have your (first) float
        print(float(token), "is a float")
    except ValueError:
        print(token, "is something else")

# => Would print ...
#
# Current is something else
# Level: is something else
# 1e+100 is a float
# db is something else
        
miku
  • 181,842
  • 47
  • 306
  • 310
86

You may like to try something like this which covers all the bases, including not relying on whitespace after the number:

>>> import re
>>> numeric_const_pattern = r"""
...     [-+]? # optional sign
...     (?:
...         (?: \d* \. \d+ ) # .1 .12 .123 etc 9.1 etc 98.1 etc
...         |
...         (?: \d+ \.? ) # 1. 12. 123. etc 1 12 123 etc
...     )
...     # followed by optional exponent part if desired
...     (?: [Ee] [+-]? \d+ ) ?
...     """
>>> rx = re.compile(numeric_const_pattern, re.VERBOSE)
>>> rx.findall(".1 .12 9.1 98.1 1. 12. 1 12")
['.1', '.12', '9.1', '98.1', '1.', '12.', '1', '12']
>>> rx.findall("-1 +1 2e9 +2E+09 -2e-9")
['-1', '+1', '2e9', '+2E+09', '-2e-9']
>>> rx.findall("current level: -2.03e+99db")
['-2.03e+99']
>>>

For easy copy-pasting:

numeric_const_pattern = '[-+]? (?: (?: \d* \. \d+ ) | (?: \d+ \.? ) )(?: [Ee] [+-]? \d+ ) ?'
rx = re.compile(numeric_const_pattern, re.VERBOSE)
rx.findall("Some example: Jr. it. was .23 between 2.3 and 42.31 seconds")
Joop
  • 3,706
  • 34
  • 55
John Machin
  • 81,303
  • 11
  • 141
  • 189
  • 5
    Very good! Finally I've found a really good pattern! – S.H Feb 13 '15 at 09:31
  • Yes, best pattern ever for numbers. Thanks a lot! – edison Aug 29 '17 at 19:25
  • 2
    Adding `(?:\+\s*|\-\s*)?` at the front would also allow for a space between the sign and the number. Even though I admit this is probably not very "standard" I have seen this pattern "floating around" in some files. – NOhs Dec 15 '17 at 12:49
  • 1
    You probably need an r in front of the pattern string in the very last snippet. – Mad Physicist Jan 14 '19 at 07:25
37

Python docs has an answer that covers +/-, and exponent notation

scanf() Token      Regular Expression
%e, %E, %f, %g     [-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?
%i                 [-+]?(0[xX][\dA-Fa-f]+|0[0-7]*|\d+)

This regular expression does not support international formats where a comma is used as the separator character between the whole and fractional part (3,14159). In that case, replace all \. with [.,] in the above float regex.

                        Regular Expression
International float     [-+]?(\d+([.,]\d*)?|[.,]\d+)([eE][-+]?\d+)?
IceArdor
  • 1,961
  • 19
  • 20
12
re.findall(r"[-+]?\d*\.?\d+|\d+", "Current Level: -13.2 db or 14.2 or 3")

as described above, works really well! One suggestion though:

re.findall(r"[-+]?\d*\.?\d+|[-+]?\d+", "Current Level: -13.2 db or 14.2 or 3 or -3")

will also return negative int values (like -3 in the end of this string)

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
Martin
  • 121
  • 1
  • 2
7

You can use the following regex to get integer and floating values from a string:

re.findall(r'[\d\.\d]+', 'hello -34 42 +34.478m 88 cricket -44.3')

['34', '42', '34.478', '88', '44.3']

Thanks Rex

Steve Westbrook
  • 1,698
  • 2
  • 20
  • 21
user3613331
  • 121
  • 1
  • 6
  • 3
    This regex will also find non-numeric combinations of periods and digits: `'.... 1.2.3.4 ..56..'` yields: `['....', '1.2.3.4', '..56..']` – scottbb Nov 06 '15 at 15:40
3

I think that you'll find interesting stuff in the following answer of mine that I did for a previous similar question:

https://stackoverflow.com/q/5929469/551449

In this answer, I proposed a pattern that allows a regex to catch any kind of number and since I have nothing else to add to it, I think it is fairly complete

Community
  • 1
  • 1
eyquem
  • 26,771
  • 7
  • 38
  • 46
2

Another approach that may be more readable is simple type conversion. I've added a replacement function to cover instances where people may enter European decimals:

>>> for possibility in "Current Level: -13.2 db or 14,2 or 3".split():
...     try:
...         str(float(possibility.replace(',', '.')))
...     except ValueError:
...         pass
'-13.2'
'14.2'
'3.0'

This has disadvantages too however. If someone types in "1,000", this will be converted to 1. Also, it assumes that people will be inputting with whitespace between words. This is not the case with other languages, such as Chinese.

Tim McNamara
  • 18,019
  • 4
  • 52
  • 83
  • "4x size AAA 1.5V batteries included" :-) – John Machin Jan 16 '11 at 02:48
  • Those terrible users! Always entering in silly data. TBH, I've intentionally kept this example demonstrative rather than robust. When I begun writing this response, @The MYYN only provided regular expressions in the accepted answer. I wanted to provide an example of another way to go about things. – Tim McNamara Jan 16 '11 at 02:51