Mathematics for Computing 201-N11 • CÉGEP in Montreal

Corrected exercises: binary arithmetic and machine numbers (201-N11)

This is the English-language set of corrected exercises on binary arithmetic for Mathematics for Computing 201-N11, the concentration course taken at cégep in Montreal by students in computer science and computer technology programs. Part A is the mechanics: addition and its carry chains, subtraction by borrowing and then by adding a two's complement, multiplication and division by shifting with the trap of the logical against the arithmetic right shift, the two's complement wheel and its asymmetric range, and the reason a carry out and a signed overflow are two different diagnostics. Part B works at exam level: hexadecimal masks on a device status register, binary fractions and Q4.4 fixed point with an error that compounds, a 12-bit sensor whose readings are widened wrongly, and the IEEE 754 single precision format built and decoded bit by bit.

The thread running through the set: a bit pattern carries no meaning of its own. The adder performs the same operation whatever you intended, and every question about whether the result is right is a question about the reading you chose. That is why the same eight bits are 200200 and 56-56, why the flag you must test changes with the declared type, and why widening a number is not the same operation for a signed and an unsigned value.

The traps named explicitly in the solutions: reading the successive multiplications of a fraction in the wrong direction, subtracting 6553665536 from a 12-bit field whose wheel has only 40964096 patterns, treating the carry out as a general error flag, testing a two-flag mask against zero instead of against the mask, and expecting a float to hold every integer a 32-bit integer holds.

10 corrected exercises • 100 points • 150 minutes

Course recap

  • Binary addition: 1+1=1021+1 = 10_2, so write 00 and carry 11. The longest carry chain is all ones plus one, and it is what sets the delay of a ripple-carry adder.
  • Subtracting is adding: ab=a+(two’s complement of b)a - b = a + (\text{two's complement of } b), and the ninth bit that comes out is the 2n2^{n} that the complement put in.
  • Two's complement of xx on nn bits: invert every bit, add 11. The pattern obtained is 2nx2^{n}-x.
  • Signed range on nn bits: 2n1-2^{n-1} to 2n112^{n-1}-1, asymmetric because zero has a single representation and sits on the positive side.
  • Shift left by kk multiplies by 2k2^{k}. Shift right by kk divides by 2k2^{k}: logically for unsigned values, arithmetically, sign bit replicated, for signed ones.
  • Carry out condemns an UNSIGNED result. Overflow condemns a SIGNED one: same sign in, opposite sign out. Neither implies the other.
  • Widening a signed value means sign extension, copying the sign bit left. Filling with zeros turns a small negative number into a large positive one.
  • Masks: OR to set bits, AND with the complement to clear them, XOR to toggle them. One hexadecimal digit is exactly four bits.
  • A decimal fraction has a finite binary expansion only if its reduced denominator is a power of 22. So 0.10.1 never terminates.
  • IEEE 754 single precision: 11 sign bit, 88 exponent bits biased by 127127, 2323 stored significand bits plus an implied leading 11. Integers are exact up to 2242^{24}.

Part A: the mechanics (/50)

Exercise 1: Adding in binary, column by column

Binary addition has exactly four one-bit cases: 0+0=00+0=0, 0+1=10+1=1, 1+0=11+0=1, and 1+1=1021+1=10_2, which writes a 00 and carries a 11 into the next column on the left. Everything else is that rule repeated. The layout below shows the weights of an 8-bit word above the columns, so that every answer can be checked in decimal without redoing the addition.

weightcarry inA+ Bsum12864321684211000111001101101
  • a) Add 101101102+00101011210110110_2 + 00101011_2 on 8 bits. Fill in the carry row, then check your result in decimal.
  • b) Add 11112+121111_2 + 1_2. What happens if the machine only has 4 bits for the result?
  • c) Add the three numbers 10112+11012+11121011_2 + 1101_2 + 111_2 in one column layout. What is different about the carries?
  • d) Add 100112+1101210011_2 + 1101_2 and confirm the sum by weights rather than by decimal conversion.
  • e) A carry can travel across the whole word. Give the 8-bit pair that makes the chain as long as possible, and say what that costs a real adder circuit.
Show the solution

a) Work right to left. Column of weight 1: 0+1=10+1=1, no carry. Weight 2: 1+1=1021+1=10_2, write 00 and carry 11. Weight 4: 1+0+1=1021+0+1=10_2, write 00, carry 11. Weight 8: 0+1+1=1020+1+1=10_2, write 00, carry 11. Weight 16: 1+0+1=1021+0+1=10_2, write 00, carry 11. Weight 32: 1+1+1=1121+1+1=11_2, write 11, carry 11. Weight 64: 0+0+1=10+0+1=1, no carry. Weight 128: 1+0+0=11+0+0=1. Result 11100001211100001_2. Decimal check: 101101102=128+32+16+4+2=18210110110_2 = 128+32+16+4+2 = 182 and 001010112=32+8+2+1=4300101011_2 = 32+8+2+1 = 43, so the sum must be 225225; and 111000012=128+64+32+1=22511100001_2 = 128+64+32+1 = 225. Two independent routes agree, which is the whole point of writing the weights above the columns.

b) 11112=151111_2 = 15 and adding 11 gives 16=10000216 = 10000_2, which needs five bits. On a 4-bit register the stored result is 00000000 and the fifth bit leaves the adder as a carry out. The value 1616 is not lost because the adder failed; it is lost because nobody kept the carry. This is the difference between the arithmetic, which is always right, and the storage, which has a width.

