CSE 351 - Spring 2010 - Section 2

Faking If-Else with Boolean Variables

if (x) y else z;
( (x << 31) >> 31) & y + (!x << 31 ) >> 31 ) & z

First, let's break apart this expression into two parts:

( (x << 31) >> 31) & y
(!x << 31 ) >> 31 ) & z

We want exactly one of these expressions to be non-zero at any time, depending on the value of x. We combine them with a "+", which is the same as logical OR ("|") between a zero and non-zero bitvector.

If x is 1, then we want the first expression to evaluate to just y and the second expression to be zero.

Alternatively, if x is 0, then we want the second expression to evaluate to just z and the first expression to be zero.

(x << 31) moves x's least significant bit (LSB) to the most significant bit (MSB) position.

(x << 31) >> 31 shifts the MSB back to the LSB. If x was originally 1, then now x is now all 1's because arithmetic right-shift extends the sign-bit. If x was originally 0, then now x is all 0's.

(!x << 31) >> 31 shifts the opposite of the LSB to the MSB and back, with arithmetic sign-bit extension. If x was originally 1, then now x is now all 0's. If x was originally 0, then now x is all 1's.

AND'ing any value (for example, y or z) to all 1's returns the same value back. AND'ing any value to all 0's returns all 0's.

Therefore, this expression gives us back either y or z, depending on whether x is 0 or 1, as desired.

Floating Point Representation

Why are denormalized values so dense near 0?

This seems to imply that precision in small numbers is more important than precision in large numbers. Intuitively, we can understand this as the influence of numerical analysts on the IEEE standard, as opposed to the hardware designers.

For large numbers, like the distance from the Earth to the sun, or the U.S. budget deficit, small differences are neglible relative to the sheer magnitude of the number. A few fractions of a foot, or tenths of a cent, don't really matter when measuring the millions of miles that separate us from our nearest star, or the trillions of dollars that we owe to other countries.

On the other hand, for small numbers, precision really does matter because the fractional part of a number is a more significant part of the total number. For example, when measuring the wavelength of visible light (nanometers, or 1e-9 meters), having extra digits of precision matter.

Therefore, having a special range of really small numbers densely packed around zero is important for numerical applications, like scientific and business calculations. However, this inconsistency in the IEEE standard is more complicated to implement in hardware.

Like many standards, the IEEE floating point standard is a compromise between purely technical concerns (which make the most sense engineering and implementation-wise) and social or human concerns (which make the standard more useful to normal, often non-technical users).

Homework Tips

Use intermediate variables with comments.

In the example above, one long expression is hard to read.

( (x << 31) >> 31) & y + (!x << 31 ) >> 31 ) & z

By breaking it up into intermediate variables, you can make it easier to read and also add comments on each line.

int x_is_one = ((x << 31) >> 31); /* shift LSB of x up to MSB and back again */
int x_is_zero = (!x << 31) >> 31); /* shift opposite of LSB up to MSB and back again */
int ifelse = (x_is_one & y) + (x_is_zero & z);

Use printf with %x

You can print out hex values with the following conversion specifier for printf. Unfortunately, C does not support built-in display of binary digits (bits) directly.

Keep in mind that 1 hex character is equal to 4 bits.

printf("%032x\n", thirty_two_bit_int);

(2**n) - 1

We use the ** operator to indicate exponent, as in 2 raised to the power of n.

To generate this expression using only basic operators, first we raise 2 to the nth power, which is the same as multiplying 1 by the nth power-of-two, which is the same as bit-shifting to the left.

2**n => 1 << n => 1 followed by n zero's.

Next, we add a negative one (since we cannot subtract). This is the same as adding the two's complement of 1.

- 1 => + (-1) => (~1 + 1) => all one's

Adding (2**n) and (-1) causes the nth bit (which is a 1 in 2^n) to carry over and overflow all the way up to the 32nd (most significant) bit, so these bits are all zeros. The remaining lower n bits are all 1's.

0 ... 0 1 ... n one's ... 1