12

I have a variable in Python containing a floating point number (e.g. num = 24654.123), and I'd like to determine the number's precision and scale values (in the Oracle sense), so 123.45678 should give me (8,5), 12.76 should give me (4,2), etc.

I was first thinking about using the string representation (via str or repr), but those fail for large numbers (although I understand now it's the limitations of floating point representation that's the issue here):

>>> num = 1234567890.0987654321
>>> str(num) = 1234567890.1
>>> repr(num) = 1234567890.0987654

Edit:

Good points below. I should clarify. The number is already a float and is being pushed to a database via cx_Oracle. I'm trying to do the best I can in Python to handle floats that are too large for the corresponding database type short of executing the INSERT and handling Oracle errors (because I want to deal with the numbers a field, not a record, at a time). I guess map(len, repr(num).split('.')) is the closest I'll get to the precision and scale of the float?

mskfisher
  • 3,291
  • 4
  • 35
  • 48
jrdioko
  • 32,230
  • 28
  • 81
  • 120

12 Answers12

23

Getting the number of digits to the left of the decimal point is easy:

int(log10(x))+1

The number of digits to the right of the decimal point is trickier, because of the inherent inaccuracy of floating point values. I'll need a few more minutes to figure that one out.

Edit: Based on that principle, here's the complete code.

import math

def precision_and_scale(x):
    max_digits = 14
    int_part = int(abs(x))
    magnitude = 1 if int_part == 0 else int(math.log10(int_part)) + 1
    if magnitude >= max_digits:
        return (magnitude, 0)
    frac_part = abs(x) - int_part
    multiplier = 10 ** (max_digits - magnitude)
    frac_digits = multiplier + int(multiplier * frac_part + 0.5)
    while frac_digits % 10 == 0:
        frac_digits /= 10
    scale = int(math.log10(frac_digits))
    return (magnitude + scale, scale)
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • Out of curiosity, where does the number 14 come from? Is it platform-independent? – jrdioko Jun 11 '10 at 18:09
  • It's platform dependent, but most platforms will use IEEE-754. http://en.wikipedia.org/wiki/Double_precision_floating-point_format#Double_precision_binary_floating-point_format I probably could have made it 15, but I wanted to be conservative and make sure my rounding worked properly. – Mark Ransom Jun 11 '10 at 18:21
  • Instead of having 14 hard-coded, would it be possible to obtained from the Python distribution? – aaragon Jan 18 '17 at 15:22
  • @aaragon it might be possible, I don't know. Since every Python version I know is based on IEEE-754, I'm not motivated to find out. – Mark Ransom Jan 18 '17 at 16:00
  • 2
    @aaragon curiosity finally got the best of me, so I looked it up. The number you're looking for is [`sys.float_info.dig`](https://docs.python.org/3/library/sys.html#sys.float_info), which is 15 on my version of Python. – Mark Ransom Jun 23 '21 at 14:59
7

Not possible with floating point variables. For example, typing

>>> 10.2345

gives:

10.234500000000001

So, to get 6,4 out of this, you will have to find a way to distinguish between a user entering 10.2345 and 10.234500000000001, which is impossible using floats. This has to do with the way floating point numbers are stored. Use decimal.

import decimal
a = decimal.Decimal('10.234539048538495')
>>> str(a)
'10.234539048538495'
>>>  (len(str(a))-1, len(str(a).split('.')[1]))
(17,15)
Chinmay Kanchi
  • 62,729
  • 22
  • 87
  • 114
5

seems like str is better choice than repr (Python 2):

>>> r=10.2345678
>>> r
10.234567800000001
>>> repr(r)
'10.234567800000001'
>>> str(r)
'10.2345678'
Nas Banov
  • 28,347
  • 6
  • 48
  • 67
  • 1
    Lambda function for finding the number of digits to the right of the decimal `lambda x: len(str(x).split('.')[-1]) if len(str(x).split('.')) > 1 else 0` – Josh Herzberg Jul 15 '20 at 21:14
  • if we want to return 0 for 100.0 input **lambda x: len(str(x).split('.')[-1]) if int(x) < x and len(str(x).split('.')) > 1 else 0** – maziar Jan 03 '21 at 21:23
  • Starting with Python 3.2, there's no longer a difference between `repr` and `str` for `float` numbers. See [Why does str(float) return more digits in Python 3 than Python 2?](https://stackoverflow.com/q/25898733/5987) – Mark Ransom Jul 09 '21 at 20:46
  • I also must add that I do not get the high-decimal output presented in the `r` output shown in this answer. Python 3.11 gives me the truncated float with 7 decimals all the way through num/str/repr. – David Zwart Mar 03 '23 at 08:40
3