c) 10112=111011_2 = 11, 11012=131101_2 = 13, 1112=7111_2 = 7, so the sum is 31=11111231 = 11111_2. Verify: weight 1 column has three 11s, that is 11211_2, so write 11 and carry 11; weight 2 has 1+0+11+0+1 plus the carry, that is 11211_2 again. With three operands a column can produce a carry of 22, not just 11, so a carry may have to jump two columns. That is exactly why hardware adds numbers two at a time rather than in one wide gulp.

d) 100112+1101210011_2 + 1101_2. Align on the right: 10011210011_2 has weights 16+2+116+2+1 and 110121101_2 has 8+4+18+4+1. Adding the weights gives 16+8+4+2+1+1=3216+8+4+2+1+1 = 32, and 32=100000232 = 100000_2. The posed addition gives the same: the two 11s of weight 1 make a carry into weight 2, which then meets 1+01+0, carries again, and the carry ripples all the way to weight 32. Checking by weights is the faster control here because the answer is a single power of two, which is instantly recognisable.

e) The longest chain is 111111112+00000001211111111_2 + 00000001_2: the carry born in the rightmost column is regenerated by every column it enters and only stops after leaving the leftmost one. The sum is 256=1000000002256 = 100000000_2. In a ripple-carry adder each column has to wait for the previous carry, so the delay of the whole circuit is set by this worst case and grows with the word width. That is the reason processors use carry-lookahead adders, which compute the carries in parallel from the operand bits instead of passing them along.

weightcarry inA+ Bsum128643216842110100111111101001101010110011

Exercise 2: Subtracting: by borrowing, then by adding instead

There are two ways to subtract in binary. The school way borrows from the column on the left, exactly as in decimal except that a borrow is worth 22 and not 1010. The machine way never subtracts at all: it adds the two's complement of the second operand, which is why a processor needs only one adder for both operations. Both give the same answer, and being able to move between them is what this exercise trains.

  • a) Compute 1101021011211010_2 - 1011_2 by borrowing. Check in decimal.
  • b) Redo the same subtraction on 8 bits by adding the two's complement of 101121011_2. Show the complement step by step.
  • c) Compute 10000002121000000_2 - 1_2 by borrowing. How many columns does the borrow cross?
  • d) On 8 bits, compute 00000101200001101200000101_2 - 00001101_2 by the complement method. Read the answer as a signed number.
  • e) In b) and d) a ninth bit came out of the adder. Explain why discarding it is correct here, and name the one case where it must not be discarded.
Show the solution

a) 1101021011211010_2 - 1011_2, aligned on the right. Weight 1: 010-1 is impossible, borrow from weight 2. That column holds 11, so it becomes 00 and weight 1 becomes 102=210_2 = 2; then 21=12-1 = 1. Weight 2: now 010-1, borrow again from weight 4, which holds 00, so the borrow has to come from weight 8. Carrying the borrow through gives 11 at weight 2 and 11 at weight 4. Weight 8: 010-1 after the borrow, take from weight 16, giving 11. Result 111121111_2. Decimal check: 110102=2611010_2 = 26, 10112=111011_2 = 11, and 2611=15=1111226-11 = 15 = 1111_2. Borrowing across a run of zeros is where mistakes happen, and the decimal check is what catches them.

b) Two's complement of 00001011200001011_2 on 8 bits: invert every bit to get 11110100211110100_2, then add 11 to get 11110101211110101_2. As an unsigned pattern that is 245245, and 245=25611245 = 256-11, which is the definition of the complement on 8 bits. Now add: 000110102+111101012=26+245=27100011010_2 + 11110101_2 = 26 + 245 = 271. In binary 271=1000011112271 = 100001111_2, nine bits. Keep the low eight: 000011112=1500001111_2 = 15. Same answer as a), obtained without a single borrow.

c) 10000002=641000000_2 = 64 and 12=11_2 = 1, so the answer is 63=111111263 = 111111_2. The borrow starts at weight 1 and finds a zero in every column until weight 64, so it crosses six columns and turns each zero it passes into a 11. This is the mirror image of exercise 1e): subtracting 11 from a power of two is the worst case for borrows, just as adding 11 to all ones is the worst case for carries.

d) Two's complement of 00001101200001101_2 (that is 1313): invert to 11110010211110010_2, add 11 to get 111100112=243=2561311110011_2 = 243 = 256-13. Add: 000001012+111100112=5+243=248=11111000200000101_2 + 11110011_2 = 5+243 = 248 = 11111000_2. Read as signed 8-bit, the leading bit is 11, so the value is 248256=8248-256 = -8. And indeed 513=85-13 = -8. Notice that the adder did nothing special: it added two unsigned patterns. The minus sign only appeared when we chose to read the result as signed.

e) The complement of xx on nn bits is 2nx2^{n}-x, so adding it adds an extra 2n2^{n} that lands entirely in bit nn, outside the word. Dropping that bit subtracts the 2n2^{n} back out, which is why the low eight bits are the correct difference. The case where it must not be dropped is unsigned addition: there the ninth bit is a genuine carry out saying the true sum did not fit, and ignoring it turns 300300 into 4444. Same bit, opposite meaning, decided by the type you declared and not by the hardware.

Exercise 3: Multiplying, dividing, and what a shift really does

