Advanced C Programming

Autumn 2016 :: ECE 264 :: Purdue University

Due 8/31

Strings: print_integer(…)

Goals

This assignment has the following goals:

  1. Understand number bases
  2. Understand how printed representations of values relate to in-memory representations
  3. Practice designing simple algorithms
  4. Get a gentle introduction to test-driven development

Overview

You will create a function called print_integer(…) that prints a given integer in a specified number base.

This is the first step toward future assignments in which you will implement your own version of printf(…) and sprintf(…). You will use your code from this assignment in the future assignments.

Prepare

Read this assignment description carefully, including the Code Quality Standards.

Next, make sure you understand each of the topics below. You may need to do a bit of external reading. We have suggested several concise references to help you get up to speed.

#include
The #include directive is effectively replaced with the entire contents of the file it refers to. See pp. 90-91 in the textbook or the links on the Resources page.
#include guards
You will not need to use #include guards for this assignment, but you will see them in the header file (hw02.h). See the link on the Resources page.
number bases
For this assignment, you will need a good understanding of number bases. You will be writing code to express integers in a variety of number bases (e.g., binary, hexadecimal, etc.). However, most of the algorithms you see online will give you the digits from right-to-left. You will need to print them left-to-right. See pp. 134-137 in the textbook or the links on the Resources page.
fputc(…, stdout)
To print a single character (ch) to the console (stdio), use this code: fputc(ch, stdout); That's all you need to know, for now. Some may ask how this is different from putchar(…). It is equivalent, but please use the fputc(…) form. If you would like to know more about fputc(…), see p. 659 in the textbook, the link on the Resources page, or simply type man fputc from bash.
Did you read the policies about homework and academic integrity? If not, please do so now. They contain requirements that apply to this and all other homework assignments. We will assume you have read and understood them completely.

Instructions

Start by getting the files. Type 264get hw02 and then cd hw02 from bash.

You will find only one file: hw02.h. That is the header file. It defines the signature of the function you will implement: print_integer(…).

You will be creating three new files: hw02.c, hw02test.c, and hw02test.txt. There is no starter code (other than the header file).

hw02.c is where your print_integer(…) implementation will go. To start this file, simply copy the function signatures from hw02.h into a new file called hw02.c and then add the braces to make them into functions.

hw02test.c will contain a main(…) function that will test your print_integer(…).

hw02test.txt will contain the expected output from running your tests in hw02test.c. It will be a simple text file.

To test if your code works perfectly, you will run the following:

# compile your test together with your hw02.c to create an executable called hw02test
gcc -o hw02test hw02test.c hw02.c

# run your executable and send the output to a file called hw02test.actual.txt (That filename is arbitrary.)
./hw02test > hw02test.actual.txt

# compare the actual output with the expected output using the diff command
diff hw02test.actual.txt hw02test.txt

The diff command prints the differences so if you see any output at all, then your test failed. If you see no output, then it passed.

Pro tips (optional)

Test-driven development

The most efficient way to complete this assignment will be to follow a the test-driven development method.

First, you will need to set up a skeleton hw02.c, hw02test.c, and hw02test.txt. Your print_integer(…) will do nothing. Your hw02test.txt will be empty. Therefore, the test procedure described above will appear to pass. Now, repeat the following 2-step process until the assignment is complete.

  1. Add a single test to your hw02test.c and hw02test.txt files. You will want to start with print_integer(…), so add a single call to print_integer(…) to your main(…) and a corresponding line in your hw02test.txt.
  2. Add just enough to your print_integer(…) so that it can pass that test. Hint: To print the digit 9, you can simply add the character literal for '0' to the number 9, i.e., '0' + 9. To the C compiler, there is almost no difference between '0' and the number 48 (ASCII value of '0'), so to that expression is the same as 48 + 9, which would give you the ASCII value for the digit '9'.
  3. Save a copy of your code by running 264submit hw02 hw02* every time you get a new part of the functionality working.

You will want to start with very simple test cases, e.g., print_integer(1, 20, ""). Then, add a test for something slightly more complicated (e.g., multi-digit number) and implement enough of print_integer(…) to make that work. Keep adding tests and the corresponding code until print_integer(…) handles everything.

