"""General floating point formatting functions. | |
Functions: | |
fix(x, digits_behind) | |
sci(x, digits_behind) | |
Each takes a number or a string and a number of digits as arguments. | |
Parameters: | |
x: number to be formatted; or a string resembling a number | |
digits_behind: number of digits behind the decimal point | |
""" | |
from warnings import warnpy3k | |
warnpy3k("the fpformat module has been removed in Python 3.0", stacklevel=2) | |
del warnpy3k | |
import re | |
__all__ = ["fix","sci","NotANumber"] | |
# Compiled regular expression to "decode" a number | |
decoder = re.compile(r'^([-+]?)0*(\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) | |
if sign == '+': sign = '' | |
if fraction: fraction = fraction[1:] | |
if exppart: expo = int(exppart[1:]) | |
else: expo = 0 | |
return sign, intpart, fraction, expo | |
def unexpo(intpart, fraction, expo): | |
"""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 | |
def roundfrac(intpart, fraction, digs): | |
"""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, '' | |
def fix(x, digs): | |
"""Format x as [-]ddd.ddd with 'digs' digits after the point | |
and at least one digit before. | |
If digs <= 0, the point is suppressed.""" | |
if type(x) != type(''): x = repr(x) | |
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 | |
def sci(x, digs): | |
"""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.""" | |
if type(x) != type(''): x = repr(x) | |
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 | |
e = repr(abs(expo)) | |
e = '0'*(3-len(e)) + e | |
if expo < 0: e = '-' + e | |
else: e = '+' + e | |
return s + 'e' + e | |
def test(): | |
"""Interactive test run.""" | |
try: | |
while 1: | |
x, digs = input('Enter (x, digs): ') | |
print x, fix(x, digs), sci(x, digs) | |
except (EOFError, KeyboardInterrupt): | |
pass |