Strings: print_integer(…)
Goals
This assignment has the following goals:
- Understand number bases
- Understand how printed representations of values relate to in-memory representations
- Practice designing simple algorithms
- 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 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 (print_integer.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 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 fromputchar(…)
. It is equivalent, but please use thefputc(…)
form. If you would like to know more aboutfputc(…)
, see p. 435 in the textbook, the link on the Resources page, or simply typeman fputc
from bash.
Instructions
Start by getting the files. Type
264get hw02
and then
cd hw02
from bash.
You will find only one file: print_integer.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: print_integer.c, test_print_integer.c, and expected.txt. There is no starter code (other than the header file).
print_integer.c is where yourprint_integer(…)
implementation will go.
To start this file, simply copy the function signatures from print_integer.h into
a new file called print_integer.c and then add the braces to make them into
functions.
test_print_integer.c will contain a main(…)
function that will test your
print_integer(…)
.
expected.txt will contain the expected output from running your tests in test_print_integer.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 print_integer.c to create an executable called test_print_integer gcc -o test_print_integer test_print_integer.c print_integer.c # run your executable and send the output to a file called actual.txt (That filename is arbitrary.) ./test_print_integer > actual.txt # compare the actual output with the expected output using the diff command diff actual.txt expected.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)
- bash, && – You can do this with a bash one-liner. To join commands, use the && operator in bash. With
&&
, the second command runs only if the first succeeded (i.e., no compiler warnings or errors). You can also send the output of your test_print_integer directly to diff using the pipe operator (|
). Putting it all together, you get this:
gcc -o test_print_integer test_print_integer.c print_integer.c && ./test_print_integer | diff expected.txt -
To repeat, use your up arrow key (in bash) - bash, alias – To avoid typing all of that, you can create an alias in bash. (This is one command, i.e., one line.)
alias testhw2='gcc -o test_print_integer test_print_integer.c print_integer.c && ./test_print_integer | diff expected.txt -'
After that, you can simply type testhw2 in bash. To make it work in future sessions, add it to your .bashrc toward the end, after the other gcc alias that adds in other compilation flags. - vim, mappings – To do all of this from vim, you can map it to a key, such as F5. (Enter all of that as a single command in Vim.)
:nmap <F5> :!gcc print_integer.c test_print_integer.c -o test_print_integer && ./test_print_integer \| diff - expected.txt<CR>
After that, you can simply press F5 from Vim. To make it work in future sessions, add it to your .vimrc. - diff, return code – A more precise way to verify that it passed—instead of visually looking at the output—is to type
echo $?
in bash. That displays the return code of the last thing you ran, in this case diff. If it is 0, then it means there was no difference, so your test passed. (At the end of any main function, you will usually seereturn 0
, if there was no error. That is the return code.), - vim, splits – To work with multiple files at the same time, use
:sp file.c
to make a horizontal split, or:vs file.c
to make a vertical split. Use F6 to move between splits (using the provided .vimrc). For example, to open all four files for this assignment at once, you could do the following:vim print_integer.c
from bash :sp test_print_integer.c
in vim :vs expected.txt
in vim F6
in vim :vs print_integer.h
in vim
- vim, tabs – Tabs are another way to work with multiple files at the same time. To open all four files in separate tabs, use the -p flag when opening vim:
vim -p print_integer.c print_integer.h test_print_integer.c expected.txt
... or more concisely using wildcards ...
vim -p hw02*.*
To open another file in a new tab while vim is already open, use:tabe
instead of:e
like this:
:tabe test_print_integer.c
Once the tabs are open, you can use Tab and Shift-Tab to switch between tabs or click with the mouse. (This last part with the brackets depends on using our sample .vimrc.)
The result will look like this:
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 print_integer.c, test_print_integer.c, and
expected.txt. Your print_integer(…)
will do
nothing. Your expected.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.
-
Add a single test to your test_print_integer.c and expected.txt files. You will
want to start with
print_integer(…)
, so add a single call toprint_integer(…)
to yourmain(…)
and a corresponding line in your expected.txt. -
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 as48 + 9
, which would give you the ASCII value for the digit '9'. -
Save a copy of your code by running
264submit 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
- Your submission must contain each of the following files, as specified.
file contents print_integer.c functions print integer(int n, int radix, char✶ prefix)
→ return type: voidPrint 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 anyint
. 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, 10, "")
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, 10, "")
should print “-768336”.print integer(-768336, 10, "$")
should print “-$768336”. Notice that the prefix ("$") comes after the "-" and before the first digit.print integer(768336, 16, "")
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, 16, "0x")
should print “0xbb950”. The “0x” prefix is added only because it is passed as an argument toprint_integer(…)
.print integer(-768336, 16, "0x")
should print “-0xbb950”.print integer(-768336, 2, "0b")
should print “-0b10111011100101010000”.print integer(768, 10, ""); print integer(-336, 10, "")
should print"768‑336"
.-
print integer(, , "")
In case of inconsistency, the textual requirements in this document take precedence over this calculator.
Reminder: Your test cases must be your own. Do not copy these or any other test cases.test_print_integer.c functions main(int argc, char✶ argv[])
→ return type: intTest yourprint_integer(…)
. Your main(…) must return EXIT_SUCCESS.expected.txt output Expected output from running your test_print_integer.c. - Your test_print_integer.c must exercise all functionality (e.g., negative numbers, any radix between 2 and 36, largest possible n, smallest possible n, etc.).
-
Only the following external header files, functions, and symbols are
allowed in your print_integer.c. That means you may use
printf(…)
in your test_print_integer.c but not in your print_integer.c. You may usefputc(…)
andstdout
in either one (or both).header functions/symbols allowed in… stdio.h fputc
,stdout
print_integer.c
,test_print_integer.c
stdio.h printf
test_print_integer.c
stdlib.h EXIT_SUCCESS
test_print_integer.c
limits.h INT_MAX
,INT_MIN
test_print_integer.c
stdbool.h bool
,true
,false
print_integer.c
,test_print_integer.c
assert.h assert
test_print_integer.c
,test_print_integer.c
- Repeat: Do not call
printf(…)
- Do not make any assumptions about the size of an
int
. - Submissions must meet the code quality standards and the course policies on homework and academic integrity.
Pre-tester ●
The pre-tester for HW02 has been released and is ready to use.
Your score will be posted to the Scores page after the deadline for each assignment.
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 20 sloc*.
cloc
reports 20 sloc for the whole file.
* sloc = "source lines of code" (excluding comments and blank lines)
Here's a screenshot of all three files that you will be turning in. (This screenshot was taken with a version of the code that had a bug. It didn't handle INT_MIN properly. Fixing the bug added another 4 lines to the code.)
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
264submit hw02 print_integer.c test_print_integer.c expected.txt
Q&A
-
Is an int always in decimal (base 10)?
No. See the next question. -
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?
These questions, while common, don't actually make sense. Here's why: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.
To answer these questions more directly: Yes, your print_integer(…) will work no matter how the inputs were specified, but you don't do anything special to make that happen. -
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 usingfputc(ch, stdout)
until you encounter the'\0'
. -
Should I print the
'\0'
?
No. -
Should hex (or other base >10) digits above 10 be uppercase or lowercase?
Lowercase. -
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 strings. -
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 strings. -
Will my print_integer be called directly by external code?
Yes. -
What are we turning in?
The specification (above) lists three files. Your expected.txt is just a plain text file. When you compile and run your test_print_integer.c file, the output should match your expected.txt file exactly. It should work the same, even if we substitute in the solution print_integer.c or another classmates' correct print_integer.c. -
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. -
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 print_integer.c with someone else's test_print_integer.c and expected.txt, or vice versa. -
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). -
What is the difference between a "radix" and a "base"?
They are synonyms for the same thing. -
Should I write
#include "print_integer.h"
(quotation marks) or#include <print_integer.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 "print_integer.h"
(You will need to include one or two other standard headers.) -
May we modify print_integer.h?
No. -
May we turn in our own print_integer.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 print_integer.h on top. Thus, any print_integer.h you submit will be overwritten. -
May we
#include <math.h>
?
No. See requirement #3 (above). -
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. -
How can we be sure to test with the largest and smallest possible
int
on the platform?
You may useINT_MAX
andINT_MIN
from#include <limits.h>
in your test_print_integer.c. No matter where you compile or run your code, those will contain the largest and smallest possible values of anint
on that platform. -
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 test_print_integer.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 yourprint_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. -
Within
print_integer(…)
what is the number base of n?
Anint
has no number base. A number base is only relevant when writing an integer literal in your code (e.g., the768336
part of the codeint n = 768336;
) and when printing a number to the console (print_integer(…)
,printf(…)
, etc.). -
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"; for(int idx_in_s = 0; s[idx_in_s] != '\0'; idx_in_s++) { fputc(s[idx_in_s], stdout); }
This snippet was updated to use a for loop, as per the code quality standards.
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 codechar* s = "La";
, s takes you to an array of three (not two) characters:'L'
(76),'a'
(97),'\0'
(0). If you had the codechar* t = ""; // empty string
, t takes you to an array of one character:'\0'
(0). -
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
The problem is that n_abs is greater than INT_MAX so it is too big for anint
.int
.
If you are trying to store the absolute value of n, you may want to assign it to anunsigned int
. This is another data type that has a larger range than anint
. 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
-
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 Q22 above. If you really wanted to know if prefix is""
, a right way is this:if(prefix[0] == '\0') { // … }
-
May I use an array to store the string?
No. Since you are not allowed to make any assumptions about the size of anint
, you don't know how big 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. -
May I use recursion?
Yes, you may if you wish, but it is not necessary. -
How do I add a newline between my test cases?
You may copy/adapt this if you wish:fputc('\n', stdout);
-
What is EXIT_SUCCESS?
EXIT_SUCCESS
is a symbolic constant for the number 0. Your code would work just as well if you wrotereturn 0
but don't do that. Writereturn EXIT_SUCCESS
instead. It makes your code more readable because "success" is what you meant. Only use 0 if you are actually referring to zero of something (e.g., zero bananas, zero passes through a loop, the zero'th element in an array, etc.). GCC says: “…implicit declaration of function…”. What does it mean?
You tried to call a function without first declaring it.
Example:
test_print_integer.c: In function ‘main’: test_print_integer.c:11:2: warning: implicit declaration of function ‘print_integer’ [-Wimplicit-function-declaration] print_integer(0, 10, ""); ^~~~~~~~~~~~~
In this example, we called
print_integer(…)
on line 11 of test_print_integer.c, but the compiler (GCC) doesn't know what parameters that function expects and what it returns. That can happen if you forget to#include
print_integer.h in test_print_integer.c.Solution: Make sure you #include "print_integer.h" in your test_print_integer.c.
GCC says: "… makes integer from …" What does it mean?
Example:
test_print_integer.c:12:8: warning: passing argument 1 of ‘fputc’ makes integer from pointer without a cast [-Wint-conversion] fputc("\n", stdout); ^~~~
This is one of the most confusing error messages you will see from GCC.
It is telling you that you mistakenly used a string literal (e.g.,
"a"
) where achar
(e.g.,'a'
) was expected.Remember: Use single quotes for a character literal (e.g.,
'a'
). Use double quotes for a string literal (e.g.,"a"
).GCC says: “▒.h: No such file or directory” What does it mean?
That can happen if one of your
#include
statements is not correct.Example:
test_print_integer.c:5:10: fatal error: print_integer.h: No such file or directory #include <print_integer.h> ^~~~~~~~~~~~~~~~~
In this case, the problem is that we used angle brackets (
<
and>
) around one of our own header files. In general, you use#include <▒▒▒.h>
for standard header files (e.g., stdio.h), and#include "▒▒▒.h"
for header files that are part of your project. The difference in syntax tells the compiler (GCC) where to look for the specified ▒▒▒.h file.GCC says: “multiple definition of `print_integer`” What does it mean?
It means GCC found two definitions of the same function. In the simplest case, that could happen if you had this:
void print_integer(…) { } void print_integer(…) { }
However, for this assignment, a common mistake is with the
#include
statements. If you mistakenly do this…#include "print_integer.c" // ✘ INCORRECT
… then that
#include
statement is replaced by the contents of the implementation file (print_integer.c), which contains the function definition ofprint_integer(…)
. You should only#include
header files (e.g., print_integer.h), since they contain only function declarations (function signature but not function body).Solution: Make sure you have this…
#include "print_integer.h" // ✔ CORRECT
… and not this…
#include "print_integer.c" // ✘ INCORRECT
GCC says: “undefined reference to `main'” What does it mean?
Example:
/lib/../lib64/crt1.o: In function `_start': (.text+0x20): undefined reference to `main' collect2: error: ld returned 1 exit status
Solution: Make sure you are including test_print_integer.c when you compile.
Incorrect:
✘ $ gcc print_integer.c -o test_print_integer
Correct:
✔ $ gcc print_integer.c test_print_integer.c -o test_print_integer
Diff says: “No newline at end of file” What does it mean?
Translation: One of the two things diff is comparing has a newline ('\n') at the end; the other does not. Your expected.txt almost certainly ends with a newline. Vim (like most/all other editors) adds a newline to the last line of any non-empty file. Thus, it is probably the output of your program that does not.
Solution: Add
fputc('\n', stdout)
after each call toprint_integer(…)
in yourmain(…)
.When I first log in—i.e., before running any commands—I get a message telling me that my disk quota is exceeded?
Sometimes, this happens when a student has an infinite loop in their
print_integer(…)
that causes it to print a large number of some character (e.g., “00000000000000000000000000000000…”). When they run./test_print_integer > actual.txt
the actual.txt file fills up with a large number of some character. That file gets so big that it exceeds the maximum amount of storage that they are allowed to use on the system.To find out if this is your problem, look at the contents of your HW02 directory.
you@eceprog ~ $
cd 264
you@eceprog ~/264 $
cd hw02
you@eceprog ~/264/hw02 $
ls -l
If the size of actual.txt is very large (i.e., more than about 10,000 bytes or so), then delete it.
you@eceprog ~/264/hw02 $
rm actual.txt