Requirements

  1. Your submission must contain each of the following files, as specified.
    file contents
    hw02.c functions
    print integer(intXn,XintXradix,Xchar✶Xprefix)
    return type: void
    Print the number n to the console (stdout) in the specified number base (radix), with the prefix immediately before the first digit. radix may be any integer between 2 and 36 (inclusive). n may be any int. For values of radix above 10, use lowercase letters to represent the digits following 9. print_integer(…) should not print a newline ('\n' or '\r'). Examples:
    • print integer(768336,X10,X"") should print “768336” because the number seven-hundred sixty-eight thousand three-hundred thirty-six would be written as “768336” in base 10 (decimal).
    • print integer(-768336,X10,X"") should print “-768336”.
    • print integer(-768336,X10,X"$") should print “-$768336”. Notice that the prefix ("$") comes after the "-" and before the first digit.
    • print integer(768336,X16,X"") should print “bb950” because the number seven-hundred sixty-eight thousand three-hundred thirty-six would be written as “bb950” in base 16 (hexadecimal).
    • print integer(768336,X16,X"0x") should print “0xbb950”. The “0x” prefix is added only because it is passed as an argument to print_integer(…).
    • print integer(-768336,X16,X"0x") should print “-0xbb950”.
    • print integer(-768336,XX2,X"0b") should print “-0b10111011100101010000”.
    • print integer(768,X10,X"");Xprint integer(-336,X10,X"") should print "768‑336".
    Reminder: Your test cases must be your own. Do not copy these or any other test cases.
    hw02.h declarations
    declaration of print_integer(…)
    hw02test.c functions
    main(intXargc,Xchar✶Xargv[])
    return type: int
    Test your print_integer(…). Your main(…) must return EXIT_SUCCESS (0).
    hw02test.txt output Expected output from running your hw02test.c.
    Either type the function signatures into your hw02.c or copy from your hw02.c. Copying from this web page won't work.
  2. Your hw02test.c must exercise all functionality (e.g., negative numbers, any radix between 2 and 36, largest possible n, smallest possible n, etc.).
  3. The only external functions/macros/symbols you may use in your hw02.c are fputc(…), va_list, va_start, va_arg, va_end, and stdout.
  4. Only the following external header files, functions, and symbols are allowed in your hw02.c. That means you may use printf(…) in your hw02test.c but not in your hw02.c. You may use fputc(…) and stdout in either one (or both).
    header functions/symbols allowed in…
    stdio.h fputc, stdout hw02.c, hw02test.c
    stdio.h printf hw02test.c
    stdlib.h EXIT_SUCCESS hw02test.c
    limits.h INT_MAX, INT_MIN hw02test.c
    stdbool.h bool, true, false hw02test.c
    All others are prohibited unless approved by the instructor. Feel free to ask if there is something you would like to use.
  5. Repeat: Do not call printf(…)
  6. Do not make any assumptions about the size of an int.
  7. Submissions must meet the code quality standards and the course policies on homework and academic integrity.

How much work is this?

This is a little harder than it looks. Getting your print_integer(…) to work properly with any valid int is a puzzle that you need to solve. However, it does not require very much code. For example, the instructor's solution for print_integer(…) is only 18 sloc*

* sloc = "source lines of code" (excluding comments and blank lines)

Here's a screenshot of all three files that you will be turning in.

Your test cases must be your own. Do not copy test cases from here or anywhere else. We are sharing a few test cases so you can see how this works. In general, our code is not fair game unless explicitly stated otherwise. See the Syllabus.


Again… Do not copy these or any other test cases.

Submit

To submit, type the following in bash:
264submit hw02 hw02.c hw02test.c hw02test.txt