Long multiplication in binary is easier than in decimal, because each digit of the multiplier is 00 or 11: every partial product is either the multiplicand shifted, or nothing at all. Multiplying by a power of two is therefore a pure shift, and dividing by one is a shift the other way. The subtlety, and the whole point of parts d) and e), is that a shift to the right is not one operation but two.

  • a) Compute 10112×10121011_2 \times 101_2 by long multiplication. Check in decimal.
  • b) Compute 1101102×1002110110_2 \times 100_2 without multiplying. Justify the method.
  • c) Divide 10110100210110100_2 by 100021000_2. Give the quotient and the remainder, and say where the remainder is visible in the bit pattern.
  • d) On 8 bits, 11110000211110000_2 is shifted right by two places. Give the result for a logical shift and for an arithmetic shift. If the pattern is a signed number, which of the two is division by 44?
  • e) A processor multiplies an mm-bit number by an nn-bit number. How many bits does the product need in the worst case? Prove it.
Show the solution

a) 10112×10121011_2 \times 101_2. The multiplier 1012101_2 has ones at weights 11 and 44, so there are two partial products: 101121011_2 itself, and 101121011_2 shifted two places, that is 1011002101100_2. Add them: 10112+1011002=11+44=55=11011121011_2 + 101100_2 = 11 + 44 = 55 = 110111_2. Decimal check: 10112=111011_2 = 11, 1012=5101_2 = 5, 11×5=5511 \times 5 = 55, and 1101112=32+16+4+2+1=55110111_2 = 32+16+4+2+1 = 55. There is no multiplication table to remember in binary, only shifting and adding.

b) 1002=4=22100_2 = 4 = 2^{2}, so multiplying by it shifts the pattern two places to the left and appends two zeros: 1101102110110_2 becomes 11011000211011000_2. The justification is positional: shifting left by kk multiplies every weight by 2k2^{k}, hence the whole number by 2k2^{k}. Check: 1101102=32+16+4+2=54110110_2 = 32+16+4+2 = 54 and 110110002=128+64+16+8=216=54×411011000_2 = 128+64+16+8 = 216 = 54 \times 4. This is why a compiler replaces a multiplication by a constant power of two with a shift.

c) 10002=8=231000_2 = 8 = 2^{3}, so shift right by three: 10110100210110100_2 becomes 10110210110_2, and the three bits pushed off the right end are 1002100_2. Quotient 101102=2210110_2 = 22, remainder 1002=4100_2 = 4. Check: 101101002=18010110100_2 = 180 and 180=8×22+4180 = 8 \times 22 + 4. The remainder is not computed, it is simply the bits that fell off, which is the fastest possible modulo by a power of two: mask with 2k12^{k}-1.

d) A logical shift feeds zeros in on the left: 11110000211110000_2 becomes 001111002=6000111100_2 = 60. An arithmetic shift replicates the sign bit: it becomes 11111100211111100_2, which read as signed is 252256=4252-256 = -4. If the pattern is signed, 11110000211110000_2 is 240256=16240-256 = -16, and 16/4=4-16/4 = -4: the arithmetic shift is the division, the logical one turns a negative number into a large positive one. Choosing the wrong shift on a signed value does not produce a small rounding error, it produces a number of the wrong sign and roughly the right magnitude, which is exactly the kind of bug that survives testing.

e) m+nm+n bits are enough, and sometimes necessary. The largest operands are 2m12^{m}-1 and 2n12^{n}-1, and their product is 2m+n2m2n+12^{m+n} - 2^{m} - 2^{n} + 1, which is strictly less than 2m+n2^{m+n}, so m+nm+n bits always suffice. They can also be needed: 2m1×2n1=2m+n22^{m-1} \times 2^{n-1} = 2^{m+n-2} already requires m+n1m+n-1 bits, and the largest product above requires m+nm+n. This is why hardware multipliers write into a double-width register, and why in C the product of two 3232-bit integers overflows unless one operand is widened first.

Exercise 4: Two's complement and the asymmetric range

Two's complement is not a minus sign glued to a magnitude. It is a wheel: with nn bits there are 2n2^{n} patterns arranged in a cycle, and the convention cuts the cycle in half, calling the top half negative by subtracting 2n2^{n} from it. Addition then works on the wheel without any special case for signs. The wheel below is the 4-bit version, with the bit patterns outside and the signed values inside.

4-bit two's complement: patterns outside, values inside00000000110010200113010040101501106011171000-81001-71010-61011-51100-41101-31110-21111-1
  • a) Write 27-27 as an 8-bit two's complement pattern. Show the two steps and check the result.
  • b) Read 10110011210110011_2 as a signed 8-bit number, then as an unsigned one.
  • c) On 8 bits, compute the opposite of 128-128 using the invert-and-add-one rule. Comment on what you get.
  • d) Give the range of nn-bit two's complement and explain, by counting patterns, why it is not symmetric.
  • e) The same value 27-27 is copied into a 16-bit variable. Write the 16-bit pattern and state the general rule.
Show the solution

a) Start from +27=16+8+2+1=000110112+27 = 16+8+2+1 = 00011011_2. Invert every bit: 11100100211100100_2. Add one: 11100101211100101_2. Check by the definition: as an unsigned pattern, 111001012=128+64+32+4+1=22911100101_2 = 128+64+32+4+1 = 229, and 229=25627229 = 256-27, which is what 27-27 must be on the 8-bit wheel. A second check costs nothing: adding 00011011200011011_2 and 11100101211100101_2 gives 256256, that is 00000000200000000_2 with a carry out, and a number plus its opposite is zero.