I think you should consider using the decimal type instead of a float. The float type will give rounding errors because the numbers are represented internally in binary but many decimal numbers don't have an exact binary representation.

Mark Byers
  • 811,555
  • 193
  • 1,581
  • 1,452
2

(0) Please confirm or deny: You are given floats to use, this is unavoidable, you can't get your data as decimal, the Oracle datatypes include decimal-based types, and this fundamental mismatch is unavoidable. Please explain any full or partial denial.

(1) Your "fail for large numbers" remark is misleading/irrelevant/wrong -- you say that your starting point is a float, but 1234567890.0987654321 can't be represented as a float, as shown by the result of repr().

(2) Perhaps you could use the NEW repr (Python 2.7 and 3.1) which provides the minimum possible precision of repr(x) that still satisfies float(repr(x)) == x

E.g. old repr(1.1) produces "1.1000000000000001", new repr(1.1) produces "1.1"

About "I guess map(len, repr(num).split('.')) is the closest I'll get to the precision and scale of the float?": You need a strategy to handle (a) negative and zero numbers (b) numbers like 1.1e20

Digging in Objects/floatobject.c should turn up the C code for the new repr() of a float object, should you need to use Python 2.6 or earlier.

(3) Perhaps if you told us the specs for the relevant Oracle data types, we could help you devise checks for choosing which type can contain a given float value.

John Machin
  • 81,303
  • 11
  • 141
  • 189
  • 0. Confirm, essentially. Everything is set up to use floats and I don't believe cx_Oracle will handle Decimal types, at least as I have it set up. 1. Edited to correct. 2. Interesting, unfortunately I'm behind 2.7. 3. I'm trying to write code that will work dynamically even if the Oracle column types change in precision or scale. (ouch, sorry for the bad formatting) – jrdioko Jun 11 '10 at 17:07
1

Basically, you can't with floating point numbers. Using the decimal type would help and if you want really large precision, consider using gmpy, the GNU Multiple Precision library's port to Python.

Daniel DiPaolo
  • 55,313
  • 14
  • 116
  • 115
1
def get_precision(f1):
    str1=str(f1)
    return len(str1.split(".")[1])

This answer returns only the number of digits after the decimal place. It will not be guaranteed as accurate because floating point math can create .99999 or .00001 for example.

i0x539
  • 4,763
  • 2
  • 20
  • 28
  • 3
    [A code-only answer is not high quality](//meta.stackoverflow.com/questions/392712/explaining-entirely-code-based-answers). While this code may be useful, you can improve it by saying why it works, how it works, when it should be used, and what its limitations are. Please [edit] your answer to include explanation and link to relevant documentation. – Muhammad Mohsin Khan Mar 01 '22 at 14:12
  • The python float isn't the standard IEEE float number. So this program get the relatively accurate precision. – Picoman Yang Mar 12 '22 at 03:50
0

I found another solution that seems to be simpler, but I'm not sure exactly if it will work for all cases.

   import math
   x = 1.2345678

   def flip(string):
       result = ""
       for ch in string:
           result = ch + result
      return result

   prec = int(math.log10(float(flip(str(x)))) + 1   # precision as int
mgvqrv
  • 1
  • 1
0

If you need to check precision, you can try:

def prec_check(a,b)
  a = str(a)
  b = str(b)
  do = bool(True)
  n = 0
  while do == True:
    if a and b and a[n] == a[b]:
      n += 1
    else:
      do = false
  return n
CanciuCostin
  • 1,773
  • 1
  • 10
  • 25
0

If you need to check the number of corresponding digits (of a and b)

def prec_check(a, b):

  a = str(a)
  b = str(b)
  do = bool(True)
  n = 0

  while do == True:

    if a and b and a[n] == a[b]:
      n += 1

    else:
      do = false

    return n

Note that this doesn't work with the "Decimal" module.

0

A number of symbols after comma. Works with int, float and Decimal types.

def get_num_precision(num):
    count = 0
    while num * 10**count % 1 != 0:
        count += 1
    return count
Den Avrondo
  • 89
  • 1
  • 6
0

Here's another Decimal approach that will work for at least some use cases. Whether it will always work depends on exactly what you're looking for.

123.45678 should give me (8,5), 12.76 should give me (4,2),

from decimal import Decimal


def get_precision_and_scale(num: float):
    # Cast float to string to get shortest round-trippable representation
    d_num = Decimal(str(num))
    sign, digits, exp = d_num.as_tuple()
    scale = len(digits)
    precision = abs(exp)
    return scale, precision


print(get_precision_and_scale(123.45678))
# (8, 5)

print(get_precision_and_scale(12.76))
# (4, 2)

Jagerber48
  • 488
  • 4
  • 13