Q&A

  1. Is an int always in decimal (base 10)?
    No. See the next question.
  2. Does my print_integer need to deal with inputs that are specified in number bases other than decimal? For example, does my print_integer need to be able to convert from hex to binary?
    An int does not have a number base. It is just a value. The base (or "radix") of a number only refers to the way it is written, i.e., in your C code, or in the output of a program you write. For example, if n is the number of fingers on two hands, you could write that as 10, 0b1010, or 0xa, but it the same quantity no matter how you write it.

    When gcc reads your code for the first time, it interprets any integer literals (raw numbers in your code) according to how you write your code. Once your code has been compiled, the base that you used to write it becomes completely moot.

    Example: To gcc, the following are equivalent and have exactly the same effect.
    int n = 65;
    printf("n can be written as %d in decimal, as 0x%x in hexadecimal, or as %c if interpreted as an ASCII character.", n, n, n);
    int n = 0x41;
    printf("n can be written as %d in decimal, as 0x%x in hexadecimal, or as %c if interpreted as an ASCII character.", n, n, n);
    int n = 0b1000001;  // non-standard, but accepted by gcc; do not use for ECE 264
    printf("n can be written as %d in decimal, as 0x%x in hexadecimal, or as %c if interpreted as an ASCII character.", n, n, n);
    They all print the following:
    n can be written as 65 in decimal, as 0x41 in hexadecimal, or as A if interpreted as an ASCII character.
  3. How do I print a string?
    Remember that a string is just an array of characters with a null character ('\0') at the end. Just start with the first character, and keep printing characters using fputc(ch, stdout) until you encounter the '\0'.
  4. Should I print the '\0'?
    No.
  5. Should hex (or other base >10) digits above 10 be uppercase or lowercase?
    Lowercase.
  6. Why do we need the prefix parameter to print_integer?
    Some number formats use a prefix (e.g., "$" for currency). For negative numbers, it is customary to put the prefix after the minus sign (e.g., "-$3.00", not "$-3.00"). This parameter allows you to print numbers in that way. It will be used further in HW04.
  7. Are there any other examples of number formats with prefixes?
    Yes. Some number formats use a prefix (e.g., "$" for currency). For negative numbers, it is customary to put the prefix after the minus sign (e.g., "-$3.00", not "$-3.00"). This parameter allows you to print numbers in that way. It will be used further in HW04.
  8. Will my print_integer be called directly by external code?
    Yes.
  9. What are we turning in?
    The specification (above) lists three files. Your hw02test.txt is just a plain text file. When you compile and run your hw02test.c file, the output should match your hw02test.txt file exactly. It should work the same, even if we substitute in the solution hw02.c or another classmates' correct hw02.c.
  10. Is an int the same as an "integer"?
    When we say int, we mean the C data type, which has a limited range (-2,147,483,648 to 2,147,483,647 on many 64-bit systems, but you may not assume those particular bounds). An "integer" is a mathematical concept; it is the same as a whole number.
  11. What if my output doesn't match up with someone else's output perfectly?
    This assignment is tightly specified, so the output of any two correct submissions should match perfectly. Likewise, we should be able to mix and match your hw02.c with someone else's hw02test.c and hw02test.txt, or vice versa.
  12. What radixes (number bases) must my print_integer(…) support?
    Your print_integer should support any value for radix between 2 and 36 (inclusive). For radixes above 10, it should represent digits beyond than 9 with lowercase letters. For example, print_integer(11, 16, "") would print "b" because b is the (11-9)th lowercase letter of the alphabet. Similarly, print_integer(20, 21, "") would print "k" because k is the (20-9)th letter of the alphabet.

    This converter may help you get a sense (and test your code).
  13. What is the difference between a "radix" and a "base"?
    They are synonyms for the same thing.
  14. Should I write #include "hw02.h" (quotation marks) or #include <hw02.h> (angle brackets)?
    Use quotation marks for header files in your project. Use angle brackets for standard header files. Thus, you should start with this:
    #include <stdio.h>
    #include "hw02.h"
    (You will need to include one or two other standard headers.)
  15. May we modify hw02.h?
    No.
  16. May we turn in our own hw02.h?
    Yes, you may turn it in, but that file will be ignored. We will copy your files into an empty directory and then copy our test files, including hw02.h on top. Thus, any hw02.h you submit will be overwritten.
  17. May we #include <math.h>?
    No. See requirement #4 (above).
  18. May we use sizeof(‥)?
    Yes. sizeof(‥) is unusual in that it is technically an operator, even though it looks like a function. You may use any valid C99 operators you like.
  19. How can we be sure to test with the largest and smallest possible int on the platform?
    You may use INT_MAX and INT_MIN from #include <limits.txt> in your hw02test.c. No matter where you compile or run your code, those will contain the largest and smallest possible values of an int on that platform.
  20. How can I test converting from bases other than 10?
    You don't. print_integer(…) is not converting from one base to another. The parameter n has no base. Although you may use base 10, 16, or 8 to write the integer literals for your test cases in hw02test.c, that is just a superficial aspect of how you wrote your code. The compiler just reads your code and converts them to values. Within your print_integer(…) there is no way to know what notation was used to write the C code that caused those values to be passed to it.
  21. Within print_integer(…) what is the number base of n?
    An int has no number base. A number base is only relevant when writing an integer literal in your code (e.g., the 768336 part of the code int n = 768336;) and when printing a number to the console (print_integer(…), printf(…), etc.).
  22. How do I print the prefix?
    This is covered in Q3 (above). You may copy/adapt this if you wish:
    char* s = "La fu ta ish ma za ra";
    int idx_in_s = 0;
    while(s[idx_in_s] != '\0') {
        fputc(s[idx_in_s], stdout);
        idx_in_s += 1;
    }
    It simply prints "La fu ta ish ma za ra" (nonsense), with no new line at the end.

    We will talk more about strings soon, but the basic idea is that a string is an array of characters (char). When stored in memory, it always ends in the null terminator, a character with the the value 0. It should be written as '\0' in code, to make it clear what you mean. So when you have the code char* s = "La";, s takes you to an array of three (not two) characters: 'L' (76), 'a' (97), '\0' (0). If you had the code char* t = ""; // empty string, t takes you to an array of one character: '\0' (0).
  23. When I try to print INT_MIN strange things happen. What's going on?
    You're probably getting an overflow error. Take the following example:
    int n     = INT_MIN;     // -2147483648 on our platform
    int m     = INT_MAX;     //  2147483647 on our platform
    int n_abs = -n;  // would be 2147483648, but it overflows!!!  It is too big for an int.
    The problem is that n_abs is greater than INT_MAX so it is too big for an int.

    If you are trying to store the absolute value of n, you may want to assign it to an unsigned int. This is another data type that has a larger range than an int. The code above could be written as follows:
    int n         = INT_MIN;  // -2147483648 on our platform
    int m         = INT_MAX;  //  2147483647 on our platform
    unsigned int n_abs = -n;  //  2147483648 ... no problem
  24. Why can't I check if prefix == ""?
    The following code will not work like you expect:
    if(prefix == "") {
        // …
    }
    For this assignment, you probably don't want to do this anyway. Just use the snippet in Q21 above. If you really wanted to know if prefix is "", a right way is this:
    if(prefix[0] == '\0') {
        // …
    }
  25. May I use an array to store the string?
    You don't want an array for this. Since you are not allowed to make any assumptions about the size of an int, you don't know how bit to make the array. Just figure out how to print the number left-to-right. First, make the code work perfectly for a one-digit number. Then, make it work for two-digit numbers. Then, make it work for three-digit numbers. By the time you get that far, you will see a pattern and a very easy way to print the number left-to-right.
  26. May I use recursion?
    Yes, you may if you wish, but it is not necessary.
  27. How do I add a newline between my test cases?
    You may copy/adapt this if you wish:
    fputc('\n', stdout);

Updates

8/29: Added Q17-Q20 to the Q&A; clarified description of print_integer and added some examples. Allow EXIT_SUCCESS from stdlib.h in hw02test.c; Allow INT_MAX and INT_MIN from limits.h in hw02test.c; removed erroneous text from description of text.txt: It must exercise all of the functionality in those two functions. For this homework, that means that in the course of running your hw02test.c file, every line of code in your hw02.c should be executed at some point. To do this, you simply have several calls to print_integer(…) that include all of the format codes above, including a variety of valid values, including positive integers, negative integers, positive float values (for currency), negative float values, 0, strings, and an empty string.

8/30: Added two more examples: print integer(-768336,X16,X"0x") and print integer(-768336,XX2,X"0b"); added Q22-Q27 to the Q&A