b) Signed: the leading bit is 11, so the pattern sits in the negative half. Its unsigned value is 128+32+16+2+1=179128+32+16+2+1 = 179, therefore the signed value is 179256=77179-256 = -77. Unsigned: the very same bits are simply 179179. Nothing in memory distinguishes the two readings; the type declared in the program does. Reading a signed byte as unsigned is how a temperature of 77-77 becomes a reading of 179179.

c) 128-128 is 10000000210000000_2. Invert: 01111111201111111_2. Add one: 10000000210000000_2, the pattern we started from. So on 8 bits, 128-128 is its own opposite: +128+128 does not exist in the range. This is not a defect of the rule, it is the wheel closing on itself, and it is a real source of bugs. In C, taking the absolute value of the most negative integer returns that same negative number, and a routine that assumes x0|x| \ge 0 can loop forever on it.

d) On nn bits the range is 2n1-2^{n-1} to 2n112^{n-1}-1. Count the patterns: there are 2n2^{n} of them, split into 2n12^{n-1} with a leading 11, all negative, and 2n12^{n-1} with a leading 00, covering zero and the positives. Zero uses one pattern from the second half, so only 2n112^{n-1}-1 remain for the positive values while the negatives keep all 2n12^{n-1}. The asymmetry is the price of having a single representation of zero, and that single zero is what makes ordinary addition work without sign tests.

e) 27-27 on 16 bits is 111111111110010121111111111100101_2, that is FFE516\text{FFE5}_{16}. Check: as unsigned that pattern is 65509=655362765509 = 65536-27. The rule is sign extension: to widen a signed number, copy the sign bit into every new position on the left, not zeros. Filling with zeros instead is the classic widening bug, because 11100101211100101_2 becomes 00000000111001012=2290000000011100101_2 = 229, so a small negative number turns into a mid-sized positive one, silently and with no error raised.

Exercise 5: Carry and overflow answer two different questions

The adder produces one extra bit on the left, the carry out, and it produces it whatever the operands mean. Whether the stored result is wrong is a separate question, and its answer depends on how the bits are read. For an unsigned reading the test is the carry out; for a signed reading the test is overflow, which is something else entirely. The two rulers below carry the same bit patterns and disagree about their values.

unsignedsigned(two's compl.)064128192255064-128-64-1same 8 bits, two readingsthe adder never asks which line you are on
  • a) On 8 bits, add 200+100200+100. Give the stored pattern, then decide separately whether the unsigned reading and the signed reading are correct.
  • b) On 8 bits, add 100+50100+50. Same two questions.
  • c) State the signed overflow rule in terms of the signs of the operands and of the result, and explain why two operands of opposite signs can never overflow.
  • d) On 8 bits, add 100+(50)-100 + (-50). What is stored, and what happened?
  • e) Give one 8-bit addition for each of the four combinations: no carry and no overflow, carry without overflow, overflow without carry, and both at once.
Show the solution

a) 200=110010002200 = 11001000_2 and 100=011001002100 = 01100100_2. The true sum is 300=1001011002300 = 100101100_2, nine bits, so the stored byte is 001011002=4400101100_2 = 44 and the carry out is 11. Unsigned reading: wrong, and the carry out says so, since 300300 does not fit in [0,255][0, 255]. Signed reading: 11001000211001000_2 is 200256=56200-256 = -56 and 01100100201100100_2 is +100+100, so the intended sum is 56+100=44-56+100 = 44, and 4444 is exactly what is stored. Correct. One addition, a carry out, and a perfectly right answer.

b) 100+50=150=100101102100+50 = 150 = 10010110_2, eight bits, so there is no carry out. Unsigned reading: correct, 150150 is stored. Signed reading: both operands are positive, but the stored pattern has a leading 11 and reads as 150256=106150-256 = -106. Wrong, and no carry out warned about it. Cases a) and b) are the proof that the carry out is not a general error flag: here it is the mirror image of a), right where a) was wrong and silent where a) shouted.

c) Signed overflow happens exactly when the two operands have the same sign and the result has the opposite sign. Equivalently, in hardware, when the carry into the sign column differs from the carry out of it. Two operands of opposite signs cannot overflow because the true sum lies strictly between them, and both of them are already inside the representable range, so the sum is inside it too. That is why an overflow check only ever needs to look at three bits, not at the magnitudes.

d) 100-100 is 156=100111002156 = 10011100_2 and 50-50 is 206=110011102206 = 11001110_2. The adder computes 156+206=362156+206 = 362, so the carry out is 11 and the stored byte is 362256=106=011010102362-256 = 106 = 01101010_2. Read as signed, that is +106+106, while the true answer is 150-150. Both operands were negative and the result came out positive, so this is overflow by the rule in c). The true value 150-150 is below 128-128 and simply has no 8-bit pattern. The carry out here is meaningless: for a signed reading it is a by-product, not a diagnostic.

e) No carry, no overflow: 30+40=7030+40 = 70, which fits both readings. Carry without overflow: 200+100200+100 from a), which is 56+100-56+100 read as signed. Overflow without carry: 100+50100+50 from b). Both at once: 100+(50)-100+(-50) from d), or more starkly 128+(128)-128 + (-128), where 100000002+10000000210000000_2 + 10000000_2 gives a carry out and stores 00000000200000000_2, so the machine reports that two of the most negative numbers add up to zero. Four combinations, one adder, and the flag you must read depends entirely on the type you declared.

Part B: exam-level problems (/50)

Exercise 6: Hexadecimal, masks and a status register

