SEN2201 · Week 2 · Bits, Data Types, and Operations

Bitwise operations, IEEE 754 floating point and ASCII

Bit masks with AND/OR/XOR/NOT, the recipe for encoding and decoding IEEE 754 single precision, and ASCII character codes. Each is a guaranteed question on the midterm.

bitwise ANDbitwise ORXORNOTbit maskIEEE 754single precisionsign bitexponentbiasfractionnormalizeddenormalizedASCII

On the previous page you read bits as integers. This page has three new things: logic operations on bits (masks), IEEE 754 for numbers with fractions, and ASCII for characters. All three are solved with a recipe. Whoever knows the recipe gets full marks.

Logic operations (bitwise operations)

These operations are done separately on the bits in the same position of two numbers. There is no carry, the bits do not affect each other.

ABA AND BA OR BA XOR BNOT A
000001
010111
100110
111100
       1100 1001   (0xC9)
AND    1111 0000   (0xF0)
       ---------
       1100 0000   (0xC0)     the low 4 bits were cleared

Bit mask

The second number chosen to change or read some bits of a number is called a mask. Which operation you choose depends on what you want to do:

GoalOperationMask ruleExample, X = 0x93 = 1001 0011
Make bits 0 (clear)ANDthe bits to clear are 0, the others 1X AND 0x0F = 0x03
Make bits 1 (set)ORthe bits to set are 1, the others 0X OR 0x0F = 0x9F
Flip bits (toggle)XORthe bits to flip are 1, the others 0X XOR 0x0F = 0x9C
Test a bitANDonly that bit is 1X AND 0x08 = 0 → bit 3 = 0

A way to remember: with AND, 0 kills; with OR, 1 creates; with XOR, 1 flips. In AND the places where the mask is 1, and in OR and XOR the places where it is 0, leave the number untouched.

TIP · Bit numbers. Bits are numbered from the right, starting at 0. The mask of bit kk is 2k2^k: bit 0 = 0x01, bit 3 = 0x08, bit 4 = 0x10, bit 5 = 0x20, bit 7 = 0x80.

The LC-3 has only AND and NOT (week 7). You build OR with DeMorgan: A OR B=NOT(NOT A AND NOT B)A \text{ OR } B = \text{NOT}(\text{NOT }A \text{ AND NOT }B).

Numbers with fractions: IEEE 754

Integer representations cannot write 3.75 or 6×10236 \times 10^{23}. For this the binary form of scientific notation is used: ±1.xxx×2e\pm 1.xxx \times 2^{e}. IEEE 754 single precision (float in C) consists of 32 bits:

SExponentFraction
1 bit8 bits23 bits
sign (0 = +)e+127e + 127 (bias)the bits after the point

N=(−1)S×1.fraction×2 exponent−127(1≤exponent≤254)N = (-1)^S \times 1.\text{fraction} \times 2^{\,\text{exponent} - 127} \qquad (1 \le \text{exponent} \le 254)

The leading 1 is the same in every normalized number, so it is not written (hidden bit). This way 24 bits of precision fit in 23 bits. The exponent is stored shifted by 127 (biased) so that negative powers can be written too.

Special cases:

ExponentFractionMeaning
00±0
0≠ 0denormal: 0.fraction×2−1260.\text{fraction} \times 2^{-126}
2550±∞
255≠ 0NaN

Converting the fractional part to binary

Multiply the fraction by 2, write the integer part (0 or 1), and continue with the fractional part. Stop when the fraction is 0. The bits you wrote are read from top to bottom.

0.375 × 2 = 0.75   → 0
0.75  × 2 = 1.5    → 1
0.5   × 2 = 1.0    → 1   (stop)
0.375 = 0.011

Encode recipe (number → bits)

Worked example: 6.125

1) sign: positive → S = 0
2) 6 = 110
3) 0.125: ×2=0.25→0, ×2=0.5→0, ×2=1.0→1  → .001
   6.125 = 110.001
4) normalize: 110.001 = 1.10001 × 2^2   (point moves 2 to the left)
5) exponent = 2 + 127 = 129 = 1000 0001
6) fraction = 10001 and 18 zeros
7) 0 10000001 10001000000000000000000
   0100 0000 1100 0100 0000 0000 0000 0000 = 0x40C40000

Worked example: -2.5

2.5 = 10.1 = 1.01 × 2^1
S = 1, exponent = 128 = 1000 0000, fraction = 01000...
1 10000000 01000000000000000000000 = 1100 0000 0010 0000 ... = 0xC0200000

Decode recipe (bits → number)

Worked example: 0x41F40000

