Given a string representing a real number in decimal, how can I count the number of needed (non-zero) decimal places?
Question:
How would I do the following:
>>> num_decimal_places('3.2220')
3 # exclude zero-padding
>>> num_decimal_places('3.1')
1
>>> num_decimal_places('4')
0
I was thinking of doing:
len((str(number) if '.' in str(number) else str(number) + '.').rstrip('0').split('.')[-1])
Is there another, simpler way to do this?
Answers:
You could try using the Decimal
function in python:
abs(Decimal(string_value).as_tuple().exponent)
as explained in
Easy way of finding decimal places
You can use a regex to parse value
, capture the decimal digits and count the length of the match, if any:
import re
def num_decimal_places(value):
m = re.match(r"^[0-9]*.([1-9]([0-9]*[1-9])?)0*$", value)
return len(m.group(1)) if m is not None else 0
this is a bit less “raw” than splitting the string with multiple if else
, not sure if simpler or more readable, though.
You dont need regex, you can convert to float
and convert back to string! this automatically will remove the zeroes :
>>> def convertor(s):
... try :
... int(s.rstrip('0').rstrip('.'))
... return 0
... except:
... return len(str(float(s)).split('.')[-1])
...
>>> convertor('4.0230')
3
>>> convertor('4.0')
0
>>> convertor('4')
0
you could also just try something like:
try:
return len(str(num).split('.')[1].rstrip('0'))
except
return 0
By string process:
- Check
.
is present in number string or not.
- If Not present then return 0.
- If present the split number string by
.
and get second item from the split result.
- Remove 0 from the right side.
- Return len of item.
code:
>>> def dCount(no_str):
... if "." in no_str:
... return len(no_str.split(".")[1].rstrip("0"))
... else:
... return 0
...
>>> dCount("2")
0
>>> dCount("2.002")
3
>>> dCount("2.1230")
3
>>> dCount("2.01230")
4
>>>
import re
def f(s):
ls = s.split('.', 1)
if len(ls) == 2 and re.match(r'd*$', ls[1]):
return len(ls[1].rstrip('0'))
return 0
assert f('12') == 0
assert f('12.') == 0
assert f('12.1') == 1
assert f('12.100006') == 6
assert f('12.1.3') == 0
assert f('12.1abc') == 0
assert f('12.100000') == 1
The best and the most Pythonic way to achieve this is:
import decimal
x = '56.001000'
x = x.rstrip('0') # returns '56.001'
x = decimal.Decimal(x) # returns Decimal('0.001')
x = x.as_tuple().exponent # returns -3
x = abs(x) #returns 3
Above code can be written in simpler way as:
>>> x = '56.001000'
>>> abs(decimal.Decimal(x.rstrip('0')).as_tuple().exponent)
3
Below is the list of used functions for more reference:
- str.rstrip(): Return a copy of the string with trailing characters removed.
- decimal.Decimal(): Construct a new Decimal object based from value.
- x.as_tuple(): Returns a namedtuple of the format: DecimalTuple(sign=0, digits=(1,), exponent=-3)
- abs(): Return the absolute value of a number.
The Decimal
type is perfect for this, you can implement num_decimal_places()
as follows:
from decimal import Decimal
def num_decimal_places(value: str):
return -Decimal(value).normalize().as_tuple().exponent
It works as follows: Decimal(value)
parses the string, including exponent notation, then .normalize()
strips any trailing zeros from the internal representation. .as_tuple().exponent
contains the number of decimal places the internally stored integer is shifted to the left, so negative numbers specify the number of places to the right of the decimal.
Use the partition
method of strings to get the decimal portion – this returns a 3-tuple regardless of whether the string actually contains a decimal point or any digits after it. Then simply remove trailing zeros from the decimal portion and count the remaining digits. Thus:
def needed_precision(number_string):
whole, period, decimal = number_string.partition('.')
return len(decimal.rstrip('0'))
This can of course be inlined:
len(number_string.partition('.')[2].rstrip('0'))
Which is less readable, but quite short and straightforward.
How would I do the following:
>>> num_decimal_places('3.2220')
3 # exclude zero-padding
>>> num_decimal_places('3.1')
1
>>> num_decimal_places('4')
0
I was thinking of doing:
len((str(number) if '.' in str(number) else str(number) + '.').rstrip('0').split('.')[-1])
Is there another, simpler way to do this?
You could try using the Decimal
function in python:
abs(Decimal(string_value).as_tuple().exponent)
as explained in
Easy way of finding decimal places
You can use a regex to parse value
, capture the decimal digits and count the length of the match, if any:
import re
def num_decimal_places(value):
m = re.match(r"^[0-9]*.([1-9]([0-9]*[1-9])?)0*$", value)
return len(m.group(1)) if m is not None else 0
this is a bit less “raw” than splitting the string with multiple if else
, not sure if simpler or more readable, though.
You dont need regex, you can convert to float
and convert back to string! this automatically will remove the zeroes :
>>> def convertor(s):
... try :
... int(s.rstrip('0').rstrip('.'))
... return 0
... except:
... return len(str(float(s)).split('.')[-1])
...
>>> convertor('4.0230')
3
>>> convertor('4.0')
0
>>> convertor('4')
0
you could also just try something like:
try:
return len(str(num).split('.')[1].rstrip('0'))
except
return 0
By string process:
- Check
.
is present in number string or not. - If Not present then return 0.
- If present the split number string by
.
and get second item from the split result. - Remove 0 from the right side.
- Return len of item.
code:
>>> def dCount(no_str):
... if "." in no_str:
... return len(no_str.split(".")[1].rstrip("0"))
... else:
... return 0
...
>>> dCount("2")
0
>>> dCount("2.002")
3
>>> dCount("2.1230")
3
>>> dCount("2.01230")
4
>>>
import re
def f(s):
ls = s.split('.', 1)
if len(ls) == 2 and re.match(r'd*$', ls[1]):
return len(ls[1].rstrip('0'))
return 0
assert f('12') == 0
assert f('12.') == 0
assert f('12.1') == 1
assert f('12.100006') == 6
assert f('12.1.3') == 0
assert f('12.1abc') == 0
assert f('12.100000') == 1
The best and the most Pythonic way to achieve this is:
import decimal
x = '56.001000'
x = x.rstrip('0') # returns '56.001'
x = decimal.Decimal(x) # returns Decimal('0.001')
x = x.as_tuple().exponent # returns -3
x = abs(x) #returns 3
Above code can be written in simpler way as:
>>> x = '56.001000'
>>> abs(decimal.Decimal(x.rstrip('0')).as_tuple().exponent)
3
Below is the list of used functions for more reference:
- str.rstrip(): Return a copy of the string with trailing characters removed.
- decimal.Decimal(): Construct a new Decimal object based from value.
- x.as_tuple(): Returns a namedtuple of the format: DecimalTuple(sign=0, digits=(1,), exponent=-3)
- abs(): Return the absolute value of a number.
The Decimal
type is perfect for this, you can implement num_decimal_places()
as follows:
from decimal import Decimal
def num_decimal_places(value: str):
return -Decimal(value).normalize().as_tuple().exponent
It works as follows: Decimal(value)
parses the string, including exponent notation, then .normalize()
strips any trailing zeros from the internal representation. .as_tuple().exponent
contains the number of decimal places the internally stored integer is shifted to the left, so negative numbers specify the number of places to the right of the decimal.
Use the partition
method of strings to get the decimal portion – this returns a 3-tuple regardless of whether the string actually contains a decimal point or any digits after it. Then simply remove trailing zeros from the decimal portion and count the remaining digits. Thus:
def needed_precision(number_string):
whole, period, decimal = number_string.partition('.')
return len(decimal.rstrip('0'))
This can of course be inlined:
len(number_string.partition('.')[2].rstrip('0'))
Which is less readable, but quite short and straightforward.