from math import frexp, ldexp, copysign, isnan, isinf import re, sys __all__ = ['fromHex', 'toHex'] _parser = re.compile(r""" \A\s* # leading whitespace (?P[-+]?) # optional sign ( 0x # hex indicator (?=[0-9a-f]|\.[0-9a-f]) # lookahead guaranteeing at least one hex digit (?P[0-9a-f]*) # integer part (possibly empty) (\.(?P[0-9a-f]*))? # followed by optional fractional part p(?P[-+]?[0-9]+) # exponent | (?PInf(inity)?|NaN) ) \s*\Z # trailing whitespace """, re.VERBOSE | re.IGNORECASE).match nbits_correction = {'0': 4, '1': 3, '2': 2, '3': 2, '4': 1, '5': 1, '6': 1, '7': 1} mant_dig = sys.float_info.mant_dig min_exp = sys.float_info.min_exp def fromHex(s): """Convert a hexadecimal floating-point string to a float.""" m = _parser(s) if m is None: raise ValueError("Invalid hexadecimal float: %s" % s) if m.group('special'): return float(s) # express in the form sign * c * 2**e, with c and e integral fracpart = m.group('frac') or '' intpart = (m.group('int') + fracpart).lstrip('0') or '0' c = int('0x' + intpart, 16) e = int(m.group('exp')) - 4*len(fracpart) # round if there are more bits than fit in a float nbits = 4*len(intpart) - nbits_correction.get(intpart[0], 0) shift = max(nbits, min_exp - e) - mant_dig if shift > 0: e += shift if shift > nbits: c = 0 else: half = 1 << (shift-1) c = (c >> shift) + bool(c & half and c & (3*half-1)) # it's left to ldexp to raise OverflowError where appropriate return ldexp(c, e)*(-1.0 if m.group('sign') == '-' else 1.0) # number of bits to output = mant_dig; in the unlikely event that # mant_dig is not 53, round up to the next number of the form 1+4k. nbits = mant_dig + (1-mant_dig) % 4 def toHex(x): """Express a float as a hexadecimal floating-point string.""" if isnan(x) or isinf(x): return str(x) sign = '-' if copysign(1., x) < 0 else '' if not x: return sign + '0x0p+0' m, e = frexp(abs(x)) shift = nbits - max(min_exp - e, 0) c = '%%0%dx' % ((nbits + 3)//4) % int(m*2**shift) return '%s0x%s.%sp%+d' % (sign, c[0], c[1:], e - 1 - shift + nbits)