Convert a Negative Decimal Number with a Fractional Part using 2’s Complement in Python

JK.
14 min readJan 12, 2022

2’s Complement Dec-Bin converter.

It’s easy for us to convert from denary to binary or from binary to denary. Have you ever thought about converting a negative denary number that contains a fractional part into a binary form? Well, I had not thought about this until I attended one of the modules in my degree course called Computer Architecture.

Base conversion is interesting, no matter it involves a negative sign or not. So, I would like to share the steps of creating your radix converter with Python today.

Prerequisites

Before we start, you have to install Python on your machine. If you are new to Python, you can watch this tutorial to help you set up Python. Also, don’t forget to install a Python IDE or code editor. I prefer to install a code editor such as VS Code, Sublime, etc.

You would need the fundamentals of base conversion before diving into this post. If you do not have the basics, you can watch this YouTube Tutorial.

Let’s move on!

Part 1: The Fundamentals of Base Conversion (Decimal To Binary)

Section 1.1: The Basics

  1. Convert a denary number (without a fractional part) to binary

We will need to divide a decimal number by 2 repeatedly. In this subsection, I will use a division algorithm or repeat division to illustrate the division. The repeat division looks like a long division, but inverted.

D |__N__
Q -> R
where N = numerator (dividend); D = denominator (divisor);
Q = quotient; R = remainder.

Let’s look at the example below:

If we convert a decimal number, say 101, to binary, it can be illustrated as:

2 |__101__
2 |__ 50__ -> 1 ^
2 |__ 25__ -> 0 |
2 |__ 12__ -> 1 |
2 |__ 6__ -> 0 |
2 |__ 3__ -> 0 |
2 |__ 1__ -> 1 |
2 |__ 0__ -> 1 |

The binary number will be represented by each of the remainders (from bottom to top). Therefore, 101₁₀ = 1100101₂.

Wait! Is this true? Let’s check on the binary table.

Binary Table For 101₁₀

64 + 32 + 4 + 1 = 101.

Yes! It’s true!

Wait for a second! Can we convert decimal point numbers?

2. Convert a denary number (with a fractional part) to binary

We will need to multiply the fractional parts by 2 repeatedly.

m₁ × m₂ = p₁.p₂ | p₁p₂ × m₂ = p₁.p₂ | p₁
...
where m₁ = multiplier; m₂ = multiplicand.However, consider p₁.p₂ as a product as a whole,
where p₁ = the whole part or an integral part of the product;
p₂ = the fractional part of the product.

I know it’s a little confusing, so I’m going to use 0.703125 in this example:

0.703125 × 2 = 1.40625 | 1
0.40625 × 2 = 0.8125 | 0
0.8125 × 2 = 1.625 | 1
0.625 × 2 = 1.25 | 1
0.25 × 2 = 0.5 | 0
0.5 × 2 = 1.0 | 1
0.0 × 2 = 0.0 | 0

As you can see in the example above, when we multiply the fractional part of a decimal number by 2 at first, we will get a product that is either less than or more than 1. If it is more than 1, we need to record 1; if it is less than 1, we need to record 0. Then we repeat the same step by multiplying the fractional part of the product by 2.

The Breakdown of The Repeat Multiplication

The binary number will be represented by each of the whole/integral parts of the product (from top to bottom). Therefore, 0.703125₁₀ = 0.1011010₂.

Wait! Is this true? Let’s check on the binary table.

Binary Table For 0.703125₁₀

0.5 + 0.125 + 0.0625 + 0.015625 = 0.703125.

Yes! It’s true!

However, some fractional numbers might cause infinite results, meaning that you will not get 0.0 in the final product.

Let’s use 0.7 as the example:

0.7 × 2 = 1.4 | 1
0.4 × 2 = 0.8 | 0
0.8 × 2 = 1.6 | 1
0.6 × 2 = 1.2 | 1
0.2 × 2 = 0.4 | 0
0.4 × 2 = 0.8 | 0
0.8 × 2 = 1.6 | 1
0.6 × 2 = 1.2 | 1
...

The binary form of 0.7₁₀ would be 0.10110011₂… In such a case, we will need to limit the bits of the fractional part, which is similar to the decimal place, such as 8-bit (8 digits after decimal points), 16-bit (16 digits after decimal points), 32-bit (32 digits after decimal points), etc. However, limiting the bits will result in an approximate value (you can also round off yourself).

Quiz:
What is the binary form of 101.703125₁₀?

Section 1.2: 2’s Complement

