98

Is there an easy way or integrated function to find out the decimal places of a floating point number?

The number is parsed from a string, so one way is to count the digits after the . sign, but that looks quite clumsy to me. Is there a possibility to get the information needed out of a float or Decimal object?

mkrieger1
  • 19,194
  • 5
  • 54
  • 65
Constantinius
  • 34,183
  • 8
  • 77
  • 85
  • 4
    Not sure in python, but be very careful about localization here as some cultures give a different meaning to the "," and "." characters when interpreting numbers – jglouie May 31 '11 at 15:35
  • Yes, you are right, but in my case I can be sure to get the values in the displayed number format. But thank you anyway for the info. :) – Constantinius May 31 '11 at 15:38
  • 2
    You may be falling prey to the old precision/accuracy trap. In short, precision is how many digits follow the dot. Accuracy is how many of those digits are actually "correct". Floating point representation is one of those areas where computers only approximate the intended value. – JS. May 31 '11 at 19:49
  • If you think the solution is starting from a string value, why don't you just find the number of digits from the string directly, instead of going the way through a Decimal type? – Alexander Jun 01 '11 at 10:02
  • 3
    As I stated in my question, I think such an approach is too clumsy. and it does not comply to numbers without decimal places or numbers in another format (e.g: scientific). The Decimal class seems to be a good abstraction. – Constantinius Jun 01 '11 at 12:35

15 Answers15

122

To repeat what others have said (because I had already typed it out!), I'm not even sure such a value would be meaningful in the case of a floating point number, because of the difference between the decimal and binary representation; often a number representable by a finite number of decimal digits will have only an infinite-digit representation in binary.

In the case of a decimal.Decimal object, you can retrieve the exponent using the as_tuple method, which returns a namedtuple with sign, digits, and exponent attributes:

>>> d = decimal.Decimal('56.4325')
>>> d.as_tuple().exponent
-4

>>> d = decimal.Decimal('56.43256436')
>>> d.as_tuple().exponent
-8

>>> d = decimal.Decimal(str(56.4325))
>>> d.as_tuple().exponent
-4

The negation of the exponent is the number of digits after the decimal point, unless the exponent is greater than 0.

101
  • 8,514
  • 6
  • 43
  • 69
senderle
  • 145,869
  • 36
  • 209
  • 233
  • BTW, I've tried to make the exponent go above 0, but I haven't been able to. I'm not sure it ever does. Anyone know how that side of things works? – senderle May 31 '11 at 16:02
  • Also, I'm running ancient Python; in 2.7 and above, `Decimal` objects have a [`from_float`](http://docs.python.org/library/decimal.html#decimal.Decimal.from_float) classmethod that directly converts floats to `Decimal` objects. – senderle May 31 '11 at 16:06
  • This is an interesting solution. I will try it and post if I succeded. +1 for now. – Constantinius May 31 '11 at 16:06
  • 8
    This is different from in my Python (version 3.2): decimal.Decimal(56.4325) gives Decimal('56.43249999999999744204615126363933086395263671875') and as_tuple().exponent returns -47 – Alexander May 31 '11 at 16:07
  • I think it won't go above 0, because the digits element of the tuple is supposed to represent **all** the digits in the decimal. If they had an exponent of 5 that would imply that 5 zeros would not be found in the digits element of the tuple. – Brian Fisher May 31 '11 at 16:08
  • @Brian, I _think_ you're right, but given my knowledge, it's possible that there are contexts in which the precision is smaller than the magnitude. – senderle May 31 '11 at 16:10
  • Yeah, I should have mentioned, that is my understanding, not a fact. By the way plus one, this seems like the answer to the question in my mind. – Brian Fisher May 31 '11 at 16:11
  • 12
    @Alexander, try it as a string. :) – senderle May 31 '11 at 16:12
  • 1
    @Alexander try decimal.Decimal("56.4325"). You original attempt first converted 56.5425 to a float and then to a decimal which is why you get such a decimal close to 56.5425 with lots of precision. – Brian Fisher May 31 '11 at 16:14
  • If the number is a string in the first place, the number of decimals is already given... – Alexander May 31 '11 at 16:15
  • @Alexander, my point was that in my answer, I used strings, while in your comment, you did not; hence the difference. Because I'm stuck using 2.6 at the moment, I couldn't test `Decimal` construction using a `float` argument. Thanks for testing it for me. – senderle May 31 '11 at 16:18
  • 2
    @senderle Yes, good point. I did actually miss the single quotes. Thanks. – Alexander May 31 '11 at 16:18
  • @senderle: your proposed answer is currently the best solution for my problem and works like a charm. Thanks a lot! `e = abs(Decimal(string_value).as_tuple().exponent)` – Constantinius Jun 01 '11 at 09:36
  • @Constantinius: If you think the solution is starting from a string value, why don't you just find the number of digits from the string directly, instead of going the way through a Decimal type? For instance: len(string_value) - string_value.find(".") - 1 – Alexander Jun 01 '11 at 10:12
  • As I stated in my question, I think such an approach is too clumsy. and it does not comply to numbers without decimal places or numbers in another format (e.g: scientific). The `Decimal` class seems to be a good abstraction. – Constantinius Jun 01 '11 at 12:34
  • 1
    @BrianFisher, saw this and remembered our discussion -- thought you might want to know. `exponent` _does_ go above zero sometimes: `decimal.Decimal(500).normalize().as_tuple().exponent == 2` – senderle Jun 27 '12 at 22:54
  • 1
    @senderle: Or, more commonly, `decimal.Decimal("5e3").as_tuple().exponent`: the significance of `5e3` is less than that of `5000`. – tricasse Dec 11 '19 at 09:22
  • what do you mean by: `unless the exponent is greater than 0` ? would it it be work for negative values like `-0.12345`? – alper Jun 06 '21 at 15:20
