Search code examples
pythonpython-3.xbinaryfractions

positive and negative fraction to binary conversion


I am trying to convert positive and negative fractions to binary, I found the following approach online (https://www.geeksforgeeks.org/convert-decimal-fraction-binary-number/) to convert positive fractions to binary and tried to extend it to support the negative fractions. I am trying to do the 2's complement on the fractional part. Any help is much appreciated.

# decimal to binary number

# Function to convert decimal to binary
# upto k-precision after decimal point
def decimalToBinary(num, k_prec) :

    binary = ""

    # Fetch the integral part of
    # decimal number
    Integral = int(num)

    # Fetch the fractional part
    # decimal number
    fractional = num - Integral

    # Conversion of integral part to
    # binary equivalent
    while (Integral) :
        
        rem = Integral % 2

        # Append 0 in binary
        binary += str(rem);

        Integral //= 2
    
    # Reverse string to get original
    # binary equivalent
    binary = binary[ : : -1]

    # Append point before conversion
    # of fractional part
    binary += '.'

    # Conversion of fractional part
    # to binary equivalent
    while (k_prec) :
        
        # Find next bit in fraction
        fractional *= 2
        fract_bit = int(fractional)

        if (fract_bit == 1) :
            
            fractional -= fract_bit
            binary += '1'
            
        else :
            binary += '0'

        k_prec -= 1

    if (num < 0): # if negative numbers do the two's complement
        binary = ~binary # struck here.
    else:
        binary = binary
    return binary

# Driver code
if __name__ == "__main__" :
    num_list=[1, 0, 0.924, -0.383]
    for i in num_list:
        print(i, decimalToBinary(i,8))
macky@test:~/test$ python frac_to_binary.py 
(1, '1.00000000')
(0, '.00000000')
(0.924, '.11101100')
Traceback (most recent call last):
  File "frac_to_binary.py", line 63, in <module>
    print(i, decimalToBinary(i,8))
  File "frac_to_binary.py", line 54, in decimalToBinary
    binary = ~binary
TypeError: bad operand type for unary ~: 'str'

Solution

  • There are actually a few ways to display binary numbers as negatives. The important thing to remember is to watch your bit size; set your bit size larger than the max value that you want to convert, or weird things may happen:

    • bits = 4 for numbers <= 7 (0111)
    • bits = 5 for numbers <= 15 (01111)
    • bits = 8 for numbers <= 127 (01111111)

    Another thing to remember is that you have to convert the whole binary representation, not each part.

    I modified your function to display four types of representations: Signed, Sign Magnitude, One's Complement, and Two's Complement:

    NOTE - Tested on CentOS 7.9, using Python 2.7, and on Ubuntu 20.04, using Python 3.8

    """Program to convert rational numbers to signed and unsigned binary representations
    Signed: Add a sign (0001 = 1, -0001 = -1)
    Sign Magnitude: The sign is the most significant bit (0001 = 1, 1001 = -1)
    One's Complement: Flip all the bits to their opposite value (0001 = 1, 1110 = -1)
    Two's Complement: Flip all the bits, then add 1 to the least significant bit (0001 = 1, 1111 = -1)
    
    Signed and decimal values checked at
        https://www.rapidtables.com/convert/number/binary-to-decimal.html
    Complements checked at https://planetcalc.com/747/
    
    Warning: Set your bit size larger than the max value that you want to convert:
    bits = 4 for numbers <= 7 (0111)
    bits = 5 for numbers <= 15 (01111)
    ...
    bits = 8 for numbers <= 127 (01111111)
    """
    
    
    def decimal_to_binary(num, bits):
        """Function to convert rational numbers to binary representations
    
        :param float num: The decimal to convert to binary
        :param int bits: The bit size; see warning in the module docstring
        :return: A formatted string with Signed, Sign Mag, One's Comp, and Two's Comp representations
        :rtype: str
        """
        # Ensure the number is a float
        num *= 1.0
    
        # Separate the number's components
        whole_part = int(str(num).split(".")[0])
        decimal_part = float("." + str(num).split(".")[1])
    
        # Convert the whole part to binary
        if whole_part >= 0:
            whole_binary = format(whole_part, "0{0}b".format(bits))
        else:
            # Remove the sign
            whole_binary = format(whole_part * -1, "0{0}b".format(bits))
    
        # Convert the decimal part to binary
        k_prec = bits
        if decimal_part == 0:
            decimal_binary = format(0, "0{0}b".format(k_prec))
        else:
            db_list = []
            while k_prec > 0:
                decimal_part *= 2
                db_list.append(int(decimal_part))
                decimal_part -= int(decimal_part)
                k_prec -= 1
            decimal_binary = ''.join(str(d) for d in db_list)
    
        # Put the binary representations back together and sign (for Signed and Sign Magnitude)
        binary = whole_binary + "." + decimal_binary
        mag_binary = "0" + binary[1:] if num >= 0 else "1" + binary[1:]
        signed_binary = binary if num >= 0 else "-" + binary
    
        if num >= 0:
            ones_binary = "n/a"
            twos_binary = ""
        else:
            # Create an unsigned binary representation (for Complements)
            raw_binary = whole_binary + decimal_binary
            ones_binary = ""
            # Flip bits; as chepner says, ~ doesn't work on strings
            for c in raw_binary:
                ones_binary += '1' if c == '0' else '0'
            # Add 1 to the least significant digit
            twos_binary = bin(int(ones_binary, 2) + 1)
            # Format for display
            ones_binary = ones_binary.replace("0b", "")
            ones_binary = ones_binary[:bits] + "." + ones_binary[bits:]
            twos_binary = twos_binary.replace("0b", "")
            twos_binary = twos_binary[:bits] + "." + twos_binary[bits:]
    
        return "{0:>8}\t{1:>10}\t{2}\t{3}\t{4}".format(
            num, signed_binary, mag_binary, ones_binary, twos_binary)
    
    
    def main():
        # I am using 4 bits for readability, so I am keeping the numbers below 7
        num_list = [1, 0, 0.924, -0.383, -0.5, -1, ]
        print("Original\t Signed\t    Sign Mag\tOne's Comp\tTwo's Comp")
        for i in num_list:
            print(decimal_to_binary(i, 4))
    
    
    if __name__ == "__main__":
        main()
    

    Output:

    Original     Signed     Sign Mag    One's Comp  Two's Comp
         1.0     0001.0000  0001.0000   n/a 
         0.0     0000.0000  0000.0000   n/a 
       0.924     0000.1110  0000.1110   n/a 
      -0.383    -0000.0110  1000.0110   1111.1001   1111.1010
        -0.5    -0000.1000  1000.1000   1111.0111   1111.1000
        -1.0    -0001.0000  1001.0000   1110.1111   1111.0000
    

    I tested the results using RapidTables' Binary to Decimal converter and PlanetCalc's One's complement, and two's complement binary codes calculator.

    If I had more time, I would break this up into separate functions and streamline a few things, but I leave that up to you. Good luck with your code!