1) 0100 0001 1111 0100 0000 0000 0000 0000
2) S = 0 | exponent = 1000 0011 | fraction = 1110100...
3) exponent = 131 → e = 131 - 127 = 4
4) significand = 1.11101
5) 1.11101 × 2^4 = 11110.1   (point moves 4 to the right)
6) 11110 = 30, .1 = 0.5 → 30.5

Worked example: 0xC1100000

1100 0001 0001 0000 ... → S = 1 | exponent = 1000 0010 = 130 → e = 3 | fraction = 001...
1.001 × 2^3 = 1001 = 9 → -9

PITFALL · field boundaries do not line up with hex digits. The first bit of 0x41 is S, the next 7 bits are the start of the exponent; the last bit of the exponent is the first bit of the next hex digit. First write the 32 bits on one line, then draw the split as 1 | 8 | 23.

PITFALL · four mistakes that lose points in IEEE. (1) Forgetting to add or subtract 127. (2) Writing the hidden 1 into the fraction. (3) Filling the fraction from the right: the bits start right after the point, and the rest is padded with 0 on the right. (4) When converting to hex, keeping S separate and breaking the grouping in fours.

Not every decimal number can be written exactly

The binary expansion of 0.1 repeats forever (0.0001100110011…). Because it does not fit in 23 bits, it is rounded. In C, comparisons like 0.1f + 0.2f == 0.3f are dangerous for this reason. Fractions whose denominator is a power of 2 (1/2, 3/8, 5/16) are written exactly.

ASCII: characters

Every character that comes from the keyboard or goes to the screen is a number. ASCII (American Standard Code for Information Interchange) maps each character to an 8-bit code (actually 7 bits are enough, the top bit is 0).

CharacterHexDecimal
NUL (end of string)x000
spacex2032
‘0’ … ‘9’x30 … x3948 … 57
‘A’ … ‘Z’x41 … x5A65 … 90
‘a’ … ‘z’x61 … x7A97 … 122
line feed (LF)x0A10

Two properties you need to know:

  1. Value of a digit = code - x30. ‘7’ = x37 → 7. The reverse: 3 + x30 = x33 = ‘3’.
  2. Upper and lower case letters differ only in bit 5 (x20 = 32). ‘M’ = x4D = 0100 1101, ‘m’ = x6D = 0110 1101.
ConversionOperation
convert to lowercaseOR x20
convert to uppercaseAND xDF
switch the caseXOR x20

The word “CAT” sits in memory as x43 x41 x54. In the LC-3, a string written with .STRINGZ puts each character in one word (16 bits) and adds x0000 at the end (week 10).

Common mistakes

Wrong ideaThe truth
OR to erase a bitErasing is done with AND, with that bit 0 in the mask
XOR = OR1 XOR 1 = 0, 1 OR 1 = 1
Write the hidden 1 into the fraction1.10101 → fraction 10101
The exponent field = eThe field = e + 127
0.1 can be written exactlyInfinite repetition, it is rounded
Exponent 255 = a very large numberA special value: ∞ or NaN
The value of the character ‘7’ is 7x37 = 55; subtract x30 to get 7
The difference between ‘A’ and ‘a’ is x10x20 (bit 5)

Test yourself

Exam-style questions. Solve them on paper first, then reveal the answer.

Question 1easy

Compute 0x6C AND 0x3A, 0x6C OR 0x3A and 0x6C XOR 0x3A (8-bit). Give hex results.

Show answer
0x28, 0x7E, 0x56

0x6C = 0110 1100, 0x3A = 0011 1010.
AND (1 if both are 1): 0010 1000 = 0x28. OR (1 if at least one is 1): 0111 1110 = 0x7E. XOR (1 if they differ): 0101 0110 = 0x56.

Question 2easy

What is NOT 0x0F0F for a 16-bit value?

Show answer
0xF0F0

NOT flips every bit: in hex each digit becomes F−hF - h. 0→F, F→0: 0xF0F0.

Question 3medium

Let S = 0xB6. Give the operation and mask to (a) set bit 6, (b) clear bit 2, (c) toggle the low 4 bits. Give each result in hex.

Show answer
OR 0x40 → 0xF6; AND 0xFB → 0xB2; XOR 0x0F → 0xB9

S = 1011 0110.
(a) Set = OR, in the mask only bit 6 = 1: 0100 0000. The result is 1111 0110 = 0xF6.
(b) Clear = AND, in the mask only bit 2 = 0: 1111 1011 = 0xFB. The result is 1011 0010 = 0xB2.
(c) Toggle = XOR, the low 4 bits are 1: 0000 1111. The result is 1011 1001 = 0xB9.

Question 4easy

How can you test whether bit 4 of X = 0x5D is 1? What is the result?

Show answer
X AND 0x10 = 0x10, so bit 4 is 1

To test, use AND with a mask in which only that bit is 1. 0101 1101 AND 0001 0000 = 0001 0000. The result is not zero, so bit 4 = 1.