What is Two’s complement? Two’s complement is the most common method of representing signed integers on computers. Note that signed integers represent both positive and negative numbers whereas unsigned integers represent non-negative numbers (zero or positive numbers) only.

Since it expresses the negative numbers in binary form, we will only use this method when converting a negative denary number to binary. The complements are as below:
1’s complement inverts the binary numbers;
2’s complement adds a bit (1) or a fractional bit (0.0…1) to the inverted binary numbers.

  1. Convert a negative denary number (without a fractional part) to binary

In this example, let’s convert a denary number -101 to binary using a signed 8-bit binary.

2 |__101__
2 |__ 50__ -> 1 ^
2 |__ 25__ -> 0 |
2 |__ 12__ -> 1 |
2 |__ 6__ -> 0 |
2 |__ 3__ -> 0 |
2 |__ 1__ -> 1 |
2 |__ 0__ -> 1 |
1’s complement (invert the binary numbers):
1100101 --> 0011010
2’s complement (add 1 to the inverted binary):
0011010
+ 1
--------
0011011

That is … not the final answer! Since we are using signed 8-bit binary, the number after 2’s complement only contains 7 bits, we shall add one bit at the beginning of it, but what bit should we add? 0 or 1? Since the denary number has a negative sign, we shall add 1 as the most significant bit in this case because 1 represents the negative sign (0 represents the positive sign).

Hence, the binary form of -101₁₀ = 10011011₂.

Wait! Is this true? Let’s check on the binary table.

Binary Table For -101₁₀ (Invalid)

128 + 16 + 8 + 2 + 1 = 155

It looks like it is equivalent to 155 if we sum up all the base 10 numbers!

Alright… Here is how we check to see if it’s -101. Remember after converting using 2’s complement, the 1 at the beginning is associated with a negative sign, so we need to add a negative sign to 128.

Binary Table For -101₁₀

-128 + 16 + 8 + 2 + 1 = -101.

Yes! It’s true!

2. Convert a negative denary number (with a fractional part) to binary

Let’s convert a denary number -101.703125 to binary, we will use 8-bit for the whole/integral part and 8-bit for the fractional part of the denary in this example, but first we have to do this separately.

a. Finding the 1’s Complement of the whole/integral part:

// Whole Part / Integral Part:
2 |__101__
2 |__ 50__ -> 1 ^
2 |__ 25__ -> 0 |
2 |__ 12__ -> 1 |
2 |__ 6__ -> 0 |
2 |__ 3__ -> 0 |
2 |__ 1__ -> 1 |
2 |__ 0__ -> 1 |
Add 1 bit at the beginning to make it 8-bit bin:
1100101 -> 01100101
1’s complement (invert the binary numbers):
01100101 --> 10011010

Note that we don’t need to do 2’s complement at the moment. Therefore, the 1’s complement of the whole/integral part is 10011010₂.

Let’s move on to the fractional part!

b. Finding the 1’s Complement of the fractional part:

// Fractional Part
0.703125 × 2 = 1.40625 | 1
0.40625 × 2 = 0.8125 | 0
0.8125 × 2 = 1.625 | 1
0.625 × 2 = 1.25 | 1
0.25 × 2 = 0.5 | 0
0.5 × 2 = 1.0 | 1
0.0 × 2 = 0.0 | 0
Add 1 bit at the end to make it 8-bit bin:
1011010 -> 10110100
1’s complement (invert the binary numbers):
10110100 --> 01001011

Note that we don’t need to do 2’s complement at the moment. Therefore, the 1’s complement of the fractional part is 0.01001011₂.

c. Combining the binaries:

Let’s combine the inverted whole/integral part and the fractional part.

10011010 + 0.01001011 = 10011010.01001011

d. Getting the 2’s Complement:

After we combined the binaries, let’s do the 2’s complement!

 10011010.01001011
+ 0.00000001
------------------
10011010.01001100

Therefore, -101.703125₁₀ = 10011010.01001100₂.

Wait! Is this true, again? Let’s check on the binary table.

Binary Table For -101.703125₁₀

-128 + 16 + 8 + 2 + 0.25 + 0.03125 + 0.015625 = -101.703125.

Yes! It’s true!

Raise Hand: I’m confused!
I know you will ask “why do we add 1 in the example in Section 2 (1) but add 0.00000001 in this example”.

Let’s proceed to the explanation below.

e. How many bits should we add to binary in 2’s complement?

I will still be using the -101 in the explanation.

We all know that the binary of -101 before 1’s complement is 01100101. Let’s look at the scenarios below.