One hexadecimal digit is exactly four bits, which is the only reason hexadecimal exists: it is binary written four times shorter, with no arithmetic of its own. A peripheral exposes an 8-bit status register whose bit 00 is READY, bit 11 is ERROR, bit 33 is BUSY and bit 77 is POWER. The other bits are reserved by the manufacturer and a driver must leave them exactly as they are. The register currently holds 4916\text{49}_{16}.

  • a) Write 4916\text{49}_{16} in binary and state which of the four named flags are on.
  • b) Turn ERROR on without disturbing any other bit. Give the operation and the new register value in hexadecimal.
  • c) Turn BUSY off, again without disturbing anything else. Give the mask and the result.
  • d) Toggle POWER. Give the operation and the result.
  • e) Write a single test that is true when READY and BUSY are both on, and explain why the obvious shorter test is wrong.
  • f) Reading BUSY with a mask returns 88, not 11. Give two ways to obtain a clean 00 or 11, and say which one a compiler prefers.
Show the solution

a) 416=01002\text{4}_{16} = 0100_2 and 916=10012\text{9}_{16} = 1001_2, so 4916=010010012=73\text{49}_{16} = 01001001_2 = 73. The bits set are those of weight 11, 88 and 6464, that is bits 00, 33 and 66. So READY is on, BUSY is on, ERROR is off, POWER is off, and reserved bit 66 is on, which is precisely why every operation below must be surgical.

b) To force a bit to 11 without touching the others, OR with a mask that has a 11 in that position only: R OR 0216R \ \text{OR}\ \text{02}_{16}. Bitwise OR leaves a bit unchanged when the mask bit is 00, since x+0x + 0 in the OR sense is xx. Result: 73 OR 2=75=4B16=01001011273\ \text{OR}\ 2 = 75 = \text{4B}_{16} = 01001011_2. Reserved bit 66 is still on, as required.

c) To force a bit to 00, AND with the complement of its mask: BUSY is 0816\text{08}_{16}, whose complement on 8 bits is F716=111101112\text{F7}_{16} = 11110111_2. So R AND F716=73 AND 247=65=4116=010000012R \ \text{AND}\ \text{F7}_{16} = 73\ \text{AND}\ 247 = 65 = \text{41}_{16} = 01000001_2. Every position where the mask has a 11 is preserved, and only bit 33 is cleared. Writing the mask as F7\text{F7} rather than 247247 is not a style choice: in hexadecimal you can see the single zero, in decimal you cannot.

d) To flip a bit, XOR with its mask: R XOR 8016=73 XOR 128=201=C916=110010012R\ \text{XOR}\ \text{80}_{16} = 73\ \text{XOR}\ 128 = 201 = \text{C9}_{16} = 11001001_2. XOR leaves a bit unchanged against a 00 and inverts it against a 11, which makes it the only one of the three operations that is its own inverse: applying it twice restores the register. Summary worth memorising: OR sets, AND with a complement clears, XOR toggles.

e) Mask both flags at once and compare with the mask itself: (R AND 0916)=0916(R\ \text{AND}\ \text{09}_{16}) = \text{09}_{16}. Here 73 AND 9=973\ \text{AND}\ 9 = 9, so the test is true. The obvious shorter test, (R AND 0916)0(R\ \text{AND}\ \text{09}_{16}) \ne 0, is a different question: it is true as soon as at least one of the two flags is on. On a register where READY is on and BUSY is off it returns true, and the driver starts a transfer on a device that is not idle. Masking tells you which bits survive; only the comparison tells you how many of them did.

f) Either shift the masked value down, (R AND 0816)(R\ \text{AND}\ \text{08}_{16}) shifted right by 33, which gives 8/8=18/8 = 1; or compare, writing the test as (R AND 0816)0(R\ \text{AND}\ \text{08}_{16}) \ne 0, which yields a boolean directly. The comparison is what a compiler prefers, because it maps to a single flag test in the condition register with no shift at all, and because it keeps working unchanged if the flag later moves to another bit position. The shift version has to be edited every time the hardware map changes.

Exercise 7: Binary fractions, fixed point, and the error that accumulates

To the right of the binary point the weights carry on halving: 212^{-1}, 222^{-2}, 232^{-3}, and so on. Converting a decimal fraction is done by successive multiplication by 22, reading the integer parts from top to bottom, which is the opposite reading order from the successive divisions used on the integer part. Whether the process terminates is not a matter of patience, it is a property of the number.

  • a) Convert 0.68750.6875 to binary by successive multiplication. Check by adding the weights.
  • b) Convert 0.10.1 the same way. Carry out at least nine steps and describe what you observe.
  • c) State exactly which finite decimal fractions have a finite binary expansion, and justify it.
  • d) A sensor stores values in unsigned Q4.4 format: 8 bits, four for the integer part, four for the fraction. Write 5.755.75 in that format, and give the resolution and the range.
  • e) A counter in Q4.4 adds 0.10.1 ten times. What does it actually hold at the end, and how large is the relative error?
  • f) A billing system must add thousands of amounts in dollars and cents. Say what it should store, and why.
Show the solution

a) 0.6875×2=1.3750.6875 \times 2 = 1.375, digit 11, keep 0.3750.375. 0.375×2=0.750.375 \times 2 = 0.75, digit 00. 0.75×2=1.50.75 \times 2 = 1.5, digit 11, keep 0.50.5. 0.5×2=1.00.5 \times 2 = 1.0, digit 11, remainder zero, stop. Reading the digits in the order produced gives 0.101120.1011_2. Check by weights: 0.5+0.125+0.0625=0.68750.5 + 0.125 + 0.0625 = 0.6875. The reading order matters and it is the reverse of the integer case: here top to bottom, there bottom to top. Mixing the two is the single most common error in this chapter and it produces a plausible looking wrong answer.

