Dividing Binary Numbers - Part 2- 5 mins
We started the journey towards optimizing the division operation within a decimal number implementation in Rust last time by investigating long division. This week we take a look at a second way to perform division upon binary numbers using the “two’s complement” method:
- Part 1 - Division using Long Division
- Part 2 - Division using Two’s Complement
- Part 3 - A simple implementation of division in Rust
- Part 4 - A decimal implementation in Rust
- Part 5 - Optimizing the decimal implementation in Rust
The basic concept
An alternative method for dividing a number in any base is to simply keep subtracting the divisor from the dividend. Each time you’re able to successfully subtract the full divisor you incrememt the quotient. At the very end, you’re left with the remainder.
As an example, let’s work with :
We were able to subtract from three times with a remainder of one:
It seems a little simplistic however I’m sure the programmers reading this will be able to notice some loop logic present with this concept. Subtraction isn’t the easiest operation to perform with binary numbers so how can we leverage the binary representation of a negative number to optimize this?
Signed binary number concepts
The binary value could represent various integers depending on the number representation being used. As an unsigned integer, it is fairly trivial to calculate what the number represents using . Typically when dealing with sign however, the first bit is used to express whether the number is positive or negative. Given the first bit of the number above is it could be assumed that the number is negative.
How the next bits are interpreted depend on the number representation being used. Two common systems are one’s complement and two’s complement. In one’s complement this binary number would represent however using a two’s complement system this would be .
First of all, it’s useful to understand the basis behind one’s complement. Fundamentally, if you reverse the bits of an integer then you’ll get the negative representation.
If we were to take astep back from the example above, we know that is represented as in binary. If we flip the bits we get . Using our simple “negation” logic means that we now have . Seems like a pretty straight forward operation; so what’s the catch?
To ensure that this rule can always be upheld, we unfortunately need to represent both (as ) as well as (). Given that it seems like a waste of space having to represent the same number twice. Enter the need for two’s complement.
Two’s complement is the most common method of representing signed integers in a computer system. The general idea is that to get the negative of a number we inverse the bits and add one. Consider ; if we reverse the bits and add one we get (i.e. ). This was previously the number that represented in our one’s complement system and consequently our range of numbers has now increased by one (i.e. from through to as opposed to being capped at ).
What if we applied this logic to ? Well, if we take the inverse and add one we essentially get . This of course is an overflow situation however if we discard the carry bit, we’re left with what we expect: .
So why are we talking about two’s complement? In the introduction we talked about using subtraction as a method of calculating the quotient for a division operation. By calculating the two’s complement of a number, we can now avoid having to do a subtraction but instead perform simple addition. How so?
Consider we have the number () and we want to subtract (). If we calculate the two’s complement representation of we get . Now if we add these two numbers together and throw away the carry bit we get:
This of course is what we expect: .
Combining these concepts
Now that we know that two’s complement can assist us with subtraction, we can continue to combine the basic concept above to perform division. Let’s use the original example of .
Firstly, is represented in binary as and is represented as . Consequently, the two’s complement representation of is .
Now if we add together these two numbers and discard the carry bit we get:
is smaller than so we should try again:
Once again, is smaller than so we try again:
We can now see that is bigger than so we can stop. Consequently as expected, we have a quotient of () and a remainder of ().
Next steps: implementing the algorithm
That’s it for today; next time we’ll go into how to implement integral division using the Rust language. Once we’ve covered the basics of integral division we’ll investigate how we can represent high precision fractions.