41

Kind of like this question, but in reverse.

Given a string like 1, 1/2, or 1 2/3, what's the best way to convert it into a float? I'm thinking about using regexes on a case-by-case basis, but perhaps someone knows of a better way, or a pre-existing solution. I was hoping I could just use eval, but I think the 3rd case prevents that.

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236

9 Answers9

56

maybe something like this (2.6+)

from fractions import Fraction
float(sum(Fraction(s) for s in '1 2/3'.split()))
newacct
  • 119,665
  • 29
  • 163
  • 224
  • Not only more elegant, but the most accurate too – Nadia Alramli Nov 27 '09 at 01:20
  • 6
    This won't handle negative numbers correctly. e.g., `-1 2/3` should be -1.666 not -0.333. – mpen Sep 25 '14 at 21:54
  • It seems to fail because it treats the whole and fraction numbers separately before summing them. So close to being clean and functional! Seems like it could do with a -/+ strip filter to apply later after the float has been output. – Benargee Feb 02 '21 at 23:28
17

I tweaked James' answer a bit.

def convert_to_float(frac_str):
    try:
        return float(frac_str)
    except ValueError:
        num, denom = frac_str.split('/')
        try:
            leading, num = num.split(' ')
            whole = float(leading)
        except ValueError:
            whole = 0
        frac = float(num) / float(denom)
        return whole - frac if whole < 0 else whole + frac


print convert_to_float('3') # 3.0
print convert_to_float('3/2') # 1.5
print convert_to_float('1 1/2') # 1.5
print convert_to_float('-1 1/2') # -1.5

http://ideone.com/ItifKv

Community
  • 1
  • 1
mpen
  • 272,448
  • 266
  • 850
  • 1,236
7

Though you should stear clear of eval completely. Perhaps some more refined version of:

num,den = s.split( '/' )
wh, num = num.split()
result = wh + (float(num)/float(den))

Sorry, meant to be num.split not s.split, and casts. Edited.

Dave
  • 698
  • 4
  • 12
3

I see there are already several good answers here, but I've had good luck with this. It also has the benefit that it will tolerate non-fraction strings if you're parsing mixed sets of data, so there's no need to check if it's a fraction string or not upfront.

def convert_to_float(frac_str):
    try:
        return float(frac_str)
    except ValueError:
        try:
            num, denom = frac_str.split('/')
        except ValueError:
            return None
        try:
            leading, num = num.split(' ')
        except ValueError:
            return float(num) / float(denom)        
        if float(leading) < 0:
            sign_mult = -1
        else:
            sign_mult = 1
        return float(leading) + sign_mult * (float(num) / float(denom))

>>> convert_to_float('3')
3.0
>>> convert_to_float('1/4')
0.25
>>> convert_to_float('1 2/3')
1.6666666666666665
>>> convert_to_float('-2/3')
-0.6666666666666666
>>> convert_to_float('-3 1/2')
-3.5
James Errico
  • 5,876
  • 1
  • 20
  • 16
  • 1
    Hey...you lied about your last test case. `'-3 1/2'` results in `-2.5`. – mpen Sep 25 '14 at 21:51
  • Thanks Mark, all fixed up! – James Errico Sep 25 '14 at 22:11
  • No sir, you broke the positive whole+fraction case. When `leading` is positive you get `whole + (frac * -1 * 0)` which of course erases the fractional part. – mpen Sep 26 '14 at 01:33
  • Trying to get too cute casting the bool to an int. Fix coming up. – James Errico Sep 26 '14 at 13:12
  • 1
    Ok Mark, I think I got it, for real this time (although now I'm paranoid that I'm still missing something. Thanks for checking my work! – James Errico Sep 26 '14 at 13:24
  • 1
    `convert_to_float('x')` returns `None`, while `convert_to_float('x/y') raises `ValueError`. I'd take out the `try/except` around the `split('/')` so that it's consistent. Also instead of introducing the `sign_mult` variable just have two return statements with simpler calculations. – Mark Ransom Oct 07 '14 at 21:21
2

That might be a dirty workaround, but you could convert spaces to a + sign to solve the 3rd case (or to a - if your fraction is negative).

schnaader
  • 49,103
  • 10
  • 104
  • 136
2

This implementation avoids using eval and works on pre-2.6 versions of Python.

# matches a string consting of an integer followed by either a divisor
# ("/" and an integer) or some spaces and a simple fraction (two integers
# separated by "/")
FRACTION_REGEX = re.compile(r'^(\d+)(?:(?:\s+(\d+))?/(\d+))?$')

def parse(x):
  i, n, d = FRACTION_REGEX.match(x).groups()
  if d is None: n, d = 0, 1  # if d is None, then n is also None
  if n is None: i, n = 0, i
  return float(i) + float(n) / float(d)

To test:

>>> for x in ['1', '1/2', '1 2/3']: print(repr(parse(x)))
... 
1.0
0.5
1.6666666666666665
Laurence Gonsalves
  • 137,896
  • 35
  • 246
  • 299
2
def fractionToFloat(fraction):

    num = 0
    mult = 1

    if fraction[:1] == "-":
        fraction = fraction[1:]     
        mult = -1

    if " " in fraction:
        a = fraction.split(" ")
        num = float(a[0])
        toSplit = a[1]
    else:
        toSplit = fraction

    frac = toSplit.split("/")
    num += float(frac[0]) / float(frac[1])

    return num * mult

It can also handle "2 1/1e-8", "-1/3" and "1/5e3".

nvd
  • 2,995
  • 28
  • 16
0

Depending on what syntax you want to support for your fractions, eval('+'.join(s.split())) (with true division in place -- i.e., Python 3 or from __future__ import division in Python 2 -- might work. It would cover all the cases you mention, in particular.

Alex Martelli
  • 854,459
  • 170
  • 1,222
  • 1,395
-1
>>> s="1/2"
>>> eval('/'.join(map(str,map(float,s.split("/")))))
0.5

>>> s="3/5"
>>> eval('/'.join(map(str,map(float,s.split("/")))))
0.59999999999999998
ghostdog74
  • 327,991
  • 56
  • 259
  • 343
  • 1) eval is dangerous overkill for this. 2) Why split and convert the string just to join it back together? Why not use `eval(s)`? – Ned Batchelder Oct 08 '14 at 14:36
  • @NedBatchelder try doing this without the split, it won't work as a float and thus evaluate to 0. – jimh Sep 15 '16 at 11:01