Scenario A:
When we do 1’s complement, it becomes 10011010. Then we do 2’s complement by adding a bit (1), we will get the final result.

 10011010
+00000001
---------
10011011

Scenario B:
The binary of -101 before 1’s complement is 01100101. Let’s imagine it has a fractional part but with multiple zeros even though it seems to only have a whole/integral part, i.e. 01100101 = 01100101.000…

When we do 1’s complement, it becomes 10011010.111… Then we do 2’s complement — we shall add a bit in fraction based on the last decimal place or the least significant bit it has, assuming that we only take up to 3 decimal places.

 10011010.111
+00000000.001 <-- the least significant bit
-------------
10011011.000

When we compare the results from both scenarios, the results are still the same.

In short, during 2’s complement, if the binary only has a whole/integral part, we must add a bit (1); if the binary has a fractional part, we must add a bit in fraction (0.0…1) based on the least significant bit it has.

In conclusion, the 2’s complement is crucial when we convert a negative denary number.

Extra: Why Inversion and Adding One Works?
You may see this awesome explanation.

Quiz:
Let’s have some exercises before we jump into the Python programming tutorial.
1. Convert -112₁₀ into binary using 2’s complement.
2. Convert -98.5625₁₀ into binary using 2’s complement.
3. Convert -55.75₁₀ into binary using 2’s complement, but you have to do two different 2’s complement: one is to add 1 bit, and another is to add a bit with fraction, then compare both of the results.

Hints: Use a binary table.

Part 2: Python Implementation

Section 2.1: Introduction

In this section, I assume you have the fundamentals of Python programming. I will only cover the 2’s complement coding part — dec-bin. I will soon create a post on bin-dec.

Section 2.2: Let’s Build Our Dec-Bin Converter!

We may need to create several functions in the code. It’s okay! Let’s do this together.

Before we start, let’s create a python file and name it as 2s_complement.py.

Then import several libraries:

from itertools import product
import re
import math
  1. Create a function for the division algorithm/repeat division

As discussed in Section 1.1 (1), we are going to translate the logic into code. Let’s create a function called divide_denary_by_2 that accepts a parameter.

DIVIDE DENARY BY 2 REPEATEDLY

In the while loop, denary is divided by 2 repeatedly, meaning that the while loop acts as a repeat division that finds remainders and each remainder will be concatenated. The if statement at line 26 checks if the binary has 8 bits. If it doesn’t, it helps us fill up zeros at the beginning. You may also use zfill() in this case.

Can’t why we just use bin() function that converts denary to binary? No! The reason why we are creating our code is that we want to translate the logic from scratch!

Note that this function does not deal with a negative value. You may see more in the explanation below.

2. Create a function for the 2’s complement

As we know, two’s complement includes 2 parts:
a. 1’s complement inverts the binary numbers;
b. 2’s complement adds a bit (1) to the inverted binary numbers.

So we will now translate this logic into codes!

1s — REPLACE / INVERT NUMBERS, 2s — ADD NUMBER TO BINARY

The first code snippet — multiwordReplace() function inverts all the binary numbers, i.e., replace 1 with 0 or replace 0 with 1. The second code snippet add_binary() function performs binary addition. These two codes are adopted from GeeksForGeeks and somewhere from the Internet, you may see here.

Alright! Let’s move on to the most exciting part!

3. Create a function to convert denary to binary

Let’s create a function to convert a denary number, doesn’t matter it is positive or negative OR it has a decimal point or not.

CONVERT DECIMAL/DENARY TO BINARY

Don’t panic! I know it’s a bit longer. Let me break it down.

  • This function will take a parameter called denary.
  • Let’s start from the top. The variable called inverseBin is a dictionary that stores two keys and two values, each value is the opposite of the key.
  • The if statement at line 21 checks whether the denary number is equivalent to 0. It displays a warning message and returns false because there is no point converting 0 to binary.
  • The if statement at line 25 checks whether the denary number is greater than or less than 0. If the denary is greater than and equal to 0, the value of isNegative will be false, else will be true.
  • denary = abs( denary ) uses an absolute function to remove the negative sign because the functions we have created do not deal with the negative sign.

You’re okay with these? Yes!

The if statement at line 34 checks whether the denary is an integer or a float.

a. So let’s talk about “what if the denary is an integer” first.
We will need to call the divide_denary_by_2( ) function and pass denary to it as an argument. The divide_denary_by_2( ) will do its work and return the binary.

Then we need to check whether isNegative is true or false at line 39. If isNegative is true, we shall proceed to two’s complement — invert the return binary, then add 1 to the inverted binary.

