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
def dec_to_bin( digits, n, encoding='unsigned mag', nint=None, overflow_error: bool = True):
  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 nint is not given all bits are taken to be integer bits.

  • overflow_error : bool

    Raises a ValueError if digits lies outside the available range for the given encoding. Default is True.

Returns:

  • bits : str

    The binary string representing digits.

def bin_to_dec(bits, encoding='unsigned mag', nint=None):
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 nint is not given all bits are taken to be integer bits.

Returns:

  • digits : float

    The base-10 float represented by bits.

def twos_complement(binary):
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.