16

"the number of decimal places" is not a property a floating point number has, because of the way they are stored and handled internally.

You can get as many decimal places as you like from a floating point number. The question is how much accuracy you want. When converting a floating point number to a string, part of the process is deciding on the accuracy.

Try for instance:

1.1 - int(1.1)

And you will see that the answer is:

0.10000000000000009

So, for this case, the number of decimals is 17. This is probably not the number you are looking for.

You can, however, round the number to a certain number of decimals with "round":

round(3.1415 - int(3.1415), 3)

For this case, the number of decimals is cut to 3.

You can't get "the number of decimals from a float", but you can decide the accuracy and how many you want.

Converting a float to a string is one way of making such a decision.

Alexander
  • 9,737
  • 4
  • 53
  • 59
  • 2
    This is an excellent post, since it points out, that my questions was wrong. But unfortunately it does not help me with my problem. (+1) – Constantinius May 31 '11 at 15:56
8

In case you only need to find the decimal place of the most significant digit, ie. the leftmost one, another primitive solution would be:

fraction = 0.001
decimal_places = int(f'{fraction:e}'.split('e')[-1])

This makes use of the scientific notation (m x 10^n), which already provides the number of decimal places in form of the power (n) of 10, to which the coefficient (m) is raised, ie. -3, derived from 1.000000e-03 for the above example.

An important difference to Decimal(str(fraction)).as_tuple().exponent is that the scientific notation will always only consider the most significant digit for the exponent n, similar to Decimal().adjusted(), whereas Decimal().as_tuple().exponent returns the exponent of the least significant digit.

String formatting to scientific notation can of course also only handle floats and no strings, thus any floating point arithmetic issues persist. However, for many use cases the above might be sufficient.

mawall
  • 171
  • 3
  • 10
7

The fastest way I found for calc digits after decimal point and rounding the number is

Calculate digits:

a=decimal.Decimal('56.9554362669143476');
lenstr = len(str(a).split(".")[1])

Calc, check and rounding:

a=decimal.Decimal('56.9554362669143476');
a = round(float(a),5) if len(str(a).split(".")[1]) > 5 else float(a)

Comparison:

$ python2 -mtimeit 'import decimal; a=decimal.Decimal('56.9554362669143476'); round(float(a),5) if a.as_tuple().exponent < -5 else float(a)'
10000 loops, best of 3: 32.4 usec per loop
$ python -mtimeit 'import decimal; a=decimal.Decimal('56.9554362669143476'); a = round(float(a),5) if len(str(a).split(".")[1]) > 5 else float(a)'
100000 loops, best of 3: 16.7 usec per loop
George
  • 126
  • 1
  • 5
6

I needed something like this and i tested some of these, the fastest i found out was :

str(number)[::-1].find('.')

Because of the floating point issue all the modulo ones gave me false results even with Decimal(number) (note that i needed this in a script to fix prices of an entire db)

len(str(Decimal(str(number))) % Decimal(1))) - 2

The need to put a string into Decimal is quite uneasy when we have floats or something like.

Here is my "bench" : https://repl.it/FM8m/17

Jérémy JOKE
  • 271
  • 4
  • 15
  • This find() approach did the trick for me for evaluating tkinter Entry() widget values. When 'number' is a string representation of a number and there is no decimal, it returns -1. With a decimal, it correctly counts the decimal places, whether as a string or float type. When 'number' is a float type without a decimal, it will return 1 because of the implicit .0 decimal place. – Doc Aug 07 '23 at 12:31
