fpformat.py 4.59 KB
Newer Older
1
"""General floating point formatting functions.
Guido van Rossum's avatar
Guido van Rossum committed
2

3 4 5
Functions:
fix(x, digits_behind)
sci(x, digits_behind)
Guido van Rossum's avatar
Guido van Rossum committed
6

7
Each takes a number or a string and a number of digits as arguments.
Guido van Rossum's avatar
Guido van Rossum committed
8

9 10 11 12
Parameters:
x:             number to be formatted; or a string resembling a number
digits_behind: number of digits behind the decimal point
"""
13 14 15
from warnings import warnpy3k
warnpy3k("the fpformat module has been removed in Python 3.0", stacklevel=2)
del warnpy3k
Guido van Rossum's avatar
Guido van Rossum committed
16

17
import re
Guido van Rossum's avatar
Guido van Rossum committed
18

Skip Montanaro's avatar
Skip Montanaro committed
19 20
__all__ = ["fix","sci","NotANumber"]

Guido van Rossum's avatar
Guido van Rossum committed
21
# Compiled regular expression to "decode" a number
22
decoder = re.compile(r'^([-+]?)0*(\d*)((?:\.\d*)?)(([eE][-+]?\d+)?)$')
Guido van Rossum's avatar
Guido van Rossum committed
23 24 25 26
# \0 the whole thing
# \1 leading sign or empty
# \2 digits left of decimal point
# \3 fraction (empty or begins with point)
27
# \4 exponent part (empty or begins with 'e' or 'E')
Guido van Rossum's avatar
Guido van Rossum committed
28

29
try:
30 31
    class NotANumber(ValueError):
        pass
32
except TypeError:
33
    NotANumber = 'fpformat.NotANumber'
Guido van Rossum's avatar
Guido van Rossum committed
34 35

def extract(s):
36 37 38 39 40 41 42 43 44 45 46 47 48
    """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)
    if sign == '+': sign = ''
    if fraction: fraction = fraction[1:]
    if exppart: expo = int(exppart[1:])
    else: expo = 0
    return sign, intpart, fraction, expo
Guido van Rossum's avatar
Guido van Rossum committed
49 50

def unexpo(intpart, fraction, expo):
51 52 53 54 55 56 57 58 59 60 61 62
    """Remove the exponent by changing intpart and fraction."""
    if expo > 0: # Move the point left
        f = len(fraction)
        intpart, fraction = intpart + fraction[:expo], fraction[expo:]
        if expo > f:
            intpart = intpart + '0'*(expo-f)
    elif expo < 0: # Move the point right
        i = len(intpart)
        intpart, fraction = intpart[:expo], intpart[expo:] + fraction
        if expo < -i:
            fraction = '0'*(-expo-i) + fraction
    return intpart, fraction
Guido van Rossum's avatar
Guido van Rossum committed
63 64

def roundfrac(intpart, fraction, digs):
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
    """Round or extend the fraction to size digs."""
    f = len(fraction)
    if f <= digs:
        return intpart, fraction + '0'*(digs-f)
    i = len(intpart)
    if i+digs < 0:
        return '0'*-digs, ''
    total = intpart + fraction
    nextdigit = total[i+digs]
    if nextdigit >= '5': # Hard case: increment last digit, may have carry!
        n = i + digs - 1
        while n >= 0:
            if total[n] != '9': break
            n = n-1
        else:
            total = '0' + total
            i = i+1
            n = 0
        total = total[:n] + chr(ord(total[n]) + 1) + '0'*(len(total)-n-1)
        intpart, fraction = total[:i], total[i:]
    if digs >= 0:
        return intpart, fraction[:digs]
    else:
        return intpart[:digs] + '0'*-digs, ''
Guido van Rossum's avatar
Guido van Rossum committed
89 90

def fix(x, digs):
91 92 93
    """Format x as [-]ddd.ddd with 'digs' digits after the point
    and at least one digit before.
    If digs <= 0, the point is suppressed."""
94
    if type(x) != type(''): x = repr(x)
95 96 97 98 99 100 101 102 103 104
    try:
        sign, intpart, fraction, expo = extract(x)
    except NotANumber:
        return x
    intpart, fraction = unexpo(intpart, fraction, expo)
    intpart, fraction = roundfrac(intpart, fraction, digs)
    while intpart and intpart[0] == '0': intpart = intpart[1:]
    if intpart == '': intpart = '0'
    if digs > 0: return sign + intpart + '.' + fraction
    else: return sign + intpart
Guido van Rossum's avatar
Guido van Rossum committed
105 106

def sci(x, digs):
107 108 109
    """Format x as [-]d.dddE[+-]ddd with 'digs' digits after the point
    and exactly one digit before.
    If digs is <= 0, one digit is kept and the point is suppressed."""
110
    if type(x) != type(''): x = repr(x)
111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131
    sign, intpart, fraction, expo = extract(x)
    if not intpart:
        while fraction and fraction[0] == '0':
            fraction = fraction[1:]
            expo = expo - 1
        if fraction:
            intpart, fraction = fraction[0], fraction[1:]
            expo = expo - 1
        else:
            intpart = '0'
    else:
        expo = expo + len(intpart) - 1
        intpart, fraction = intpart[0], intpart[1:] + fraction
    digs = max(0, digs)
    intpart, fraction = roundfrac(intpart, fraction, digs)
    if len(intpart) > 1:
        intpart, fraction, expo = \
            intpart[0], intpart[1:] + fraction[:-1], \
            expo + len(intpart) - 1
    s = sign + intpart
    if digs > 0: s = s + '.' + fraction
132
    e = repr(abs(expo))
133 134 135 136
    e = '0'*(3-len(e)) + e
    if expo < 0: e = '-' + e
    else: e = '+' + e
    return s + 'e' + e
Guido van Rossum's avatar
Guido van Rossum committed
137 138

def test():
139 140 141 142 143 144 145
    """Interactive test run."""
    try:
        while 1:
            x, digs = input('Enter (x, digs): ')
            print x, fix(x, digs), sci(x, digs)
    except (EOFError, KeyboardInterrupt):
        pass