b) 0.1×2=0.20.1 \times 2 = 0.2 gives 00; 0.40.4 gives 00; 0.80.8 gives 00; 1.61.6 gives 11, keep 0.60.6; 1.21.2 gives 11, keep 0.20.2; and now 0.20.2 has already occurred, so the block 00110011 repeats for ever: 0.1=0.0001120.1 = 0.0\overline{0011}_2. The process does not stop, and it will not stop with more bits, more memory or a better processor. Every machine that stores 0.10.1 stores something else, slightly larger or smaller depending on where it cuts.

c) A fraction has a finite binary expansion if and only if, written in lowest terms, its denominator is a power of 22. A finite expansion with kk fractional bits is a number of the form m/2km/2^{k}, so the reduced denominator can only be a power of two; conversely any m/2km/2^{k} is written directly with kk bits. Thus 0.50.5, 0.250.25 and 0.6875=11/160.6875 = 11/16 are exact, while 0.1=1/100.1 = 1/10 and 0.2=1/50.2 = 1/5 are not, because 1010 and 55 carry a factor 55 that no power of two can cancel.

d) Q4.4 means the stored 8-bit integer represents the value divided by 24=162^{4} = 16. For 5.755.75: 5.75×16=925.75 \times 16 = 92, and 92=01011100292 = 01011100_2, so the integer part is 01010101 and the fraction 11001100, that is 010111002=5C160101\,1100_2 = \text{5C}_{16}. Resolution: one unit of the stored integer is 1/16=0.06251/16 = 0.0625, so nothing finer can be expressed. Range: from 00 to 255/16=15.9375255/16 = 15.9375. Fixed point is just integer arithmetic with an agreed scale factor, which is why it is used on processors without a floating point unit.

e) 0.10.1 is not a multiple of 1/161/16: 0.1×16=1.60.1 \times 16 = 1.6, which rounds to 22, so the stored value is 2/16=0.1252/16 = 0.125. Every addition therefore adds 0.1250.125, not 0.10.1. After ten additions the counter holds 1.251.25 instead of 1.01.0: an absolute error of 0.250.25 and a relative error of 25%25\%. The error did not creep in, it was decided at the first rounding and then multiplied by ten. This is the argument for choosing a scale that matches the data: in a format scaled by 100100 instead of 1616, 0.10.1 is exact.

f) It should store integer cents, not dollars. Cents are exact integers in any base, additions of integers never round, and the total is converted to dollars once, at the moment of display. Storing dollars as binary fractions means that neither 0.100.10 nor 0.010.01 is exact, so each of the thousands of additions can be off by a fraction of a cent and the accumulated drift eventually shows up as a total that does not match the sum of the printed lines. Financial software stores integers or decimal types for this reason alone.

Exercise 8: Five statements to correct

Each of the five statements below is FALSE. Say why, give a counterexample where one exists, and write the corrected statement.

  • 1) « Shifting a binary number one place to the right always divides it by two. »
  • 2) « On 8 bits, two's complement represents the integers from 127-127 to +127+127. »
  • 3) « If an addition produces a carry out, the stored result is wrong. »
  • 4) « A 32-bit float can hold every integer that a 32-bit integer can hold, since both use 32 bits. »
  • 5) « Two's complement just means putting a minus sign in the leading bit. »
Show the solution

1) FALSE on two counts. First, the quotient is an integer: 7=11127 = 111_2 shifted right gives 112=311_2 = 3, not 3.53.5, and the discarded bit is the remainder. Second, on a negative number the answer depends on which shift is used: 7-7 on 8 bits is 11111001211111001_2, and an arithmetic shift gives 111111002=411111100_2 = -4, while a logical shift gives 011111002=12401111100_2 = 124. Corrected statement: an arithmetic right shift by kk divides a signed number by 2k2^{k} and rounds towards minus infinity, while a logical right shift divides an unsigned number by 2k2^{k}; the two agree only on non-negative values. Note in passing that 7-7 shifted gives 4-4 whereas division in C gives 3-3, so a compiler cannot blindly replace a division by a shift.

2) FALSE. The range is 128-128 to +127+127. Counting settles it: 256256 patterns, 128128 with a leading 11 for the negatives and 128128 with a leading 00 shared between zero and the positives, so the positives get only 127127 of them. Corrected statement: on nn bits, two's complement covers [2n1, 2n11][-2^{n-1},\ 2^{n-1}-1], an asymmetric interval, because zero has a single representation and it sits on the positive side.

3) FALSE. The carry out only condemns an unsigned reading. Counterexample: 200+100200+100 on 8 bits produces a carry out and stores 4444; read as signed those operands are 56-56 and +100+100, and 4444 is the exact answer. Conversely 100+50100+50 produces no carry out at all and yet is wrong when read as signed, since it stores 106-106. Corrected statement: for an unsigned reading the error flag is the carry out; for a signed reading it is overflow, which is when the operands share a sign and the result does not.

4) FALSE. A single precision float spends 11 bit on the sign and 88 on the exponent, leaving 2323 stored significand bits plus one implied, so only 2424 bits of precision. Every integer up to 224=167772162^{24} = 16\,777\,216 is exact, but 1677721716\,777\,217 is not: it rounds to 1677721616\,777\,216. A 32-bit integer, by contrast, is exact up to 23112^{31}-1. Corrected statement: a float trades precision for range, so it reaches far larger magnitudes than a 32-bit integer while representing far fewer integers exactly.