That’s it! The binary that added 1 is the final answer! So what does this code do?

one = “+ “ + one.zfill( 8 )

This code adds multiple zeros in front of “1” in 2’s complement and displays it on the vertical addition!

If isNegative is false, the return binary from divide_denary_by_2( ) would be the final answer!

b. Let’s talk about “what if the denary is a float” now.
The codes here are slightly different. We will first need to separate the float into two parts — a whole part and a fractional part and they are represented by dec_int and dec_frac respectively. Then, we will need to call the divide_denary_by_2( ) function and pass dec_int to it as an argument just like mentioned above. The divide_denary_by_2( ) will do its work and return the binary of dec_int and store it to a variable called binary.

We will then process the fractional part at line 70. Starting from here, we are translating the logic from Section 1.1 (2) that multiplies the fractional part by 2 repeatedly. The products — 0 or 1 will be concatenated but only up to 8 characters (bits) into a string called bin_frac.

We then need to check whether isNegative is true or false at line 85. If isNegative is true, we shall proceed to two’s complement — invert the return binary, then add 1 to the inverted binary, but it’s a little tricky here:
i. first invert binary, then invert bin_frac.
ii. combine binary and bin_frac and pass it to add_binary() function as an argument. Note that this doesn’t have any decimal point yet.
iii. get the return value from add_binary() and assign it in a variable called newBin.
iv. determine the original length of binary and add a decimal point in newBin. The position of the decimal point is determined by the length. Note that this method might not be useful if newBin has an extra bit.

Once newBin added a decimal place, it would be the final answer!

If isNegative is false, we need to concatenate binary and bin_frac with a decimal point. That’s it! That’s the final answer!

4. Create a while loop and try these functions

FUNCTION CALLING

What we need to do is to create a variable called x and assign 0 to it. Then create a while loop that allows users to enter a denary number. Note that the value of user input will always be a string. The if statement at line 7 validates the type of the user input using regular expression and converts it to integer or float.

Also, the if statement at line 18 ensures the converted decimal is in the range — less than 128 or more than -128. Since we are building an 8-bit converter (8-bit for the whole part and 8-bit for the fractional part), therefore numbers greater than max value: 127.99999999 or less than min value -127.99999999 are not allowed. Otherwise, it fails.

TA-DA! You got your dec-bin converter! Let’s try the converter now!

The 2’s Complement Converter

Here are some samples:
1. -55.75₁₀ = 11001000.01000000₂
2. -98.5625₁₀ = 10011101.01110000₂
3. 100₁₀ = 01100100₂

End of the class! Hooray!

GitHub Repo:
Thanks for reading my post! You may go to my repo to download the Python file and run it on your machine. Feel free to fork and give a star to my repo. If you have a good idea or find a bug in my codes, make a pull request and raise an issue at my repo anytime!

THANK YOU! Please rate my post and repo!

Source:
1. npburns224. (2020) Converting fractional 2’s complement value to decimal. [Online] Available from: https://math.stackexchange.com/questions/3547196/converting-fractional-2s-complement-value-to-decimal. [Accessed: 09th January 2022].
2. Tropical_Peach. (2012) 2’s complement representation of fractions? [Online] Available from: https://math.stackexchange.com/questions/3547196/converting-fractional-2s-complement-value-to-decimal. [Accessed: 09th January 2022].
3. The Organic Chemistry Tutor. (2019) Binary Addition and Subtraction With Negative Numbers, 2’s Complements & Signed Magnitude. [Online] Available from: https://www.youtube.com/watch?v=sJXTo3EZoxM. [Accessed: 09th January 2022].
4. Computer Science. (2017) Binary 3 — Fixed Point Binary Fractions. [Online] Available from: https://www.youtube.com/watch?v=QFlbvSeBkwY. [Accessed: 09th January 2022].
5. Guru gcsecs. (2020) Most significant bit/Least significant bit. [Online] Available from: https://www.youtube.com/watch?v=KReHvV2SQj0. [Accessed: 09th January 2022].
6. Finley, T. (2000) Two’s Complement. [Online] Available from: https://www.cs.cornell.edu/~tomf/notes/cps104/twoscomp.html#:~:text=which%20is%20correct.-,Why%20Inversion%20and%20Adding%20One%20Works,-Invert%20and%20add. [Accessed: 09th January 2022].

--

--

JK.

SWE & Blockchain Enthusiast. Looks like I am a tech guy, but I am more. 😉 Twitter: https://twitter.com/chk_jacky