50

I have looked in the standard library and on StackOverflow, and have not found a similar question. So, is there a way to do the following without rolling my own function? Bonus points if someone writes a beautiful function if there is no built in way.

def stringPercentToFloat(stringPercent)
    # ???
    return floatPercent

p1 = "99%"
p2 = "99.5%"
print stringPercentToFloat(p1)
print stringPercentToFloat(p2)

>>>> 0.99
>>>> 0.995
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
Wulfram
  • 3,292
  • 2
  • 15
  • 11

6 Answers6

89

Use strip('%') , as:

In [9]: "99.5%".strip('%')
Out[9]: '99.5'             #convert this to float using float() and divide by 100

In [10]: def p2f(x):
   ....:    return float(x.strip('%'))/100
   ....: 

In [12]: p2f("99%")
Out[12]: 0.98999999999999999

In [13]: p2f("99.5%")
Out[13]: 0.995
wjandrea
  • 28,235
  • 9
  • 60
  • 81
Ashwini Chaudhary
  • 244,495
  • 58
  • 464
  • 504
  • The accepted answer does not take locales into account. Most of the time the decimal point is a period (.), but in other countries it may be a comma (,), so the conversion will fail. – krema Jun 16 '20 at 18:59
  • 2
    @krema Well, it's out of scope for this question. It makes no sense to mention `locale.atof` every time float is being used for conversion. – Ashwini Chaudhary Jun 17 '20 at 04:47
20
float(stringPercent.strip('%')) / 100.0
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
17

Simply replace the % by e-2 before parsing to float :

float("99.5%".replace('%', 'e-2'))

It's safer since the result will still be correct if there's no % used.

Florent B.
  • 41,537
  • 7
  • 86
  • 101
4

I wrote the following method that should always return the output to the exact same accuracy as the input, with no floating point errors such as in the other answers.

def percent_to_float(s):
    s = str(float(s.rstrip("%")))
    i = s.find(".")
    if i == -1:
        return int(s) / 100
    if s.startswith("-"):
        return -percent_to_float(s.lstrip("-"))
    s = s.replace(".", "")
    i -= 2
    if i < 0:
        return float("." + "0" * abs(i) + s)
    else:
        return float(s[:i] + "." + s[i:])

Explanation

  1. Strip the "%" from the end.
  2. If percent has no ".", simply return it divided by 100.
  3. If percent is negative, strip the "-" and re-call function, then convert the result back to a negative and return it.
  4. Remove the decimal place.
  5. Decrement i (the index the decimal place was at) by 2, because we want to shift the decimal place 2 spaces to the left.
  6. If i is negative, then we need to pad with zeros.
    • Example: Suppose the input is "1.33%". To be able to shift the decimal place 2 spaces to the left, we would need to pad with a zero.
  7. Convert to a float.

Test case (Try it online):

from unittest.case import TestCase

class ParsePercentCase(TestCase):
    tests = {
        "150%"              : 1.5,
        "100%"              : 1,
        "99%"               : 0.99,
        "99.999%"           : 0.99999,
        "99.5%"             : 0.995,
        "95%"               : 0.95,
        "90%"               : 0.9,
        "50%"               : 0.5,
        "66.666%"           : 0.66666,
        "42%"               : 0.42,
        "20.5%"             : 0.205,
        "20%"               : 0.2,
        "10%"               : 0.1,
        "3.141592653589793%": 0.03141592653589793,
        "1%"                : 0.01,
        "0.1%"              : 0.001,
        "0.01%"             : 0.0001,
        "0%"                : 0,
    }
    tests = sorted(tests.items(), key=lambda x: -x[1])

    def test_parse_percent(self):
        for percent_str, expected in self.tests:
            parsed = percent_to_float(percent_str)
            self.assertEqual(expected, parsed, percent_str)

    def test_parse_percent_negative(self):
        negative_tests = [("-" + s, -f) for s, f in self.tests]
        for percent_str, expected in negative_tests:
            parsed = percent_to_float(percent_str)
            self.assertEqual(expected, parsed, percent_str)
Hat
  • 540
  • 8
  • 25
  • 1
    You went to a lot of work for nothing, there are no floating point errors in the other answers that yours doesn't have too. See https://stackoverflow.com/questions/588004/is-floating-point-math-broken – Mark Ransom Mar 07 '18 at 04:54
  • Well for instance in [Ashwini's answer](https://stackoverflow.com/a/12432693/2203482) you can see that his one-liner method called like `p2f("99%")` returns `0.98999999999999999`, whereas if you try running the test case in my answer you would see that the same input string of "99%" will give you the expected output of `0.99`. Could you give an example of an input string to my method that would cause a floating point error? – Hat Mar 07 '18 at 08:19
  • Read the link I gave you. It's a matter of how it's printed, not the actual value. With Ashwini's answer I get `0.99` output on both Python 2.7.1 and 3.6.3. Yours returns `0` on 2.7.1 because of integer division. – Mark Ransom Mar 07 '18 at 12:35
  • For example, `'%0.20f' % parse_percent('99%')` returns `'0.98999999999999999112'` – Mark Ransom Mar 07 '18 at 12:41
  • Well I wrote my method for Python 3, and I wrote it because none of the other answers to this question, while succinct, satisfied my requirement of [consistently returning a float value without floating point errors](https://ideone.com/AmV1RJ). – Hat Mar 08 '18 at 04:49
  • Well that's because `'%0.20f' % 0.99` returns `'0.98999999999999999112'`. That has nothing to do with the parse_percent method. – Hat Mar 08 '18 at 04:52
  • the suggested method on negative numbers return error in the following case: ```print(percent_to_float('-10%'))``` and result is: ```AttributeError: 'str' object has no attribute 'startwith'```. – Cindy Apr 01 '19 at 20:43
  • @Cindy I think you may have copied it incorrectly. It should be start**s**with, not startwith. – Hat Apr 02 '19 at 03:35
2

Based on the answer from @WKPlus this solution takes into account the locales where the decimal point is either a point . or a comma ,

float("-3,5%".replace(',','.')[:-1]) / 100
krema
  • 939
  • 7
  • 20
1

Another way: float(stringPercent[:-1]) / 100

WKPlus
  • 6,955
  • 2
  • 35
  • 53