Python Forum
replacement for fpformat.extract - Printable Version

+- Python Forum (https://python-forum.io)
+-- Forum: Python Coding (https://python-forum.io/forum-7.html)
+--- Forum: General Coding Help (https://python-forum.io/forum-8.html)
+--- Thread: replacement for fpformat.extract (/thread-41944.html)



replacement for fpformat.extract - GbSig1998 - Apr-12-2024

I have some python 2.7 code that I need to update to 3.10. The code used fpformat.extract() to determine the following:

Python 2.7 Code
  floatLst=[-10999999.0,-10234.0,-123.456,-10.4,-0.00137,0.0,0.00137,10.4,123.456,10234.0,10999999]
  fmt = "%.8E"
  for numFloat in floatLst:
    sign, intPart, fractPart, exponent = fpformat.extract(fmt % numFloat)
    print (sign,intPart,fractPart,exponent)
The closet equivalent that I've found so far in Python 3.10 uses Decimal.

  from decimal import *

  floatLst=[-10999999.0,-10234.0,-123.456,-10.4,-0.00137,0.0,0.00137,10.4,123.456,10234.0,10999999]
  for numFloat in floatLst:
    sign,digits,exponent = Decimal(numFloat).as_tuple()
    print (numFloat,digits,exponent,len(digits))
This definitely does not work as well. Due to rounding both digits and exponent can be confusing.
Is there another class, or methods of either floats or strings that is specifically designed to replace fpformat.extract()?
I have search quite extensively for solutions to this question.

Any guidance will be greatly appreciated.


RE: replacement for fpformat.extract - Gribouillis - Apr-12-2024

Here is the Python 2 code of fpformat.extract(), you could perhaps convert this function directly, or it may run out of the box in Python 3.
# Compiled regular expression to "decode" a number
decoder = re.compile(r'^([-+]?)(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
# \0 the whole thing
# \1 leading sign or empty
# \2 digits left of decimal point
# \3 fraction (empty or begins with point)
# \4 exponent part (empty or begins with 'e' or 'E')

try:
    class NotANumber(ValueError):
        pass
except TypeError:
    NotANumber = 'fpformat.NotANumber'

def extract(s):
    """Return (sign, intpart, fraction, expo) or raise an exception:
    sign is '+' or '-'
    intpart is 0 or more digits beginning with a nonzero
    fraction is 0 or more digits
    expo is an integer"""
    res = decoder.match(s)
    if res is None: raise NotANumber, s
    sign, intpart, fraction, exppart = res.group(1,2,3,4)
    intpart = intpart.lstrip('0');
    if sign == '+': sign = ''
    if fraction: fraction = fraction[1:]
    if exppart: expo = int(exppart[1:])
    else: expo = 0
    return sign, intpart, fraction, expo



RE: replacement for fpformat.extract - deanhystad - Apr-12-2024

Since you know you'll be passing in a number and you know the format, you can make assumptions that could not be made by fpformat.extract()
import re


values = [-10999999.0, -10234.0, -123.456, -10.4, -0.00137, 0.0, 0.00137, 10.4, 123.456, 10234.0, 10999999]

for v in values:
    s, w, f, e = re.match(r'(-?)(.)\.(.+)e(.*)', f"{v:.8e}").groups()
    print(f"{v:11} :  {s:1}  {w:1}  {f:8}  {e}")
Output:
-10999999.0 : - 1 09999990 +07 -10234.0 : - 1 02340000 +04 -123.456 : - 1 23456000 +02 -10.4 : - 1 04000000 +01 -0.00137 : - 1 37000000 -03 0.0 : 0 00000000 +00 0.00137 : 1 37000000 -03 10.4 : 1 04000000 +01 123.456 : 1 23456000 +02 10234.0 : 1 02340000 +04 10999999 : 1 09999990 +07



RE: replacement for fpformat.extract - GbSig1998 - Apr-12-2024

(Apr-12-2024, 02:42 PM)Gribouillis Wrote: Here is the Python 2 code of fpformat.extract(), you could perhaps convert this function directly, or it may run out of the box in Python 3.
# Compiled regular expression to "decode" a number
decoder = re.compile(r'^([-+]?)(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
# \0 the whole thing
# \1 leading sign or empty
# \2 digits left of decimal point
# \3 fraction (empty or begins with point)
# \4 exponent part (empty or begins with 'e' or 'E')

try:
    class NotANumber(ValueError):
        pass
except TypeError:
    NotANumber = 'fpformat.NotANumber'

def extract(s):
    """Return (sign, intpart, fraction, expo) or raise an exception:
    sign is '+' or '-'
    intpart is 0 or more digits beginning with a nonzero
    fraction is 0 or more digits
    expo is an integer"""
    res = decoder.match(s)
    if res is None: raise NotANumber, s
    sign, intpart, fraction, exppart = res.group(1,2,3,4)
    intpart = intpart.lstrip('0');
    if sign == '+': sign = ''
    if fraction: fraction = fraction[1:]
    if exppart: expo = int(exppart[1:])
    else: expo = 0
    return sign, intpart, fraction, expo

Thank you very much!
I was able to convert the code that you posted to Python 3.10 Here is the 3.10 code. I did change a few names to align more with my convention and the name of the function to match its usage.
import re
# Compiled regular expression to "decode" a number
decoder = re.compile(r'^([-+]?)(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
# \0 the whole thing
# \1 leading sign or empty
# \2 digits left of decimal point
# \3 fraction (empty or begins with point)
# \4 exponent part (empty or begins with 'e' or 'E')

try:
    class NotANumber(ValueError):
        pass
except TypeError:
    NotANumber = 'fpformat.NotANumber'

def formatNastranFloat(x):
    if abs(x) < .01 or abs(x) > 9999999:
        fmt = "%.8E"
    else:
        fmt = "%.8G"
    s=fmt%x

    """Return (sign, intPart, fraction, expo) or raise an exception:
    sign is '+' or '-'
    intPart is 0 or more digits beginning with a nonzero
    fraction is 0 or more digits
    expo is an integer"""

    res = decoder.match(s)
    if res is None: raise NotANumber(s)
    sign, intPart, fractPart, expPart = res.group(1,2,3,4)
    print (sign, intPart, fractPart, expPart)
    intPart = intPart.lstrip('0');
    if sign == '+': sign = ''
    if fractPart: fractPart = fractPart[1:]
    if expPart: expo = int(expPart[1:])
    else: expo = 0

    # remove trailing zeros in the fraction
    if fractPart:
        for i in range(len(fractPart)-1, -1, -1):
            if fractPart[i] != "0":
                break
        fractPart = fractPart[:i+1]

    # the length of the fractional parts is whatever is left over
    fracLen = 8 - len(sign) - len(intPart) - 1 - len(str(expo))
    s = (sign + intPart + "." + fractPart[:fracLen] + str(expo)).ljust(8)

    return s,sign, intPart, fractPart, expo

if __name__=='__main__':
  myFloat=-0.000124
  s,sign, intPart, fraction, expo=formatNastranFloat(myFloat)
  print (s,sign, intPart, fraction, expo)



RE: replacement for fpformat.extract - deanhystad - Apr-12-2024

I don't think your code works. Test with your numbers from the first post:

floatLst=[-10999999.0,-10234.0,-123.456,-10.4,-0.00137,0.0,0.00137,10.4,123.456,10234.0,10999999]