from math import frexp, ldexp, copysign, isnan, isinf import re, sys _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[-+]?\d+) # 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, '8': 0, '9': 0, 'a': 0, 'b': 0, 'c': 0, 'd': 0, 'e': 0, 'f': 0} mant_dig = sys.float_info.mant_dig min_exp = sys.float_info.min_exp # dap = max no. of hex digits after the point, assuming 1 bit before dap = (mant_dig+2) // 4 def fromHex(s): """Convert a hex floating-point string to a float.""" m = _parser(s) if m is None: raise ValueError("Invalid hex float literal: %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[intpart[0]] shift = max(nbits - mant_dig, min_exp - mant_dig - e) 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)) return ldexp(c, e)*(-1.0 if m.group('sign') == '-' else 1.0) def toHex(x): """Express a float as a hex 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 = mant_dig + min(e - min_exp, 0) c = '%x' % int(m*2**shift) c = '0'*(dap+1-len(c)) + c return '%s0x%s.%sp%+d' % (sign, c[0], c[1:], e-shift+4*dap)