5) FALSE. That description is sign-magnitude, a different and older convention, and the two disagree from the very first value: 10000001210000001_2 is 1-1 in sign-magnitude but 127-127 in two's complement. Sign-magnitude also has two zeros, 00000000200000000_2 and 10000000210000000_2, and it breaks ordinary addition, since adding 00000001200000001_2 and 10000001210000001_2 gives 10000010210000010_2, which sign-magnitude reads as 2-2 instead of 00. Corrected statement: in two's complement the leading bit is a normal positional digit whose weight is 2n1-2^{n-1}, negative rather than positive, which is exactly what lets one adder serve both signs.

Exercise 9: Problem: a 12-bit sensor and a 16-bit accumulator

A thermocouple board returns temperatures as 12-bit two's complement counts, one count being 0.0625 0.0625\ ^\circC, that is 1/161/16 of a degree. The firmware copies each reading into a 16-bit signed variable, sums 200200 of them and divides to obtain an average. Two things can go wrong on that path, and both have gone wrong in shipped products. The diagram shows the 16-bit word with the 12 sensor bits already in place.

16-bit wordbits 15 to 12bits 11 to 0: the ADC reading1512110????111101100000
  • a) A reading comes back as 0F016\text{0F0}_{16}. Convert it to a temperature.
  • b) The next reading is F6016\text{F60}_{16}. Convert it to a temperature, being careful about the sign.
  • c) Give the correct 16-bit pattern for the reading of b), in hexadecimal, and justify the four leading bits.
  • d) A junior developer fills those four bits with zeros instead. What temperature does the system then report, and what does that cost?
  • e) The firmware sums 200200 readings in a 16-bit signed accumulator. Show that this can overflow, and find the smallest accumulator width that cannot.
  • f) With the probe held at its maximum, 20472047 counts, give the average the buggy 16-bit version reports and compare it with the truth.
Show the solution

a) 0F016=0000111100002\text{0F0}_{16} = 0000\,1111\,0000_2. Bit 1111 is 00, so the value is positive and equal to its unsigned reading: 0F016=240\text{0F0}_{16} = 240 counts. Temperature: 240/16=15.0 240/16 = 15.0\ ^\circC. Reading the sign bit before anything else is the habit to build here, because the conversion that follows is different in the two cases and looks equally reasonable either way.

b) F6016=1111011000002\text{F60}_{16} = 1111\,0110\,0000_2, and bit 1111 is 11, so the value is negative. On 12 bits the wheel has 212=40962^{12} = 4096 patterns, so the signed value is 39364096=1603936 - 4096 = -160 counts. Temperature: 160/16=10.0 -160/16 = -10.0\ ^\circC. The trap is to subtract 6553665536 instead of 40964096: the field is 12 bits wide, and the wheel size is fixed by the field, not by the variable that will later hold it.

c) The correct 16-bit pattern is FF6016=11111111011000002\text{FF60}_{16} = 1111\,1111\,0110\,0000_2. The four leading bits are copies of the sign bit, which is sign extension. The check is arithmetic, not visual: as unsigned, FF6016=65376\text{FF60}_{16} = 65376, and 6537665536=16065376 - 65536 = -160, the same value as on 12 bits. Widening a signed number must preserve its value, and only replicating the sign bit does that.

d) Filling with zeros gives 0F6016=3936\text{0F60}_{16} = 3936, a positive number, so the system reports 3936/16=246.0 3936/16 = 246.0\ ^\circC. A probe sitting at 10 -10\ ^\circC is reported at 246 246\ ^\circC. Nothing raises an error: the arithmetic is valid, the variable is in range, the reading is simply false. On a freezer controller this trips the over-temperature alarm and starts a cooling cycle that is not needed; on a furnace it does the opposite. Note that the bug is invisible in testing as long as the bench temperature stays positive.

e) A 16-bit signed accumulator holds at most 2151=327672^{15}-1 = 32\,767. With 200200 readings of up to 20472047 counts, the true sum reaches 200×2047=409400200 \times 2047 = 409\,400, more than twelve times the capacity, so it overflows. For a width nn we need 2n114094002^{n-1}-1 \ge 409\,400; since 2181=2621432^{18}-1 = 262\,143 is too small and 2191=5242872^{19}-1 = 524\,287 is enough, the smallest workable width is n=20n = 20 bits. In practice the accumulator is declared as 32-bit, both because 2020 is not a machine width and because it leaves room if the sample count grows.

f) The true sum is 409400409\,400. On 16 bits it wraps modulo 6553665\,536: 4094006×65536=16184409\,400 - 6 \times 65\,536 = 16\,184, which is below 3276832\,768 and therefore reads as a positive 1618416\,184. Divided by 200200 that is 80.9280.92 counts, so the reported temperature is 80.92/16=5.06 80.92/16 = 5.06\ ^\circC, while the probe is actually at 2047/16=127.94 2047/16 = 127.94\ ^\circC. The system reports a comfortable 55 degrees on a component near 128128 degrees, with no flag raised anywhere. Sum first in a width that fits, scale once at the end, and the whole class of bug disappears.

16-bit wordbits 15 to 12bits 11 to 0: the ADC reading15121101111111101100000

Exercise 10: Problem: the IEEE 754 single precision format