Question 5medium

Encode 10.75 in IEEE 754 single precision. Give the final hex value.

Show answer
0x412C0000

10 = 1010, 0.75 = .11 → 1010.11 = 1.01011 × 2^3.
S = 0. Exponent = 3 + 127 = 130 = 1000 0010. Fraction = 01011 and 18 zeros.
0 10000010 01011000000000000000000 → 0100 0001 0010 1100 0000 ... = 0x412C0000.

Question 6medium

Encode -0.8125 in IEEE 754 single precision. Give the final hex value.

Show answer
0xBF500000

0.8125 × 2 = 1.625 → 1; 0.625 × 2 = 1.25 → 1; 0.25 × 2 = 0.5 → 0; 0.5 × 2 = 1.0 → 1. So 0.1101.
Normalize: 1.101 × 2^-1. S = 1, exponent = -1 + 127 = 126 = 0111 1110, fraction = 101 and zeros.
1 01111110 10100... → 1011 1111 0101 0000 ... = 0xBF500000.

Question 7medium

What decimal number does the IEEE 754 single-precision value 0x42290000 represent?

Show answer
42.25

0100 0010 0010 1001 0000 ... → S = 0 | exponent = 1000 0100 = 132 | fraction = 0101001...
E = 132 - 127 = 5. 1.0101001 × 2^5 = 101010.01 = 32 + 8 + 2 + 0.25 = 42.25.

Question 8medium

What decimal number does 0xC0A80000 represent in IEEE 754 single precision?

Show answer
-5.25

1100 0000 1010 1000 ... → S = 1 | exponent = 1000 0001 = 129 → E = 2 | fraction = 0101...
1.0101 × 2^2 = 101.01 = 5.25. S = 1 → -5.25.

Question 9hard

Which value is represented by 0x7F800000 in IEEE 754 single precision?

Show answer
+infinity

0 11111111 000...0: the exponent field is 255 (all ones) and the fraction is 0 → infinity. Because S = 0 it is positive infinity. If the fraction were nonzero, it would be NaN (not a number).

Question 10medium

The ASCII code of 'q' is 0x71. Which bitwise operation and mask convert it to 'Q'? Give the result.

Show answer
AND 0xDF → 0x51

Upper and lower case letters differ only in bit 5 (0x20). To get 'Q', clear bit 5: AND, with bit 5 = 0 in the mask: 1101 1111 = 0xDF. 0111 0001 AND 1101 1111 = 0101 0001 = 0x51 = 'Q'.

Question 11easy

A program reads the character '7' from the keyboard (ASCII). Which value must be subtracted to get the integer 7?

Show answer
0x30 (48)

Digits are consecutive in ASCII: '0' = 0x30, '1' = 0x31, …, '9' = 0x39. '7' = 0x37, 0x37 - 0x30 = 7. In LC-3 programs this is always what is done to convert a digit coming from the keyboard to a number (weeks 7-8 and 10).

Question 12hard

Is 0.1 exactly representable in IEEE 754 single precision? Explain briefly.

Show answer
No

The binary expansion of 0.1 repeats forever: 0.000110011001100… It does not fit in a 23-bit fraction, so it is rounded. The value actually stored is 0.10000000149011612 (0x3DCCCCCD). Fractions whose denominator is a power of 2 (0.5, 0.25, 0.375) are written exactly.

Verification: The bit operations were computed with node; the IEEE 754 encodings were verified at the bit level with DataView.setFloat32/getFloat32; the ASCII codes were checked with charCodeAt (verify/SEN2201/02.mjs).

Review card

AND = clear/test (the bits that are 0 in the mask are erased) | OR = set | XOR = toggle | NOT = flip all mask of bit k = 2^k: bit4 = 0x10, bit5 = 0x20, bit7 = 0x80 clear bit k: AND ~(2^k) (bit2 -> AND 0xFB) IEEE 754 single: S(1) | E(8, bias 127) | F(23) N = (-1)^S × 1.F × 2^(E-127), 1 ≤ E ≤ 254 E = 0 -> denormal 0.F × 2^-126 (and ±0) | E = 255 -> ±∞ if F=0, otherwise NaN ENCODE: divide the integer part, fraction ×2 -> 1.xxx × 2^e -> E = e+127 -> F = xxx, 0s on the right DECODE: expand to 32 bits -> split 1|8|23 -> e = E-127 -> shift 1.F by e the hidden 1 is NOT WRITTEN. 1.0 = 0x3F800000, -1.0 = 0xBF800000 ASCII: '0'=x30..'9'=x39 | 'A'=x41 | 'a'=x61 | space=x20 | NUL=x00 digit -> number: - x30 | lowercase: OR x20 | uppercase: AND xDF | toggle: XOR x20