DU Computer Science Bootcamp: Systems Assignment 2

Due: Friday October 2 at 11:59pm


For this assignment you will be writing bit manipulation functions. Your functions will need to perform the same as the standard operations. Testing code will be provided. After writing the specified functions, you will submit them via the department's Git server.

Starting Code

#include <iostream>
#include <iomanip>
#include <cstdint>
#include <cmath>
#include <functional>
#include <string>

union Bits {
  float f;
  uint32_t u;
  int32_t i;
};

void printBits(float in);
void printBits(int32_t in);

float absFloatBitwise(float in);
int32_t absIntBitwise(int32_t in);
float negateFloatBitwise(float in);
int32_t negateIntBitwise(int32_t in);
float doubleFloatBitwise(float in);
int32_t doubleIntBitwise(int32_t in);

void testFloat(std::string name, std::function<float(float)> standardFunction,
               std::function<float(float)> bitwiseFunction);
void testInt(std::string name, std::function<int(int)> standardFunction,
			std::function<int(int)> bitwiseFunction);

void printBits(float in) {
  Bits b;
  b.f = in;

  // Store the current format status of std::out
  std::ios state(nullptr);
  state.copyfmt(std::cout);

  std::cout << b.f << " = 0x"
            << std::uppercase << std::hex << std::setfill('0') << std::setw(8) << b.u << "\n";

  uint32_t sign = ????;
  uint32_t exponent = ????;
  int32_t unbiased_exponent = int32_t(exponent) - 127;
  uint32_t mantissa = ????;

  std::cout << "Sign: 0x" << std::hex << sign << " "
            << "Exponent: 0x" << std::hex << std::setfill('0') << std::setw(2)
            << exponent << " Unbiased: " << std::dec << unbiased_exponent << " "
            << "Mantissa: 0x" << std::hex << std::setfill('0') << std::setw(6)
            << mantissa << " ";

  if (exponent == 0xFF) {
    std::cout << "Special value: ";
    if (mantissa == 0) {
      if (sign == 0)
        std::cout << "positive infinity";
      else
        std::cout << "negative infinity";
    } else {
      std::cout << "NaN";
    }
  } else if (exponent == 0) {
    std::cout << "Denormalized ";
    if (sign == 0)
        std::cout << "positive";
      else
        std::cout << "negative";
  } else {
    std::cout << "Normalized ";
    if (sign == 0)
        std::cout << "positive";
      else
        std::cout << "negative";
  }

  std::cout << "\n";

  // Restore the format status of std::out
  std::cout.copyfmt(state);
}

void printBits(int32_t in) {
  Bits b;
  b.i = in;

  // Store the current format status of std::out
  std::ios state(nullptr);
  state.copyfmt(std::cout);

  std::cout << std::dec << b.i << " = 0x"
            << std::uppercase << std::hex << std::setfill('0') << std::setw(8) << b.u << " ";

  uint32_t sign = ????;

  if (sign == 0)
    std::cout << "positive\n";
  else
    std::cout << "negative\n";

  // Restore the format status of std::out
  std::cout.copyfmt(state);
}

void testFloat(std::string name, std::function<float(float)> standardFunction,
               std::function<float(float)> bitwiseFunction) {
  std::cout << "Testing " << name << std::endl;
  for (uint64_t i = 0; i < (uint64_t{1} << 33); ++i) {
    Bits original;
    original.u = uint32_t(i);

    Bits normal;
    normal.f = standardFunction(original.f);

    Bits bits;
    bits.f = bitwiseFunction(original.f);

    if (normal.u != bits.u) {
      std::cout << "\nFound difference for i = " << std::dec << i << " = 0x"
                << std::uppercase << std::hex << std::setfill('0')
                << std::setw(8) << i << ":\n"
                << "Original:\n";
      printBits(original.f);
      std::cout << "Bitwise calculated:\n";
      printBits(bits.f);
      std::cout << "std::abs calculated:\n";
      printBits(normal.f);
    }
  }
}