4

The decimal library is for working with decimal numbers, like in Accounting. It doesn't inherently have a function to return the number of decimal places. This is especially a problem when you realize that the context it runs under sets it at whatever the user wants.

If you get a string, you can convert to decimal, but this will either tack on zeros to get you to your accuracy, or use the rounding setting to truncate it.

Your best bet would probably bet splitting on the dot in your string and counting the number of chars in the resulting substring.

Spencer Rathbun
  • 14,510
  • 6
  • 54
  • 73
2

This will help you ;)

import decimal

def find_decimals(value):
    return (abs(decimal.Decimal(str(value)).as_tuple().exponent))

# Floats
print (find_decimals(123.45678))
print (find_decimals(0.00001))

# Scientific Notation
print (find_decimals(1e-05))

# Strings
print (find_decimals('123.45678'))
print (find_decimals('0.00001'))
print (find_decimals('1e-05'))

Output:

5
5
5
5
5
5
kure
  • 21
  • 2
1

If you know you're not going to have parsing issues (or if you're letting python itself or some other library handle that for you, hopefully handling localization issues)... just parse it and use modf. The return value is a pair of values, one of which is the integral part, the other is the fractional part.

jkerian
  • 16,497
  • 3
  • 46
  • 59
  • This is interesting, but in my case not useful, since I still have the `float` from which I cannot get the number of decimal digits. – Constantinius May 31 '11 at 16:05
1

Since Python floating point numbers are internally represented as binary rather than decimal, there's really no shortcut other than converting to decimal. The only built-in way to do that is by converting to a string. You could write your own code to do a decimal conversion and count the digits, but it would be a duplication of effort.

Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
1
    from math import log10
    my_number = 3.1415927

    int(log10(float(str(my_number)[::-1])))+1

The way it works is that log10(number)+1 will give us the number of digits to the LEFT of the decimal place.

If we were to REVERSE the digits then the digits on the left would have previously been on the RIGHT.

    # Reverse the string
    any_string[::-1]
1

Easiest way to do it quick

precision = lambda x: len(str(x).split('.')[1])

obviously watch the types!

I needed this as I need to dynamically round a range of big and small numbers.

>>> precision = lambda x: tuple(len(p) for p in str(x).split('.'))
>>> precision(123.345345)
(3, 6)
nialloc
  • 805
  • 7
  • 17
0

Here's what worked for me:

string_value = "1234.1111"
exponent = (-len(string_value.rpartition(".")[-1])
            if "." in citem.toll_free_pricing else 0)
print(exponent)
# prints "-4"


string_value = "1234"
exponent = (-len(string_value.rpartition(".")[-1])
            if "." in citem.toll_free_pricing else 0)
print(exponent)
# prints "0"

Here's how the code is "broken-down":

string_value = "1234.1111"

if "." in citem.toll_free_pricing:

    tuple_value = string_value.rpartition(".")
    print(tuple_value)
    # prints "('1234', '.', '1111')"

    decimals = tuple_value[-1]
    print(decimals)
    # prints "1111"

    decimal_count = len(decimals)
    print(decimal_count)
    # prints "4"

    exponent = -decimal_count
    print(exponent)
    # prints "-4"

else:
    print(0)
nicolas.leblanc
  • 588
  • 7
  • 27
0
x = 1.2345

x_string = (str(x))

x_string_split = x_string.split('.')

x_string_split_decimal = x_string_split[1]

print(x_string_split_decimal)

print(len(x_string_split_decimal))
Suraj Rao
  • 29,388
  • 11
  • 94
  • 103
Ganesh
  • 1
  • Remember that Stack Overflow isn't just intended to solve the immediate problem, but also to help future readers find solutions to similar problems, which requires understanding the underlying code. This is especially important for members of our community who are beginners, and not familiar with the syntax. Given that, **can you [edit] your answer to include an explanation of what you're doing** and why you believe it is the best approach? That's especially important here where there are eleven existing answers. – Jeremy Caney May 19 '22 at 00:25
0

The way it works is to convert the input decimal to characters, when it detects ".", it will add the following decimal to decimals, and finally return its length

def decimal(obj):
    is_point = False
    decimals = []
    for get_float in str(obj):
        if is_point:
            decimals.append(get_float)
        if get_float == ".":
            is_point = True
    return len(decimals)

when i use it

print(decimal(1.32))
>> 2
0

turn it to a string, loop within it until you meet a period then subtract i+1 from the length of str

d = ['45.08']
n=len(d[0])
for i in range(n):
    if d[0][i]=='.':
        dp= n-1-i
        break
print (dp)
output = 2 (int)