CMU updated their lab questions for CSAPP last year, and many solutions online are actually outdated. I’m trying to figure out these questions and I’ll post my solutions for the latest Data Lab with some explanations here. I hope this could be helpful if you have any problems with the lab questions.

## 1. bitXor

1 | /* |

For this problem, we need to know that `not(not a and not b) = a or b (i.e. ~(~x & ~y) = x | y)`

(De Morgan’s laws) and `(x or y) and not(x and y) = x xor y`

.

## 2. TMin

1 | /* |

`TMin = 1000...0000`

in binary, so shift 1 to the left (`0000...0001`

in binary) by 31 to get `TMin`

.

## 3. isTmax

1 | /* |

The first trick is to get the value of TMax by simply flipping the bit-level representation of TMin.

Then, you might notice that the return statement is pretty similar to the one in `bitXor`

, only with an extra `!`

(logical negation). Actually, by doing the xor operation, we are checking the equality of two numbers. We could also say that the xor operation works just like `!=`

, so by adding a `!`

to our xor implementation, we are literally just checking the equality like what `==`

does.

## 4. allOddBits

1 | /* |

Read the prompt first. It could be helpful.

Then you may start to wonder what is special about 0xAAAAAAAA. Well, you can try to convert it to binary and it turns out to be `0xAAAAAAAA = 0b10101010101010101010101010101010`

, which is a number with all odd-numbered bits in the word set to 1, exactly the same thing asked in this problem.

What we are going to do is calculate `x & 0xAAAAAAAA`

. This operation allows us to ignore all numbers on even-numbered bits. Since the numbers on even-numbered bits are not helpful for this problem and it only makes things more tricky so we should ignore them. By ignoring the even-numbered bits, we will get the same value as `0xAAAAAAAA`

if x has all of its odd-numbered bits set to 1.

Finally, check if the result has the same value as `0xAAAAAAAA`

using the equality check trick we did before.

## 5. negate

1 | /* |

`-a = ~a + 1`

## 6. isAsciiDigit

1 | /* |

We are doing comparison in this question. Check question 8 to see how we compare two numbers using only bit-level operations.

## 7. conditional

1 | /* |

The ternary operator `x ? y : z`

will return `y`

if `x`

is true and `z`

otherwise. In order to map an integer value to boolean, we first need to know that `0`

will be converted to `false`

while any other values will be converted to `true`

, so we need to somehow create an algorithm to map `x`

to a new domain.

The expression `((x | (~x + 1)) >> 31)`

will convert `x`

to 0 if `x = 0`

and convert `x`

to -1 (1111….1111 in binary) if `x != 0`

.

Then, we can apply the mapped `x`

value to `y`

and `z`

. Since we have `0000...0000`

or `1111...1111`

as our mapped value in bit level representation, we could simply apply an `AND`

operation.

`a AND 0`

will empty the value and set all bits to 0.`a AND -1`

will give out exactly the same value as the input.

By doing so, we can set either of `y`

and `z`

to zero while leaving one of them unchanged. Finally, we can get the result by adding them up.

## 8. isLessOrEqual

1 | /* |

There are two cases that `x <= y`

:

`x`

is negative and`y`

is positive

We can determine this by checking the sign bits of`x`

and`y`

. If`x`

is signed while`y`

is not, return`1`

;`x`

and`y`

have the same sign but`y - x`

is positive.

We can determine this by checking if the sign bits of`x`

and`y`

are the same and checking the sign bit of`y - x`

. If both`x`

and`y`

have the same sign bit and sign bit of`y - x`

is 0, return`1`

.

## 9. logicalNeg

1 | /* |

For a logical negation, we want it to return 1 when and only when x is 0. A special property of 0 in 2’s complement is that there’s no negative zero, so `(x | -x)`

will give out 0 if x is zero, while giving out a number with 1 on its most significant bit when x is nonzero.

Then we shift the value of `(x | -x)`

by 31 so that we can get -1 if x is nonzero or 0 if x is zero. (Remember that arithmetic shift copies the most significant bit while shifting, which means if we have a number with 1 on its MSB, it will keep copying 1 and give us `111....111`

which is -1 in 2’s complement)

Finally, we add a 1 as bias to get the result.

## 10. howManyBits

1 | /* howManyBits - return the minimum number of bits required to represent x in |

This question is one of the trickiest questions in this lab. It took me quite a long time to figure this out.

We can divide the whole process into three steps.

- Flip the bits if x < 0

In 2’s complement, if we have more bits available than what is required to represent a number, we copy the sign bit until all empty bits are filled with duplicated sign bits, which is called sign extension. By flipping the bits of a negative number, we can remove the leading ones in its bit level representation caused by sign extension. - Fill the lower bits with ones

Now we have a new number with bit level representation like`0000 0000 1000 1010`

. We want to fill the lower bits with ones (i.e. turn it into`0000 0000 1111 1111`

) so that we can count the number of ones to get our result. - Count ones

We can count ones by grouping and adding. Each step of those five, adds neighbouring bits together in groups of 1, then 2, then 4 etc. The method is based in divide and conquer.

Check out this post on StackOverflow for detailed explanation on this algorithm: https://stackoverflow.com/questions/3815165/how-to-implement-bitcount-using-only-bitwise-operators

Don’t forget to add a one for sign bit for our final result.

I strongly recommend you to pick a small number (e.g. a 8-bit number) and do the whole process by hand to understand how the algorithm works.

## 11. floatScale2

1 | /* |

According to the IEEE Standard for floating point number, a single-precision floating point number is stored and formatted like this.

And there’re generally three ways to interpret a floating point

- Normalized
- Denormalized
- Special value

For this question, we first need to determine whether this number is a normalized, denormalized or special value representation by checking the `exp`

field. We can get the value of `exp`

field using `((uf >> 23) & 0xff)`

(i.e. shift to right by 23 and get rid of the sign bit).

If the number is a 0 or special value representation, return `uf`

without any modification.

If the number is a denormalized representation, shift the `frac`

field to left by 1 without changing the `exp`

field. To get the `frac`

field, we use `(uf & 0x007FFFFF)`

to empty all bits but the `frac`

field. Don’t forget to put the sign bit back.

If the number is a normalized representation, add 1 to the `exp`

field.

## 12. floatFloat2Int

1 | /* |

In order to cast a floating point number into integer, we first need to extract the three fields of a floating point representation.

- 1 sign bit
`s`

- 8 bits for
`exp`

field - 23 bits for
`frac`

field

Then we subtract the bias to get the real exponent and add the implicit 1 to `frac`

field in order to get the real mantissa `M`

. Although there is a denormalized representation for representing small numbers, but since we are casting to integer and all those small numbers will be rounded to 0, we don’t need to care about the denormalized representation in this case.

Check the exponent. If the exponent is less less than zero (negative exponent) or the `exp`

field is zero (denorm), return 0 because the result will be rounded to zero anyway.

If the exponent is greater than or equal to 31 (overflow while casting to integer) or the `exp`

field is equal to `0xFF`

(special values), return `0x80000000`

according to the instruction in the prompt.

If the exponent is greater than 24, left shift by `exponent - 24`

.

If the exponent is less than or equal to 24, right shift by `24 - exponent`

.

Finally, we check the sign bit an negate our result if the original number is nagetive.

## 13. floatPower2

1 | /* |

For this question, we should consider 4 conditions:

- Too small to be represented as denormalized
- Denormalized
- Normalized
- Too big

Since we know that we have 1 bit for sign, 8 bits for `exp`

field and 23 bits for `frac`

field in a single precision number, we can figure out the range of those four conditions.

In normalized representation, the maximum value for `exp`

field is 254 (since we cannot have all ones), so the maximum exponent should be `254 - Bias = 254 - 127 = 127`

. The minimum value for `exp`

field is 1 (we cannot have all zeros as well), so the minimum exponent value should be `1 - Bias = 1 - 127 = -126`

.

In denormalized representation, the `exp`

field is always 0 (giving out an exponent of -126) and the powers of two are represented by the `frac`

field. The minimum value in our case is `2^-150`

while the maximum value is `2^-127`

.

Hence, everything with an exponent less than -150 should be regarded as 0, and everything with an exponent greater than 127 should be `+INF`

.

After knowing the range of each representation, we can set up a conditional statement and compose the result.

As we mentioned before, the powers of 2 in denormalized representation are calculated by manipulating the bits in `frac`

field. Therefore, we can simply shift the 1 in `frac`

field by correct amount to get the result.

For normalized representation, we change the value of `exp`

field to get a power of 2, but don’t forget to shift the whole `exp`

field to the correct position (shift to left by 23). Leave the `frac`

field empty so that it will be interpreted as 1.0.