Daniel Goldberg
Daniel Goldberg

Reputation: 20558

How do I check if a string represents a number (float or int)?

How do I check if a string represents a numeric value in Python?

def is_number(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

The above works, but it seems clunky.


If what you are testing comes from user input, it is still a string even if it represents an int or a float. See How can I read inputs as numbers? for converting the input, and Asking the user for input until they give a valid response for ensuring that the input represents an int or float (or other requirements) before proceeding.

Upvotes: 2010

Views: 1794663

Answers (30)

dogdog
dogdog

Reputation: 133

def is_number(x:str):
    x = x.replace(".", "", 1)
    if x.startswith("-"):
        x = x[1:]
    return x.isdigit()

if __name__ == '__main__':
    for si in ["123.456", "-123.456", "123", "-123", "--123", "a123", "123a"]:
        print(si, is_number(si))

Upvotes: 0

Blue Phoenix
Blue Phoenix

Reputation: 151

My short answer is: may be duplicated one, sorry for that ... def is_float(s): if s.isdigit(): return False try: float(s) return True except Exception as e: return False

Upvotes: 0

user2489252
user2489252

Reputation:

TL;DR The best solution is s.replace('.','',1).isdigit()

I did some benchmarks comparing the different approaches

def is_number_tryexcept(s):
    """ Returns True if string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False
       
import re    
def is_number_regex(s):
    """ Returns True if string is a number. """
    if re.match("^\d+?\.\d+?$", s) is None:
        return s.isdigit()
    return True


def is_number_repl_isdigit(s):
    """ Returns True if string is a number. """
    return s.replace('.','',1).isdigit()

If the string is not a number, the except-block is quite slow. But more importantly, the try-except method is the only approach that handles scientific notations correctly.

funcs = [
          is_number_tryexcept, 
          is_number_regex,
          is_number_repl_isdigit
          ]

a_float = '.1234'

print('Float notation ".1234" is not supported by:')
for f in funcs:
    if not f(a_float):
        print('\t -', f.__name__)

Float notation ".1234" is not supported by:

  • is_number_regex

    scientific1 = '1.000000e+50' scientific2 = '1e50'

    print('Scientific notation "1.000000e+50" is not supported by:') for f in funcs: if not f(scientific1): print('\t -', f.name)

    print('Scientific notation "1e50" is not supported by:') for f in funcs: if not f(scientific2): print('\t -', f.name)

Scientific notation "1.000000e+50" is not supported by:

  • is_number_regex
  • is_number_repl_isdigit
    Scientific notation "1e50" is not supported by:
  • is_number_regex
  • is_number_repl_isdigit

EDIT: The benchmark results

import timeit

test_cases = ['1.12345', '1.12.345', 'abc12345', '12345']
times_n = {f.__name__:[] for f in funcs}

for t in test_cases:
    for f in funcs:
        f = f.__name__
        times_n[f].append(min(timeit.Timer('%s(t)' %f, 
                      'from __main__ import %s, t' %f)
                              .repeat(repeat=3, number=1000000)))

where the following functions were tested

from re import match as re_match
from re import compile as re_compile

def is_number_tryexcept(s):
    """ Returns True if string is a number. """
    try:
        float(s)
        return True
    except ValueError:
        return False

def is_number_regex(s):
    """ Returns True if string is a number. """
    if re_match("^\d+?\.\d+?$", s) is None:
        return s.isdigit()
    return True


comp = re_compile("^\d+?\.\d+?$")    

def compiled_regex(s):
    """ Returns True if string is a number. """
    if comp.match(s) is None:
        return s.isdigit()
    return True


def is_number_repl_isdigit(s):
    """ Returns True if string is a number. """
    return s.replace('.','',1).isdigit()

enter image description here

Upvotes: 341

Amit
Amit

Reputation: 2108

There are already good answers in this post. I wanted to give a slightly different perspective.

Instead of searching for a digit, number or float we could do a negative search for an alphabet. i.e. we could ask the program to look if it is not alphabet.

## Check whether it is not alpha rather than checking if it is digit
print(not "-1.2345".isalpha())
print(not "-1.2345e-10".isalpha())

It will work well if you are sure that your string is a well formed number (Condition 1 and Condition 2 below). However it will fail if the string is not a well formed number by mistake. In such a case it will return a number match even if the string was not a valid number. To take care of this situation, there are many rule based methods must be there. However at this moment, regex comes to my mind. Below are three cases. Please note regex can be much better since I am not a regex expert. Below there are two lists: one for valid numbers and one for invalid numbers. Valid numbers must be picked up while the invalid numbers must not be.

== Condition 1: String is guranteed to be a valid number but 'inf' is not picked ==

Valid_Numbers = ["1","-1","+1","0.0",".1","1.2345","-1.2345","+1.2345","1.2345e10","1.2345e-10","-1.2345e10","-1.2345E10","-inf"]
Invalid_Numbers = ["1.1.1","++1","--1","-1-1","1.23e10e5","--inf"]

################################ Condition 1: Valid number excludes 'inf' ####################################

Case_1_Positive_Result = list(map(lambda x: not x.isalpha(),Valid_Numbers))
print("The below must all be True")
print(Case_1_Positive_Result)

## This check assumes a valid number. So it fails for the negative cases and wrongly detects string as number
Case_1_Negative_Result = list(map(lambda x: not x.isalpha(),Invalid_Numbers))
print("The below must all be False")
print(Case_1_Negative_Result)
The below must all be True
[True, True, True, True, True, True, True, True, True, True, True, True, True]
The below must all be False
[True, True, True, True, True, True]

== Condition 2: String is guranteed to be a valid number and 'inf' is picked ==

################################ Condition 2: Valid number includes 'inf'  ###################################
Case_2_Positive_Result = list(map(lambda x: x=="inf" or not x.isalpha(),Valid_Numbers+["inf"]))
print("The below must all be True")
print(Case_2_Positive_Result)

## This check assumes a valid number. So it fails for the negative cases and wrongly detects string as number
Case_2_Negative_Result = list(map(lambda x: x=="inf" or not x.isalpha(),Invalid_Numbers+["++inf"]))
print("The below must all be False")
print(Case_2_Negative_Result)
The below must all be True
[True, True, True, True, True, True, True, True, True, True, True, True, True, True]
The below must all be False
[True, True, True, True, True, True, True]

== Condition 3: String is not guranteed to be a valid number ==

import re
CompiledPattern = re.compile(r"([+-]?(inf){1}$)|([+-]?[0-9]*\.?[0-9]*$)|([+-]?[0-9]*\.?[0-9]*[eE]{1}[+-]?[0-9]*$)")
Case_3_Positive_Result = list(map(lambda x: True if CompiledPattern.match(x) else False,Valid_Numbers+["inf"]))
print("The below must all be True")
print(Case_3_Positive_Result)

## This check assumes a valid number. So it fails for the negative cases and wrongly detects string as number
Case_3_Negative_Result = list(map(lambda x: True if CompiledPattern.match(x) else False,Invalid_Numbers+["++inf"]))
print("The below must all be False")
print(Case_3_Negative_Result)
The below must all be True
[True, True, True, True, True, True, True, True, True, True, True, True, True, True]
The below must all be False
[False, False, False, False, False, False, False]

Upvotes: 1

Zoomulator
Zoomulator

Reputation: 21230

For non-negative (unsigned) integers only, use isdigit():

>>> a = "03523"
>>> a.isdigit()
True
>>> b = "963spam"
>>> b.isdigit()
False

Documentation for isdigit(): Python2, Python3

For Python 2 Unicode strings: isnumeric().

Upvotes: 1798

Rafael Braga
Rafael Braga

Reputation: 195

One fast and simple option is to check the data type:

def is_number(value):
    return type(value) in [int, float]

Or if you want to test if the values os a string are numeric:

def isNumber (value):
    return True if type(value) in [int, float] else str(value).replace('.','',1).isdigit()

tests:

>>> isNumber(1)
True

>>> isNumber(1/3)
True

>>> isNumber(1.3)
True

>>> isNumber('1.3')
True

>>> isNumber('s1.3')
False

Upvotes: 0

Gru
Gru

Reputation: 965

I know I'm late to the party, but figured out a solution which wasn't here: This solution follows the EAFP principle in Python

def get_number_from_string(value):
    try:
        int_value = int(value)
        return int_value

    except ValueError:
        return float(value)

Explanation:

If the value in the string is a float and I first try to parse it as an int, it will throw a ValueError. So, I catch that error and parse the value as float and return.

Upvotes: 2

Micka
Micka

Reputation: 1834

For my very simple and very common use-case: is this human written string with keyboard a number?

I read through most answers, and ended up with:

def isNumeric(string):
    result = True
    try:
        x = float(string)
       result = (x == x) and (x - 1 != x)
    except ValueError:
        result = False
    return result

It will return False for (+-)NaN and (+-)inf.

You can check it out here: https://trinket.io/python/ce32c0e54e

Upvotes: 0

DJ Swarm
DJ Swarm

Reputation: 19

Sorry for the Zombie thread post - just wanted to round out the code for completeness...

# is_number() function - Uses re = regex library
# Should handle all normal and complex numbers
# Does not accept trailing spaces. 
# Note: accepts both engineering "j" and math "i" but only the imaginary part "+bi" of a complex number a+bi
# Also accepts inf or NaN
# Thanks to the earlier responders for most the regex fu

import re

ISNUM_REGEXP = re.compile(r'^[-+]?([0-9]+|[0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?[ij]?$')

def is_number(str):
#change order if you have a lot of NaN or inf to parse
    if ISNUM_REGEXP.match(str) or str == "NaN" or str == "inf": 
        return True 
    else:
        return False
# A couple test numbers
# +42.42e-42j
# -42.42E+42i

print('Is it a number?', is_number(input('Gimme any number: ')))

Gimme any number: +42.42e-42j

Is it a number? True

Upvotes: 0

Moinuddin Quadri
Moinuddin Quadri

Reputation: 48110

This answer provides step by step guide having function with examples to find the string is:

  • Positive integer
  • Positive/negative - integer/float
  • How to discard "NaN" (not a number) strings while checking for number?

Check if string is positive integer

You may use str.isdigit() to check whether given string is positive integer.

Sample Results:

# For digit
>>> '1'.isdigit()
True
>>> '1'.isalpha()
False

Check for string as positive/negative - integer/float

str.isdigit() returns False if the string is a negative number or a float number. For example:

# returns `False` for float
>>> '123.3'.isdigit()
False
# returns `False` for negative number
>>> '-123'.isdigit()
False

If you want to also check for the negative integers and float, then you may write a custom function to check for it as:

def is_number(n):
    try:
        float(n)   # Type-casting the string to `float`.
                   # If string is not a valid `float`, 
                   # it'll raise `ValueError` exception
    except ValueError:
        return False
    return True

Sample Run:

>>> is_number('123')    # positive integer number
True

>>> is_number('123.4')  # positive float number
True
 
>>> is_number('-123')   # negative integer number
True

>>> is_number('-123.4') # negative `float` number
True

>>> is_number('abc')    # `False` for "some random" string
False

Discard "NaN" (not a number) strings while checking for number

The above functions will return True for the "NAN" (Not a number) string because for Python it is valid float representing it is not a number. For example:

>>> is_number('NaN')
True

In order to check whether the number is "NaN", you may use math.isnan() as:

>>> import math
>>> nan_num = float('nan')

>>> math.isnan(nan_num)
True

Or if you don't want to import additional library to check this, then you may simply check it via comparing it with itself using ==. Python returns False when nan float is compared with itself. For example:

# `nan_num` variable is taken from above example
>>> nan_num == nan_num
False

Hence, above function is_number can be updated to return False for "NaN" as:

def is_number(n):
    is_number = True
    try:
        num = float(n)
        # check for "nan" floats
        is_number = num == num   # or use `math.isnan(num)`
    except ValueError:
        is_number = False
    return is_number

Sample Run:

>>> is_number('Nan')   # not a number "Nan" string
False

>>> is_number('nan')   # not a number string "nan" with all lower cased
False

>>> is_number('123')   # positive integer
True

>>> is_number('-123')  # negative integer
True

>>> is_number('-1.12') # negative `float`
True

>>> is_number('abc')   # "some random" string
False

PS: Each operation for each check depending on the type of number comes with additional overhead. Choose the version of is_number function which fits your requirement.

Upvotes: 36

Siddharth Satpathy
Siddharth Satpathy

Reputation: 3043

In a most general case for a float, one would like to take care of integers and decimals. Let's take the string "1.1" as an example.

I would try one of the following:

1.> isnumeric()

word = "1.1"

"".join(word.split(".")).isnumeric()
>>> True

2.> isdigit()

word = "1.1"

"".join(word.split(".")).isdigit()
>>> True

3.> isdecimal()

word = "1.1"

"".join(word.split(".")).isdecimal()
>>> True

Speed:

► All the aforementioned methods have similar speeds.

%timeit "".join(word.split(".")).isnumeric()
>>> 257 ns ± 12 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit "".join(word.split(".")).isdigit()
>>> 252 ns ± 11 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit "".join(word.split(".")).isdecimal()
>>> 244 ns ± 7.17 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Upvotes: 10

zardosht
zardosht

Reputation: 3591

str.isnumeric()

Return True if all characters in the string are numeric characters, and there is at least one character, False otherwise. Numeric characters include digit characters, and all characters that have the Unicode numeric value property, e.g. U+2155, VULGAR FRACTION ONE FIFTH. Formally, numeric characters are those with the property value Numeric_Type=Digit, Numeric_Type=Decimal or Numeric_Type=Numeric.

str.isdecimal()

Return True if all characters in the string are decimal characters and there is at least one character, False otherwise. Decimal characters are those that can be used to form numbers in base 10, e.g. U+0660, ARABIC-INDIC DIGIT ZERO. Formally a decimal character is a character in the Unicode General Category “Nd”.

Both available for string types from Python 3.0.

Upvotes: 7

Amir Saniyan
Amir Saniyan

Reputation: 13759

def is_float(s):
    if s is None:
        return False

    if len(s) == 0:
        return False

    digits_count = 0
    dots_count = 0
    signs_count = 0

    for c in s:
        if '0' <= c <= '9':
            digits_count += 1
        elif c == '.':
            dots_count += 1
        elif c == '-' or c == '+':
            signs_count += 1
        else:
            return False

    if digits_count == 0:
        return False

    if dots_count > 1:
        return False

    if signs_count > 1:
        return False

    return True

Upvotes: 1

I think your solution is fine, but there is a correct regexp implementation.

There does seem to be a lot of regexp hate towards these answers which I think is unjustified, regexps can be reasonably clean and correct and fast. It really depends on what you're trying to do. The original question was how can you "check if a string can be represented as a number (float)" (as per your title). Presumably you would want to use the numeric/float value once you've checked that it's valid, in which case your try/except makes a lot of sense. But if, for some reason, you just want to validate that a string is a number then a regex also works fine, but it's hard to get correct. I think most of the regex answers so far, for example, do not properly parse strings without an integer part (such as ".7") which is a float as far as python is concerned. And that's slightly tricky to check for in a single regex where the fractional portion is not required. I've included two regex to show this.

It does raise the interesting question as to what a "number" is. Do you include "inf" which is valid as a float in python? Or do you include numbers that are "numbers" but maybe can't be represented in python (such as numbers that are larger than the float max).

There's also ambiguities in how you parse numbers. For example, what about "--20"? Is this a "number"? Is this a legal way to represent "20"? Python will let you do "var = --20" and set it to 20 (though really this is because it treats it as an expression), but float("--20") does not work.

Anyways, without more info, here's a regex that I believe covers all the ints and floats as python parses them.

# Doesn't properly handle floats missing the integer part, such as ".7"
SIMPLE_FLOAT_REGEXP = re.compile(r'^[-+]?[0-9]+\.?[0-9]+([eE][-+]?[0-9]+)?$')
# Example "-12.34E+56"      # sign (-)
                            #     integer (12)
                            #           mantissa (34)
                            #                    exponent (E+56)

# Should handle all floats
FLOAT_REGEXP = re.compile(r'^[-+]?([0-9]+|[0-9]*\.[0-9]+)([eE][-+]?[0-9]+)?$')
# Example "-12.34E+56"      # sign (-)
                            #     integer (12)
                            #           OR
                            #             int/mantissa (12.34)
                            #                            exponent (E+56)

def is_float(str):
  return True if FLOAT_REGEXP.match(str) else False

Some example test values:

True  <- +42
True  <- +42.42
False <- +42.42.22
True  <- +42.42e22
True  <- +42.42E-22
False <- +42.42e-22.8
True  <- .42
False <- 42nope

Running the benchmarking code in @ron-reiter's answer shows that this regex is actually faster than the normal regex and is much faster at handling bad values than the exception, which makes some sense. Results:

check_regexp with good floats: 18.001921
check_regexp with bad floats: 17.861423
check_regexp with strings: 17.558862
check_correct_regexp with good floats: 11.04428
check_correct_regexp with bad floats: 8.71211
check_correct_regexp with strings: 8.144161
check_replace with good floats: 6.020597
check_replace with bad floats: 5.343049
check_replace with strings: 5.091642
check_exception with good floats: 5.201605
check_exception with bad floats: 23.921864
check_exception with strings: 23.755481

Upvotes: 7

Samantha Atkins
Samantha Atkins

Reputation: 678

User helper function:

def if_ok(fn, string):
  try:
    return fn(string)
  except Exception as e:
    return None

then

if_ok(int, my_str) or if_ok(float, my_str) or if_ok(complex, my_str)
is_number = lambda s: any([if_ok(fn, s) for fn in (int, float, complex)])

Upvotes: 1

S.Lott
S.Lott

Reputation: 391972

Which, not only is ugly and slow

I'd dispute both.

A regex or other string parsing method would be uglier and slower.

I'm not sure that anything much could be faster than the above. It calls the function and returns. Try/Catch doesn't introduce much overhead because the most common exception is caught without an extensive search of stack frames.

The issue is that any numeric conversion function has two kinds of results

  • A number, if the number is valid
  • A status code (e.g., via errno) or exception to show that no valid number could be parsed.

C (as an example) hacks around this a number of ways. Python lays it out clearly and explicitly.

I think your code for doing this is perfect.

Upvotes: 786

user10461621
user10461621

Reputation:

The input may be as follows:

a="50" b=50 c=50.1 d="50.1"


1-General input:

The input of this function can be everything!

Finds whether the given variable is numeric. Numeric strings consist of optional sign, any number of digits, optional decimal part and optional exponential part. Thus +0123.45e6 is a valid numeric value. Hexadecimal (e.g. 0xf4c3b00c) and binary (e.g. 0b10100111001) notation is not allowed.

is_numeric function

import ast
import numbers              
def is_numeric(obj):
    if isinstance(obj, numbers.Number):
        return True
    elif isinstance(obj, str):
        nodes = list(ast.walk(ast.parse(obj)))[1:]
        if not isinstance(nodes[0], ast.Expr):
            return False
        if not isinstance(nodes[-1], ast.Num):
            return False
        nodes = nodes[1:-1]
        for i in range(len(nodes)):
            #if used + or - in digit :
            if i % 2 == 0:
                if not isinstance(nodes[i], ast.UnaryOp):
                    return False
            else:
                if not isinstance(nodes[i], (ast.USub, ast.UAdd)):
                    return False
        return True
    else:
        return False

test:

>>> is_numeric("54")
True
>>> is_numeric("54.545")
True
>>> is_numeric("0x45")
True

is_float function

Finds whether the given variable is float. float strings consist of optional sign, any number of digits, ...

import ast

def is_float(obj):
    if isinstance(obj, float):
        return True
    if isinstance(obj, int):
        return False
    elif isinstance(obj, str):
        nodes = list(ast.walk(ast.parse(obj)))[1:]
        if not isinstance(nodes[0], ast.Expr):
            return False
        if not isinstance(nodes[-1], ast.Num):
            return False
        if not isinstance(nodes[-1].n, float):
            return False
        nodes = nodes[1:-1]
        for i in range(len(nodes)):
            if i % 2 == 0:
                if not isinstance(nodes[i], ast.UnaryOp):
                    return False
            else:
                if not isinstance(nodes[i], (ast.USub, ast.UAdd)):
                    return False
        return True
    else:
        return False

test:

>>> is_float("5.4")
True
>>> is_float("5")
False
>>> is_float(5)
False
>>> is_float("5")
False
>>> is_float("+5.4")
True

what is ast?


2- If you are confident that the variable content is String:

use str.isdigit() method

>>> a=454
>>> a.isdigit()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'int' object has no attribute 'isdigit'
>>> a="454"
>>> a.isdigit()
True

3-Numerical input:

detect int value:

>>> isinstance("54", int)
False
>>> isinstance(54, int)
True
>>> 

detect float:

>>> isinstance("45.1", float)
False
>>> isinstance(45.1, float)
True

Upvotes: 10

ravi tanwar
ravi tanwar

Reputation: 618

This code handles the exponents, floats, and integers, wihtout using regex.

return True if str1.lstrip('-').replace('.','',1).isdigit() or float(str1) else False

Upvotes: 2

xin.chen
xin.chen

Reputation: 986

import re
def is_number(num):
    pattern = re.compile(r'^[-+]?[-0-9]\d*\.\d*|[-+]?\.?[0-9]\d*$')
    result = pattern.match(num)
    if result:
        return True
    else:
        return False


​>>>: is_number('1')
True

>>>: is_number('111')
True

>>>: is_number('11.1')
True

>>>: is_number('-11.1')
True

>>>: is_number('inf')
False

>>>: is_number('-inf')
False

Upvotes: 2

Ron Reiter
Ron Reiter

Reputation: 3934

I wanted to see which method is fastest. Overall the best and most consistent results were given by the check_replace function. The fastest results were given by the check_exception function, but only if there was no exception fired - meaning its code is the most efficient, but the overhead of throwing an exception is quite large.

Please note that checking for a successful cast is the only method which is accurate, for example, this works with check_exception but the other two test functions will return False for a valid float:

huge_number = float('1e+100')

Here is the benchmark code:

import time, re, random, string

ITERATIONS = 10000000

class Timer:    
    def __enter__(self):
        self.start = time.clock()
        return self
    def __exit__(self, *args):
        self.end = time.clock()
        self.interval = self.end - self.start

def check_regexp(x):
    return re.compile("^\d*\.?\d*$").match(x) is not None

def check_replace(x):
    return x.replace('.','',1).isdigit()

def check_exception(s):
    try:
        float(s)
        return True
    except ValueError:
        return False

to_check = [check_regexp, check_replace, check_exception]

print('preparing data...')
good_numbers = [
    str(random.random() / random.random()) 
    for x in range(ITERATIONS)]

bad_numbers = ['.' + x for x in good_numbers]

strings = [
    ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(random.randint(1,10)))
    for x in range(ITERATIONS)]

print('running test...')
for func in to_check:
    with Timer() as t:
        for x in good_numbers:
            res = func(x)
    print('%s with good floats: %s' % (func.__name__, t.interval))
    with Timer() as t:
        for x in bad_numbers:
            res = func(x)
    print('%s with bad floats: %s' % (func.__name__, t.interval))
    with Timer() as t:
        for x in strings:
            res = func(x)
    print('%s with strings: %s' % (func.__name__, t.interval))

Here are the results with Python 2.7.10 on a 2017 MacBook Pro 13:

check_regexp with good floats: 12.688639
check_regexp with bad floats: 11.624862
check_regexp with strings: 11.349414
check_replace with good floats: 4.419841
check_replace with bad floats: 4.294909
check_replace with strings: 4.086358
check_exception with good floats: 3.276668
check_exception with bad floats: 13.843092
check_exception with strings: 15.786169

Here are the results with Python 3.6.5 on a 2017 MacBook Pro 13:

check_regexp with good floats: 13.472906000000009
check_regexp with bad floats: 12.977665000000016
check_regexp with strings: 12.417542999999995
check_replace with good floats: 6.011045999999993
check_replace with bad floats: 4.849356
check_replace with strings: 4.282754000000011
check_exception with good floats: 6.039081999999979
check_exception with bad floats: 9.322753000000006
check_exception with strings: 9.952595000000002

Here are the results with PyPy 2.7.13 on a 2017 MacBook Pro 13:

check_regexp with good floats: 2.693217
check_regexp with bad floats: 2.744819
check_regexp with strings: 2.532414
check_replace with good floats: 0.604367
check_replace with bad floats: 0.538169
check_replace with strings: 0.598664
check_exception with good floats: 1.944103
check_exception with bad floats: 2.449182
check_exception with strings: 2.200056

Upvotes: 10

Alex Pinto
Alex Pinto

Reputation: 262

I have a similar problem. Instead of defining a isNumber function, I want to convert a list of strings to floats, something that in high-level terms would be:

[ float(s) for s in list if isFloat(s)]

It is a given we can not really separate the float(s) from the isFloat(s) functions: these two results should be returned by the same function. Also, if float(s) fails, the whole process fails, instead of just ignoring the faulty element. Plus, "0" is a valid number and should be included in the list. When filtering out bad elements, be certain not to exclude 0.

Therefore, the above comprehension must be modified somehow to:

  • if any element in the list cannot be converted, ignore it and don't throw an exception
  • avoid calling float(s) more than once for each element (one for the conversion, the other for the test)
  • if the converted value is 0, it should still be present in the final list

I propose a solution inspired in the Nullable numerical types of C#. These types are internally represented by a struct that has the numerical value and adds a boolean indicating if the value is valid:

def tryParseFloat(s):
    try:
        return(float(s), True)
    except:
        return(None, False)

tupleList = [tryParseFloat(x) for x in list]
floats = [v for v,b in tupleList if b]

Upvotes: -2

SethMMorton
SethMMorton

Reputation: 48815

For strings of non-numbers, try: except: is actually slower than regular expressions. For strings of valid numbers, regex is slower. So, the appropriate method depends on your input.

If you find that you are in a performance bind, you can use a new third-party module called fastnumbers that provides a function called isfloat. Full disclosure, I am the author. I have included its results in the timings below.


from __future__ import print_function
import timeit

prep_base = '''\
x = 'invalid'
y = '5402'
z = '4.754e3'
'''

prep_try_method = '''\
def is_number_try(val):
    try:
        float(val)
        return True
    except ValueError:
        return False

'''

prep_re_method = '''\
import re
float_match = re.compile(r'[-+]?\d*\.?\d+(?:[eE][-+]?\d+)?$').match
def is_number_re(val):
    return bool(float_match(val))

'''

fn_method = '''\
from fastnumbers import isfloat

'''

print('Try with non-number strings', timeit.timeit('is_number_try(x)',
    prep_base + prep_try_method), 'seconds')
print('Try with integer strings', timeit.timeit('is_number_try(y)',
    prep_base + prep_try_method), 'seconds')
print('Try with float strings', timeit.timeit('is_number_try(z)',
    prep_base + prep_try_method), 'seconds')
print()
print('Regex with non-number strings', timeit.timeit('is_number_re(x)',
    prep_base + prep_re_method), 'seconds')
print('Regex with integer strings', timeit.timeit('is_number_re(y)',
    prep_base + prep_re_method), 'seconds')
print('Regex with float strings', timeit.timeit('is_number_re(z)',
    prep_base + prep_re_method), 'seconds')
print()
print('fastnumbers with non-number strings', timeit.timeit('isfloat(x)',
    prep_base + 'from fastnumbers import isfloat'), 'seconds')
print('fastnumbers with integer strings', timeit.timeit('isfloat(y)',
    prep_base + 'from fastnumbers import isfloat'), 'seconds')
print('fastnumbers with float strings', timeit.timeit('isfloat(z)',
    prep_base + 'from fastnumbers import isfloat'), 'seconds')
print()

Try with non-number strings 2.39108395576 seconds
Try with integer strings 0.375686168671 seconds
Try with float strings 0.369210958481 seconds

Regex with non-number strings 0.748660802841 seconds
Regex with integer strings 1.02021503448 seconds
Regex with float strings 1.08564686775 seconds

fastnumbers with non-number strings 0.174362897873 seconds
fastnumbers with integer strings 0.179651021957 seconds
fastnumbers with float strings 0.20222902298 seconds

As you can see

  • try: except: was fast for numeric input but very slow for an invalid input
  • regex is very efficient when the input is invalid
  • fastnumbers wins in both cases

Upvotes: 18

Aruthawolf
Aruthawolf

Reputation: 281

I know this is particularly old but I would add an answer I believe covers the information missing from the highest voted answer that could be very valuable to any who find this:

For each of the following methods connect them with a count if you need any input to be accepted. (Assuming we are using vocal definitions of integers rather than 0-255, etc.)

x.isdigit() works well for checking if x is an integer.

x.replace('-','').isdigit() works well for checking if x is a negative.(Check - in first position)

x.replace('.','').isdigit() works well for checking if x is a decimal.

x.replace(':','').isdigit() works well for checking if x is a ratio.

x.replace('/','',1).isdigit() works well for checking if x is a fraction.

Upvotes: 15

donald
donald

Reputation: 478

use following it handles all cases:-

import re
a=re.match('((\d+[\.]\d*$)|(\.)\d+$)' ,  '2.3') 
a=re.match('((\d+[\.]\d*$)|(\.)\d+$)' ,  '2.')
a=re.match('((\d+[\.]\d*$)|(\.)\d+$)' ,  '.3')
a=re.match('((\d+[\.]\d*$)|(\.)\d+$)' ,  '2.3sd')
a=re.match('((\d+[\.]\d*$)|(\.)\d+$)' ,  '2.3')

Upvotes: -3

mathfac
mathfac

Reputation: 196

I also used the function you mentioned, but soon I notice that strings as "Nan", "Inf" and it's variation are considered as number. So I propose you improved version of your function, that will return false on those type of input and will not fail "1e3" variants:

def is_float(text):
    try:
        float(text)
        # check for nan/infinity etc.
        if text.isalpha():
            return False
        return True
    except ValueError:
        return False

Upvotes: 1

Matthew Wilcoxson
Matthew Wilcoxson

Reputation: 3622

Updated after Alfe pointed out you don't need to check for float separately as complex handles both:

def is_number(s):
    try:
        complex(s) # for int, long, float and complex
    except ValueError:
        return False

    return True

Previously said: Is some rare cases you might also need to check for complex numbers (e.g. 1+2i), which can not be represented by a float:

def is_number(s):
    try:
        float(s) # for int, long and float
    except ValueError:
        try:
            complex(s) # for complex
        except ValueError:
            return False

    return True

Upvotes: 49

Sdwdaw
Sdwdaw

Reputation: 1037

For int use this:

>>> "1221323".isdigit()
True

But for float we need some tricks ;-). Every float number has one point...

>>> "12.34".isdigit()
False
>>> "12.34".replace('.','',1).isdigit()
True
>>> "12.3.4".replace('.','',1).isdigit()
False

Also for negative numbers just add lstrip():

>>> '-12'.lstrip('-')
'12'

And now we get a universal way:

>>> '-12.34'.lstrip('-').replace('.','',1).isdigit()
True
>>> '.-234'.lstrip('-').replace('.','',1).isdigit()
False

Upvotes: 34

TheRedstoneLemon
TheRedstoneLemon

Reputation: 309

Try this.

 def is_number(var):
    try:
       if var == int(var):
            return True
    except Exception:
        return False

Upvotes: 0

user1508746
user1508746

Reputation: 47

I was working on a problem that led me to this thread, namely how to convert a collection of data to strings and numbers in the most intuitive way. I realized after reading the original code that what I needed was different in two ways:

1 - I wanted an integer result if the string represented an integer

2 - I wanted a number or a string result to stick into a data structure

so I adapted the original code to produce this derivative:

def string_or_number(s):
    try:
        z = int(s)
        return z
    except ValueError:
        try:
            z = float(s)
            return z
        except ValueError:
            return s

Upvotes: 3

astrodsg
astrodsg

Reputation: 61

I needed to determine if a string cast into basic types (float,int,str,bool). After not finding anything on the internet I created this:

def str_to_type (s):
    """ Get possible cast type for a string

    Parameters
    ----------
    s : string

    Returns
    -------
    float,int,str,bool : type
        Depending on what it can be cast to

    """    
    try:                
        f = float(s)        
        if "." not in s:
            return int
        return float
    except ValueError:
        value = s.upper()
        if value == "TRUE" or value == "FALSE":
            return bool
        return type(s)

Example

str_to_type("true") # bool
str_to_type("6.0") # float
str_to_type("6") # int
str_to_type("6abc") # str
str_to_type(u"6abc") # unicode       

You can capture the type and use it

s = "6.0"
type_ = str_to_type(s) # float
f = type_(s) 

Upvotes: 5

Related Questions