# pypy / pypy / rlib / rstruct / ieee.py

 ``` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153``` ```""" Packing and unpacking of floats in the IEEE 32-bit and 64-bit formats. """ import math from pypy.rlib import rarithmetic, rfloat, objectmodel from pypy.rlib.rarithmetic import r_ulonglong def round_to_nearest(x): """Python 3 style round: round a float x to the nearest int, but unlike the builtin Python 2.x round function: - return an int, not a float - do round-half-to-even, not round-half-away-from-zero. We assume that x is finite and nonnegative; except wrong results if you use this for negative x. """ int_part = r_ulonglong(x) frac_part = x - int_part if frac_part > 0.5 or frac_part == 0.5 and int_part & 1: int_part += 1 return int_part def float_unpack(Q, size): """Convert a 32-bit or 64-bit integer created by float_pack into a Python float.""" if size == 8: MIN_EXP = -1021 # = sys.float_info.min_exp MAX_EXP = 1024 # = sys.float_info.max_exp MANT_DIG = 53 # = sys.float_info.mant_dig BITS = 64 elif size == 4: MIN_EXP = -125 # C's FLT_MIN_EXP MAX_EXP = 128 # FLT_MAX_EXP MANT_DIG = 24 # FLT_MANT_DIG BITS = 32 else: raise ValueError("invalid size value") if not objectmodel.we_are_translated(): # This tests generates wrong code when translated: # with gcc, shifting a 64bit int by 64 bits does # not change the value. if Q >> BITS: raise ValueError("input out of range") # extract pieces one = r_ulonglong(1) sign = rarithmetic.intmask(Q >> BITS - 1) exp = rarithmetic.intmask((Q & ((one << BITS - 1) - (one << MANT_DIG - 1))) >> MANT_DIG - 1) mant = Q & ((one << MANT_DIG - 1) - 1) if exp == MAX_EXP - MIN_EXP + 2: # nan or infinity result = rfloat.NAN if mant else rfloat.INFINITY elif exp == 0: # subnormal or zero result = math.ldexp(mant, MIN_EXP - MANT_DIG) else: # normal mant += one << MANT_DIG - 1 result = math.ldexp(mant, exp + MIN_EXP - MANT_DIG - 1) return -result if sign else result def float_pack(x, size): """Convert a Python float x into a 64-bit unsigned integer with the same byte representation.""" if size == 8: MIN_EXP = -1021 # = sys.float_info.min_exp MAX_EXP = 1024 # = sys.float_info.max_exp MANT_DIG = 53 # = sys.float_info.mant_dig BITS = 64 elif size == 4: MIN_EXP = -125 # C's FLT_MIN_EXP MAX_EXP = 128 # FLT_MAX_EXP MANT_DIG = 24 # FLT_MANT_DIG BITS = 32 else: raise ValueError("invalid size value") sign = rfloat.copysign(1.0, x) < 0.0 if not rfloat.isfinite(x): if rfloat.isinf(x): mant = r_ulonglong(0) exp = MAX_EXP - MIN_EXP + 2 else: # rfloat.isnan(x): mant = r_ulonglong(1) << (MANT_DIG-2) # other values possible exp = MAX_EXP - MIN_EXP + 2 elif x == 0.0: mant = r_ulonglong(0) exp = 0 else: m, e = math.frexp(abs(x)) # abs(x) == m * 2**e exp = e - (MIN_EXP - 1) if exp > 0: # Normal case. mant = round_to_nearest(m * (r_ulonglong(1) << MANT_DIG)) mant -= r_ulonglong(1) << MANT_DIG - 1 else: # Subnormal case. if exp + MANT_DIG - 1 >= 0: mant = round_to_nearest(m * (r_ulonglong(1) << exp + MANT_DIG - 1)) else: mant = r_ulonglong(0) exp = 0 # Special case: rounding produced a MANT_DIG-bit mantissa. if not objectmodel.we_are_translated(): assert 0 <= mant <= 1 << MANT_DIG - 1 if mant == r_ulonglong(1) << MANT_DIG - 1: mant = r_ulonglong(0) exp += 1 # Raise on overflow (in some circumstances, may want to return # infinity instead). if exp >= MAX_EXP - MIN_EXP + 2: raise OverflowError("float too large to pack in this format") # check constraints if not objectmodel.we_are_translated(): assert 0 <= mant < 1 << MANT_DIG - 1 assert 0 <= exp <= MAX_EXP - MIN_EXP + 2 assert 0 <= sign <= 1 exp = r_ulonglong(exp) sign = r_ulonglong(sign) return ((sign << BITS - 1) | (exp << MANT_DIG - 1)) | mant def pack_float(result, x, size, be): l = [] if be else result unsigned = float_pack(x, size) for i in range(size): l.append(chr((unsigned >> (i * 8)) & 0xFF)) if be: l.reverse() result.append("".join(l)) def unpack_float(s, be): unsigned = r_ulonglong(0) for i in range(len(s)): c = ord(s[len(s) - 1 - i if be else i]) unsigned |= r_ulonglong(c) << (i * 8) return float_unpack(unsigned, len(s)) ```