# CSAPP Datalab (New)

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

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

TMin = 1000...0000 in binary, so shift 1 to the left (0000...0001 in binary) by 31 to get TMin.

## 3. isTmax

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

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

-a = ~a + 1

## 6. isAsciiDigit

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

## 7. conditional

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

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

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

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

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

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

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.