



Study with the several resources on Docsity
Earn points by helping other students or get them with a premium plan
Prepare for your exams
Study with the several resources on Docsity
Earn points to download
Earn points by helping other students or get them with a premium plan
This odometer model turns out to be the key to how computers represent negative numbers. If we are working with six digit decimal numbers only, ...
Typology: Exercises
1 / 7
This page cannot be seen from the preview
Don't miss anything!




To motivate our representation of negative numbers, let me take you back to your childhood again. Remember driving in your parent’s car and looking at the odometer.^1 Remember the excitement you felt when the odometer turned from 009999 to 010000. Even more exciting was to see the odometer go from 999999 to 000000, if you were so lucky. This odometer model turns out to be the key to how computers represent negative numbers. If we are working with six digit decimal numbers only, then we might say that 999999 behaves the same as -1. The reason is that if we add 1 to 999999 then we get 000000 which is zero.
999999
Notice that we are ignoring a seventh digit in the sum (a carry over). We do so because we are restricting ourselves to six digits. Take another six digit number 328769. Let’s look for the number that, when added to 328769, gives us 000000. By inspection, we can determine this number to be 671231.
328769
Thus, on a six digit odometer, 651231 behaves the same as -328769. Let’s apply the same idea to binary representations of integers. Consider eight bit numbers. Let’s look at 26ten = 00011010two. How do we represent − (^26) ten?
00011010
To find −26, we need to find the number that, when added to 00011010 gives us 00000000. We use the following trick. If we “invert” each of the bits of 26 and add it to 26, we get
00011010 ← 26
This is not quite right, but its close. We just need to add 1 more. (Remember the odometer).
11111111
Note we have thrown away the ninth bit because we are restricting ourself to eight bits only. Thus,
(^1) Ok, ok, odometers were used back in the 20th century, before you were born..
11100101 ← inverted bits
Alternatively, we add 1 to the inverted bit representation and this must give us −26.
11100101 ← inverted bits
00011010 11100110 ← This is − 26 10. 00000000
Thus, to represent the negative of a binary number, we invert each of the bits and then add 1. This is called the twos complement representation.
One special case is to check is that the twos complement of 00000000 is indeed 00000000.
00000000 11111111 ← invert bits 11111111
And adding 1 gets us back to zero. This makes sense, since -0 = 0. Another special case is the decimal number 128, and again we assume a 8 bit representation. If you write 128 in 8-bit binary, you get 10000000. Note that taking the twos complement gives you back the same number 10000000.
10000000
And adding 1 gets us back to zero. So, what is -128?
01111111 ← the inverted bits
Note that 128 has the same representation as -128. Of course, we can’t have both: we have to decide on one. Either 10000000 represents 128 or it represents -128. How does that work?
If n=16, the corresponding table is:
binary signed unsigned 0000000000000000 0 0 0000000000000001 1 1 : : : 0000000001111111 127 127 0000000010000000 128 128 0000000010000001 129 129 : : 0111111111111111 215 − 1 215 − 1 1000000000000000 − 215 215 1000000000000001 − 215 + 1 215 + 1 : : 1111111101111111 -129 216 − 129 1111111110000000 -128 216 − 128 1111111110000001 -127 216 − 127 : : 1111111111111111 -1 216 − 1
Take n = 32. The largest signed integer is thus 2^31 − 1. In Java (and C), the type int defines a 32 bit signed number. Let’s explore the limits on this representation. First, note that 2^10 = 1024 ≈ 103 , i.e. one thousand, and 2^20 ≈ 106 or one million, and 2^30 ≈ 109 or one billion. So, 2^31 ≈ 2 , 000 , 000 , 000 or two billion and in fact 2^31 is a bit more than two billion. What if we declare:
int j = 4000000000; // 4 billion > 2^
This gives a compiler error. ”The literal of type int is out of range.” The compiler knows that 4,000,000,000 is greater than 2^31 − 1. Now try:
int j = 2000000000; // 2 billion < 2^ System.out.println( 2 * j );
This prints out -294967296. To understand why these particular digits are printed, you would need to convert 4000000000 to binary, which I don’t recommend because it is tedious. The point is that it is a negative number! This can easily happen if you are not careful, and obviously it can lead to problems.
Let’s next talk about binary representations of fractional numbers, that is, numbers that lie between the integers. Take a decimal number such as 22.63. We write this as:
The “.” is called the decimal point. We can use a similar representation for fractional binary numbers. For example,
(110.011)two = 1 ∗ 22 + 1 ∗ 21 + 0 ∗ 20 + 0 ∗ 2 −^1 + 1 ∗ 2 −^2 + 1 ∗ 2 −^3
where “.” is called the binary point. If we convert to decimal, we get
4 + 2 + 0.25 + 0. 125 = 6. 375
Binary to decimal conversion
Just as with integers, we can convert a binary number into a decimal number using the brute force method, namely remember or figure out the powers of 2 and then add up all contributions from 1 bits. This is relatively easy to do for simple examples such as above. What about for examples that have far more bits to the right of the binary point?
Decimal to binary conversion
Take the example 22.63 above. We can convert the number to the left of the decimal point from decimal to binary, using the method from lecture 1, namely 22 = (10110) 2. But how do we convert the fractional part (.63) to binary? The idea is to use the fact that multiplication by 2 in binary produces a shift to the left by one bit. (i.e. Multiplying by 2 in binary just adds 1 to each exponent in the sum of powers of 2, and this corresponds to a shift of bits to the left.) We convert 0.63 to binary as follows. To the left of the binary point, we represent the number in binary. (Assume the number is positive. We’ll deal with negative numbers next lecture.) To the right of the binary point, we represent the fractional part in decimal. To go from one line to the next, we multiply by 2 and divide by 2. Specifically we multiple the fractional decimal part by 2, and keep track of the number of divisions by 2 by incrementing the exponent of a power of 2. To the left of the binary point, we shift by one bit and we fill the least significant bit with 1 or 0 depending on whether the (doubled) fractional part is greater than 1. You should verify why this makes sense, namely think what happens when we multiply by 2.
. 63 = (1) 2. 26 × 2 −^1 = (10) 2. 52 × 2 −^2 = (101) 2. 04 × 2 −^3 = (1010) 2. 08 × 2 −^4 = (10100) 2. 16 × 2 −^5 = (101000) 2. 32 × 2 −^6 = (1010000) 2. 64 × 2 −^7 = (10100001) 2. 28 × 2 −^8 = (101000010) 2. 56 × 2 −^9 = etc. etc Notice that the number of bits on the left of the binary point is 9, corresponding to the shift by 9 bits which is implied by the exponent of 2−^9. Finishing the example, ... since 22 in binary is 10110, we have
We commonly write hexadecimal numbers as 0x where the underline is filled with char- acters from 0,...,9,a,b,c,d,e,f. For example,
0x2fa3 = 0010 1111 1010 0011.
Sometimes hexadecimal numbers are written with capital letters. In that case, a large X is used as in the example 0X2FA3. If the number of bits is not a multiple of 4, then you group starting at the rightmost bit (the least significant bit). For example, if we have six bits string 101011 , then we represent it as 0x2b. Note that looking at this hexadecimal representation, we don’t know if it represents 6 bits or 7 or 8, that is, 101011 or 0101011 or 00101011. But usually this is clear from the context.