pqcprep.binary_tools
Collection of functions relating to binary encoding and decoding.
1""" 2Collection of functions relating to binary encoding and decoding. 3""" 4 5import numpy as np 6 7def dec_to_bin(digits,n,encoding='unsigned mag',nint=None, overflow_error: bool=True): 8 """ 9 Encode a base-10 float to a binary string. 10 11 The fractional part is rounded to the available precision. Little endian convention 12 is used. 13 14 Arguments: 15 ---------- 16 - **digits** : *float* 17 18 The float to encode. 19 20 - **n** : *int* 21 22 The number of bits in the binary string 23 24 - **encoding** : *str* 25 26 The type of binary encoding used. Possible options are `'unsigned mag'` for unsigned magnitude 27 encoding, `'signed mag'` for signed magnitude encoding, and `'twos comp'` for two's complement 28 representation. Default is `'unsigned mag'`. 29 30 - **nint** : *int*, *optional* 31 32 Number of integer bits used in the encoding. If `nint` is not given all bits are taken to be 33 integer bits. 34 35 - **overflow_error** : *bool* 36 37 Raises a `ValueError` if `digits` lies outside the available range for the given encoding. Default 38 is True. 39 40 Returns: 41 -------- 42 - **bits** : *str* 43 44 The binary string representing `digits`. 45 46 """ 47 48 # set number of integer bits 49 if nint==None: 50 nint=n 51 52 # one bit is reserved to store the sign for signed encodings 53 if nint==n and (encoding=='signed mag' or encoding=='twos comp'): 54 nint-= 1 55 56 # determine number of precision bits 57 if encoding=='signed mag' or encoding=='twos comp': 58 p = n - nint - 1 59 elif encoding=='unsigned mag': 60 p = n - nint 61 else: 62 raise ValueError("Unrecognised type of binary encoding. Should be 'unsigned mag', 'signed mag', or 'twos comp'.") 63 64 # raise overflow error if float is out of range for given encoding 65 if overflow_error: 66 if encoding=='unsigned mag': 67 min_val = 0 68 max_val = (2.**nint) - (2.**(-p)) 69 elif encoding=='signed mag': 70 min_val = - (2.**nint - 2.**(-p)) 71 max_val = + (2.**nint - 2.**(-p)) 72 elif encoding=='twos comp': 73 min_val = - (2.**nint) 74 max_val = + (2.**nint - 2.**(-p)) 75 76 if (digits>max_val and nint != 0) or digits<min_val: 77 raise ValueError(f"Float {digits} out of available range [{min_val},{max_val}].") 78 79 # take absolute value and separate integer and fractional part: 80 digits_int = np.modf(np.abs(digits))[1] 81 digits_frac = np.modf(np.abs(digits))[0] 82 83 # add fractional parts 84 bits_frac='' 85 for i in range(p): 86 bits_frac +=str(int(np.modf(digits_frac * 2)[1])) 87 digits_frac =np.modf(digits_frac * 2)[0] 88 89 # add integer parts 90 bits_int='' 91 for i in range(nint): 92 bits_int +=str(int(digits_int % 2)) 93 digits_int = digits_int // 2 94 95 # reverse array (little endian convention) 96 bits_int= bits_int[::-1] 97 98 # assemble bit string 99 if encoding=="unsigned mag": 100 bits = bits_int + bits_frac 101 if encoding=="signed mag": 102 if digits >= 0: 103 bits = '0' + bits_int + bits_frac 104 else: 105 bits = '1' + bits_int + bits_frac 106 if encoding=="twos comp": 107 if digits >=0: 108 bits = '0' + bits_int + bits_frac 109 elif digits== min_val: 110 bits = '1' + bits_int + bits_frac 111 else: 112 bits = twos_complement('0' + bits_int + bits_frac) 113 114 return bits 115 116def bin_to_dec(bits,encoding='unsigned mag',nint=None): 117 """ 118 Decode a binary string to a float in base-10. 119 120 Little endian convention is used. 121 122 Arguments: 123 ---------- 124 - **bits** : *str* 125 126 The binary string to decode. 127 128 - **encoding** : *str* 129 130 The type of binary encoding used. Possible options are `'unsigned mag'` for unsigned magnitude 131 encoding, `'signed mag'` for signed magnitude encoding, and `'twos comp'` for two's complement 132 representation. Default is `'unsigned mag'`. 133 134 - **nint** : *int*, *optional* 135 136 Number of integer bits used in the encoding. If `nint` is not given all bits are taken to be 137 integer bits. 138 139 Returns: 140 -------- 141 - **digits** : *float* 142 143 The base-10 float represented by `bits`. 144 145 """ 146 147 # get integer bits 148 n = len(bits) 149 if nint==None: 150 nint=n 151 152 # reverse bit string (little endian convention) 153 bits=bits[::-1] 154 155 # cast as array of integers 156 bit_arr = np.array(list(bits)).astype('int') 157 158 # decode binary string 159 if encoding=="unsigned mag": 160 p = n - nint 161 digits = np.sum([bit_arr[i] * (2.**(i-p)) for i in range(n)]) 162 elif encoding=="signed mag": 163 if nint==n: 164 nint -= 1 165 p = n - nint -1 166 digits = ((-1.)**bit_arr[-1] ) * np.sum([bit_arr[i] * (2.**(i-p)) for i in range(n-1)]) 167 elif encoding=="twos comp": 168 if nint==n: 169 nint -= 1 170 p = n - nint -1 171 digits = (-1.)*bit_arr[-1]*2**nint + np.sum([bit_arr[i] * (2.**(i-p)) for i in range(n-1)]) 172 else: 173 raise ValueError("Unrecognised type of binary encoding. Should be 'unsigned mag', 'signed mag', or 'twos comp'.") 174 175 return digits 176 177def twos_complement(binary): 178 """ 179 Calculate the [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) of a bit string. 180 181 Little endian convention is used. An all-zero bit string is its own complement. 182 183 Arguments: 184 ---------- 185 186 - **binary** : *str* 187 188 The binary string. 189 190 Returns: 191 -------- 192 193 - **compl** : *str* 194 195 The two's complement of `binary`. 196 197 """ 198 # cast as list of integers 199 binary_to_array = np.array(list(binary)).astype(int) 200 201 # check if bit string is all zeros and return itself if True 202 if np.sum(binary_to_array)==0: 203 return binary 204 205 # calculate two's complement 206 inverted_bits = ''.join((np.logical_not(binary_to_array).astype(int)).astype(str)) 207 compl = dec_to_bin(bin_to_dec(inverted_bits, encoding='unsigned mag')+ 1,len(binary),encoding='unsigned mag') 208 209 return compl
8def dec_to_bin(digits,n,encoding='unsigned mag',nint=None, overflow_error: bool=True): 9 """ 10 Encode a base-10 float to a binary string. 11 12 The fractional part is rounded to the available precision. Little endian convention 13 is used. 14 15 Arguments: 16 ---------- 17 - **digits** : *float* 18 19 The float to encode. 20 21 - **n** : *int* 22 23 The number of bits in the binary string 24 25 - **encoding** : *str* 26 27 The type of binary encoding used. Possible options are `'unsigned mag'` for unsigned magnitude 28 encoding, `'signed mag'` for signed magnitude encoding, and `'twos comp'` for two's complement 29 representation. Default is `'unsigned mag'`. 30 31 - **nint** : *int*, *optional* 32 33 Number of integer bits used in the encoding. If `nint` is not given all bits are taken to be 34 integer bits. 35 36 - **overflow_error** : *bool* 37 38 Raises a `ValueError` if `digits` lies outside the available range for the given encoding. Default 39 is True. 40 41 Returns: 42 -------- 43 - **bits** : *str* 44 45 The binary string representing `digits`. 46 47 """ 48 49 # set number of integer bits 50 if nint==None: 51 nint=n 52 53 # one bit is reserved to store the sign for signed encodings 54 if nint==n and (encoding=='signed mag' or encoding=='twos comp'): 55 nint-= 1 56 57 # determine number of precision bits 58 if encoding=='signed mag' or encoding=='twos comp': 59 p = n - nint - 1 60 elif encoding=='unsigned mag': 61 p = n - nint 62 else: 63 raise ValueError("Unrecognised type of binary encoding. Should be 'unsigned mag', 'signed mag', or 'twos comp'.") 64 65 # raise overflow error if float is out of range for given encoding 66 if overflow_error: 67 if encoding=='unsigned mag': 68 min_val = 0 69 max_val = (2.**nint) - (2.**(-p)) 70 elif encoding=='signed mag': 71 min_val = - (2.**nint - 2.**(-p)) 72 max_val = + (2.**nint - 2.**(-p)) 73 elif encoding=='twos comp': 74 min_val = - (2.**nint) 75 max_val = + (2.**nint - 2.**(-p)) 76 77 if (digits>max_val and nint != 0) or digits<min_val: 78 raise ValueError(f"Float {digits} out of available range [{min_val},{max_val}].") 79 80 # take absolute value and separate integer and fractional part: 81 digits_int = np.modf(np.abs(digits))[1] 82 digits_frac = np.modf(np.abs(digits))[0] 83 84 # add fractional parts 85 bits_frac='' 86 for i in range(p): 87 bits_frac +=str(int(np.modf(digits_frac * 2)[1])) 88 digits_frac =np.modf(digits_frac * 2)[0] 89 90 # add integer parts 91 bits_int='' 92 for i in range(nint): 93 bits_int +=str(int(digits_int % 2)) 94 digits_int = digits_int // 2 95 96 # reverse array (little endian convention) 97 bits_int= bits_int[::-1] 98 99 # assemble bit string 100 if encoding=="unsigned mag": 101 bits = bits_int + bits_frac 102 if encoding=="signed mag": 103 if digits >= 0: 104 bits = '0' + bits_int + bits_frac 105 else: 106 bits = '1' + bits_int + bits_frac 107 if encoding=="twos comp": 108 if digits >=0: 109 bits = '0' + bits_int + bits_frac 110 elif digits== min_val: 111 bits = '1' + bits_int + bits_frac 112 else: 113 bits = twos_complement('0' + bits_int + bits_frac) 114 115 return bits
Encode a base-10 float to a binary string.
The fractional part is rounded to the available precision. Little endian convention is used.
Arguments:
digits : float
The float to encode.
n : int
The number of bits in the binary string
encoding : str
The type of binary encoding used. Possible options are
'unsigned mag'for unsigned magnitude encoding,'signed mag'for signed magnitude encoding, and'twos comp'for two's complement representation. Default is'unsigned mag'.nint : int, optional
Number of integer bits used in the encoding. If
nintis not given all bits are taken to be integer bits.overflow_error : bool
Raises a
ValueErrorifdigitslies outside the available range for the given encoding. Default is True.
Returns:
bits : str
The binary string representing
digits.
117def bin_to_dec(bits,encoding='unsigned mag',nint=None): 118 """ 119 Decode a binary string to a float in base-10. 120 121 Little endian convention is used. 122 123 Arguments: 124 ---------- 125 - **bits** : *str* 126 127 The binary string to decode. 128 129 - **encoding** : *str* 130 131 The type of binary encoding used. Possible options are `'unsigned mag'` for unsigned magnitude 132 encoding, `'signed mag'` for signed magnitude encoding, and `'twos comp'` for two's complement 133 representation. Default is `'unsigned mag'`. 134 135 - **nint** : *int*, *optional* 136 137 Number of integer bits used in the encoding. If `nint` is not given all bits are taken to be 138 integer bits. 139 140 Returns: 141 -------- 142 - **digits** : *float* 143 144 The base-10 float represented by `bits`. 145 146 """ 147 148 # get integer bits 149 n = len(bits) 150 if nint==None: 151 nint=n 152 153 # reverse bit string (little endian convention) 154 bits=bits[::-1] 155 156 # cast as array of integers 157 bit_arr = np.array(list(bits)).astype('int') 158 159 # decode binary string 160 if encoding=="unsigned mag": 161 p = n - nint 162 digits = np.sum([bit_arr[i] * (2.**(i-p)) for i in range(n)]) 163 elif encoding=="signed mag": 164 if nint==n: 165 nint -= 1 166 p = n - nint -1 167 digits = ((-1.)**bit_arr[-1] ) * np.sum([bit_arr[i] * (2.**(i-p)) for i in range(n-1)]) 168 elif encoding=="twos comp": 169 if nint==n: 170 nint -= 1 171 p = n - nint -1 172 digits = (-1.)*bit_arr[-1]*2**nint + np.sum([bit_arr[i] * (2.**(i-p)) for i in range(n-1)]) 173 else: 174 raise ValueError("Unrecognised type of binary encoding. Should be 'unsigned mag', 'signed mag', or 'twos comp'.") 175 176 return digits
Decode a binary string to a float in base-10.
Little endian convention is used.
Arguments:
bits : str
The binary string to decode.
encoding : str
The type of binary encoding used. Possible options are
'unsigned mag'for unsigned magnitude encoding,'signed mag'for signed magnitude encoding, and'twos comp'for two's complement representation. Default is'unsigned mag'.nint : int, optional
Number of integer bits used in the encoding. If
nintis not given all bits are taken to be integer bits.
Returns:
digits : float
The base-10 float represented by
bits.
178def twos_complement(binary): 179 """ 180 Calculate the [two's complement](https://en.wikipedia.org/wiki/Two%27s_complement) of a bit string. 181 182 Little endian convention is used. An all-zero bit string is its own complement. 183 184 Arguments: 185 ---------- 186 187 - **binary** : *str* 188 189 The binary string. 190 191 Returns: 192 -------- 193 194 - **compl** : *str* 195 196 The two's complement of `binary`. 197 198 """ 199 # cast as list of integers 200 binary_to_array = np.array(list(binary)).astype(int) 201 202 # check if bit string is all zeros and return itself if True 203 if np.sum(binary_to_array)==0: 204 return binary 205 206 # calculate two's complement 207 inverted_bits = ''.join((np.logical_not(binary_to_array).astype(int)).astype(str)) 208 compl = dec_to_bin(bin_to_dec(inverted_bits, encoding='unsigned mag')+ 1,len(binary),encoding='unsigned mag') 209 210 return compl
Calculate the two's complement of a bit string.
Little endian convention is used. An all-zero bit string is its own complement.
Arguments:
binary : str
The binary string.
Returns:
compl : str
The two's complement of
binary.