A single precision float packs three fields into 32 bits: one sign bit, eight exponent bits stored with a bias of 127127, and twenty-three significand bits to which a leading 11 is implied and never stored. The value is (1)s×1.f×2e127(-1)^{s} \times 1.f \times 2^{e-127}. Everything surprising about floating point, from the spacing of the numbers to the failure of an equality test, follows from those field widths.

31bits 30 to 23bits 22 to 0sign bitsefexponent, 8 bits, bias 127significand, 23 bits, leading 1 impliedvalue = (-1)^s x 1.f x 2^(e - 127)
  • a) Encode +9.75+9.75. Give the three fields, then the 32-bit word in hexadecimal.
  • b) Decode C120000016\text{C1200000}_{16}.
  • c) Give the gap between two consecutive floats near 1.01.0, then near 10001000. Explain the difference in one sentence.
  • d) Give the largest integer NN such that every integer from 00 to NN is exactly representable, and show that the next one is not.
  • e) Explain why the test comparing 0.1+0.20.1+0.2 with 0.30.3 fails, and say what to write instead.
  • f) The exponent fields 0000000000000000 and 1111111111111111 are reserved. Say what for, and why that reservation was worth eight of the 256256 exponent values.
Show the solution

a) 9.75=1001.1129.75 = 1001.11_2, since 8+1+0.5+0.25=9.758+1+0.5+0.25 = 9.75. Normalise: 1001.112=1.001112×231001.11_2 = 1.00111_2 \times 2^{3}. Sign s=0s = 0. Exponent field e=3+127=130=100000102e = 3+127 = 130 = 10000010_2. Significand: the bits after the implied leading 11, that is 0011100111 followed by eighteen zeros. The full word is 0 10000010 001110000000000000000000\ 10000010\ 00111000000000000000000, and grouping into fours gives 0100 0001 0001 1100 0000 0000 0000 0000=411C0000160100\ 0001\ 0001\ 1100\ 0000\ 0000\ 0000\ 0000 = \text{411C0000}_{16}. Check the other way: 1+23+24+25=1.218751 + 2^{-3} + 2^{-4} + 2^{-5} = 1.21875 and 1.21875×8=9.751.21875 \times 8 = 9.75.

b) C120000016=1100 0001 0010 0000 0000 0000 0000 00002\text{C1200000}_{16} = 1100\ 0001\ 0010\ 0000\ 0000\ 0000\ 0000\ 0000_2. Split by field: s=1s = 1, so the number is negative. Exponent field =100000102=130= 10000010_2 = 130, so the true exponent is 130127=3130-127 = 3. Significand bits =01000= 01000\ldots, so 1.f=1+22=1.251.f = 1 + 2^{-2} = 1.25. Value =1.25×23=10.0= -1.25 \times 2^{3} = -10.0. Splitting at the right place is the whole exercise: the boundaries fall inside hexadecimal digits, so the pattern has to be expanded to binary first, and trying to read the fields directly off the hexadecimal is how this question is failed.

c) Near 1.01.0 the exponent is 00, so consecutive floats differ by one unit in the last significand bit, that is 2231.19×1072^{-23} \approx 1.19 \times 10^{-7}. Near 10001000 the exponent is 99, because 5121000<1024512 \le 1000 < 1024, so the gap is 2923=2146.1×1052^{9-23} = 2^{-14} \approx 6.1 \times 10^{-5}, roughly five hundred times wider. In one sentence: floats have constant relative precision and therefore absolute precision that degrades in proportion to the magnitude.

d) N=224=16777216N = 2^{24} = 16\,777\,216. Up to that point the significand has enough bits to name every integer: with exponent kk the representable numbers are spaced by 2k232^{k-23}, and that spacing stays at most 11 as long as k23k \le 23, which covers everything below 2242^{24}. At 2242^{24} itself the exponent is 2424 and the spacing becomes 22, so 1677721716\,777\,217 falls between two neighbours and rounds to 1677721616\,777\,216. In a float, adding 11 to 1677721616\,777\,216 leaves it unchanged, and a counting loop written with floats stops advancing there while continuing to run.

e) Neither 0.10.1 nor 0.20.2 is exactly representable, since their denominators carry a factor 55, so each is stored as the nearest available float. Their sum is rounded again, and the result lands one unit in the last place above the float nearest to 0.30.3. The bits differ, so the equality test is false, and it is false for a correct reason: the machine is comparing two numbers that were never the ones written in the source. What to write instead is a tolerance test, comparing the absolute difference with a small threshold, or better a relative one scaled to the magnitude of the operands. For money the right answer is not a tolerance at all, it is to store integer cents.

f) Exponent field 0000000000000000 marks zero and the subnormal numbers, which drop the implied leading 11 and fill the gap between the smallest normal float and zero, so that subtracting two nearly equal numbers cannot silently return zero. Exponent field 1111111111111111 marks the infinities and the NaNs, which let an overflow or a division by zero flow through a computation and be tested at the end instead of aborting it. Eight of the 256256 exponent codes buy a system in which every arithmetic operation returns something well defined, and that closure is what makes numerical code portable across machines.

31bits 30 to 23bits 22 to 0sign bit01000 00100011 1000 0000 ... 0000exponent, 8 bits, bias 127significand, 23 bits, leading 1 implied9.75 = 1.00111 x 2^3, stored as 411C0000 in hex

See also

Taking 201-N11 at cégep in Montreal?

Get in touch for a first session. With a B.Sc. in Computer Science from McGill and an M.Sc. in Applied Computer Science from Concordia, I teach binary arithmetic through what actually breaks in production: silent overflows, sign extension bugs and float comparisons.

Site by Studio Squalli