void testInt(std::string name, std::function<int(int)> standardFunction,
               std::function<int(int)> bitwiseFunction) {
  std::cout << "Testing " << name << std::endl;
  for (uint64_t i = 0; i < (uint64_t{1} << 33); ++i) {
    Bits original;
    original.u = uint32_t(i);

    Bits normal;
    normal.i = standardFunction(original.i);

    Bits bits;
    bits.i = bitwiseFunction(original.i);

    if (normal.u != bits.u) {
      std::cout << "\nFound difference for i = " << std::dec << i << " = 0x"
                << std::uppercase << std::hex << std::setfill('0')
                << std::setw(8) << i << ":\n"
                << "Original:\n";
      printBits(original.i);
      std::cout << "Bitwise calculated:\n";
      printBits(bits.i);
      std::cout << "Standard calculated:\n";
      printBits(normal.i);
    }
  }
}


int main() {
  testFloat("float absolute value", [](float a){ return std::abs(a); }, absFloatBitwise);
  testInt("int absolute value", [](int a){ return std::abs(a); }, absIntBitwise);

  testFloat("float negate", [](float a){ return -a; }, negateFloatBitwise);
  testInt("int negate", [](int a){ return -a; }, negateIntBitwise);

  testFloat("float double", [](float a){ return 2 * a; }, doubleFloatBitwise);
  testInt("int double", [](int a){ return 2 * a; }, doubleIntBitwise);

  return 0;
}

The functions

There are six functions to write. When writing them you are restricted on the operations you can use to ensure you use bit level manipulations (and, or, not, xor, left shift, right shift). Before worry about these restrictions try to write the function using only bit operations and you will likely come up with solutions that meet the restrictions.

  1. float absFloatBitwise(float in);
    Your solution should be branchless (i.e. no conditional expressions).
  2. int32_t absIntBitwise(int32_t in);
    You may use one branch, but do not use a minus, only bit operations. You may use the increment operator or addition, but only to increment by 1.
  3. float negateFloatBitwise(float in);
    Your solution should be branchless (i.e. no conditional expressions).
  4. int32_t negateIntBitwise(int32_t in);
    Your solution should be branchless (i.e. no conditional expressions).
  5. float doubleFloatBitwise(float in);
    Leave infinities alone. In the case of an overflow, change the number to be the appropriate infinity. In the case of a NaN, on the systems I tested multiplying by 2 turned on a specific bit instead of doing anything sensible. Look at the output to determine the bit and flip it on in the mantissa. Do not use a multiplication. You may use the increment operator or addition, but only to increment by 1.
  6. int32_t doubleIntBitwise(int32_t in);
    Your solution should be branchless (i.e. no conditional expressions). Do not use a multiplication.
Note that there are no "halving" operations to go with the doubling. The reason is because halving for floating point is very tricky to write due to different rounding modes for floating point numbers.

If you are stuck on the branchless restrictions, think of things like setting specific bits to be the way you want them.

Output

When complete and correct, the starting code should output:

Testing float absolute value
Testing int absolute value
Testing float negate
Testing int negate
Testing float double
Testing int double
This took 3.5 minutes on my laptop and 7 minutes on the Linux servers, so the total program you write could easily take as long. As you are working, I recommend you comment out the appropriate test function calls in main as you are working, so you only have one test uncommented at a time while you work on each function.

Should your functions have any errors, all the values which have problems will be printed out with full details.

Submission

Your work must be submitted via the department's Git server. To do so, perform the following steps:

  1. Use the existing "bootcamp" repository you created for Systems Assignment 1
  2. Create a "systems_assignment2" directory and place your source code in it
  3. Commit your changes and push them to the department's server
  4. When complete you can verify the files are present by using the web interface.
If you have any questions or problems with the submission system, email Will.