By the time you have completed this lab, you should be able to:
You'll also get more practice with the things you learned in labs lab00 and lab01:
You'll need some of the skills from lab00 and lab01 in order to complete this lab.
We will not accept your lab02 submission until lab00 and lab01 are complete. So please go finish lab00 and lab01 if you haven't submitted them yet, via the turnin program.
If you've forgotten how, consult steps 1 and 2 from lab01.
Visit the following web link—you may want to use "right click" (or "control-click" on Mac) to bring up a window where you can open this in a new window or tab:
You should see a listing of several C programs. We are going to copy those into your ~/cs16/lab02 directory all at once with the following command:
cp ~pconrad/public_html/cs16/09F/labs/lab02/files/* ~/cs16/lab02
cp: target `/cs/student/youruserid/cs16/lab02' is not a directory
The * symbol in this command is a "wildcard"—it means that we want all of the files from the source directory copy be copied into the destination directory namely ~/cs16/lab02.
After doing this command, if you cd into ~/cs16/lab02 and use the ls command, you should see three files in your ~/cs16/lab02 directory:
Note that command indicates that we want to copy all of the files in the
During lab00, you copied a file into your directory ~/cs16/lab00 called firstCProgram.c
This week, we are going to copy that program into your ~/cs16/lab02 directory.
-bash-3.2$ lsIf so, you are ready for step 3.
fourthCProgram.c fToCWithTests.c thirdCProgram.c
-bash-3.2$
Our next step is to look over the contents of thirdCProgram.c
There are at least three ways we can display the contents of thirdCProgram.c on our screen. Try each of them in turn:
Now that I've got a way to look over thirdCProgram.c, what am I looking for?
Note that thirdCProgram.c is a program we've seen in lecture before—in our lecture about test-driven development. I want you to see the checkExpect function that we discussed in lecture, the smaller function—with a stub, and our main function.
Your first task is to just read over the code and understand it—don't make any changes yet.
Once you feel that you understand what is in the code, go ahead and compile and run it, with the commands:
make thirdCProgram
./thirdCProgram
It should look something like this:
-bash-3.2$ make thirdCProgram cc thirdCProgram.c -o thirdCProgram -bash-3.2$ ./thirdCProgram Test failed for Test case 1: expected -1234567.000000 got 3.000000: Test failed for Test case 2: expected -1234567.000000 got 3.000000: Test failed for Test case 3: expected -1234567.000000 got 3.000000: Test failed for Test case 4: expected -1234567.000000 got 7.000000: Test failed for Test case 5: expected -1234567.000000 got 0.000000: Test failed for Test case 6: expected -1234567.000000 got 0.000000: Test failed for Test case 7: expected -1234567.000000 got -5.000000: Test failed for Test case 8: expected -1234567.000000 got -5.000000: -bash-3.2$
You can see that the test cases are all failing, because we have a stub for the smaller() function instead of some working code.
That is, we have:
// return the smaller of two integers a,b---or return a if they are both equal
int smaller(int a, int b)
{
return -1234567; // stub value! This tests the test.
}
where we should have something like this:
// return the smaller of two integers a,b---or return a if they are both equal
int smaller(int a, int b)
{
if (a <= b) return a; else return b;
}
This fact that all the test cases are failing here is actually a good sign! It means our test cases are working!
(Later in this lab, we'll see a case where the opposite is true—a case that I hope will help you appreciate the value of using stub functions to 'test the test')
Now that we've seen that the test function is doing its job, our next step is to replace the stub code with working code.
To bring up emacs to make changes to thirdCProgram.c, type:
emacs thirdCProgram.c
This will either bring up a new window where you can edit this program, or if you are working with a basic terminal program (such as PuTTY on Windows), it will bring up the program directly in your terminal window.
See the discussion of this question in lab01. The short answer is: no, you may use any text editor—but the only ones we'll provide help for are emacs (and maybe vim too)—and learning either emacs or vim is a pretty basic skill that is very helpful to learn.
If you need a review of emacs command, look back at lab01, where there is a handy chart of basic emacs commands, plus a link to a handy emacs tip-sheet.
What to change
Then, use the following commands to compile and run the program again.
make thirdCProgram
./thirdCProgram
If you have syntax errors in your program—for example, a missing semicolon, or some other problem, you may get error messages when you type the make thirdCProgram command. If this happens, try—at first—to see if you can figure out what is wrong on your own before asking the TA for help. But, if after 5-10 minutes of really trying to figure it out, you are still stuck, then you may want to ask for assistance.
If you don't have errors, then
the output should look something like this:
-bash-3.2$ make thirdCProgram
cc thirdCProgram.c -o thirdCProgram
-bash-3.2$ ./thirdCProgram
Test passed for Test case 1
Test passed for Test case 2
Test passed for Test case 3
Test passed for Test case 4
Test passed for Test case 5
Test passed for Test case 6
Test passed for Test case 7
Test passed for Test case 8
-bash-3.2$
If so, then you are almost ready to move on to Step 6—but first, one more Unix trick. Earlier, you were told that you needed to remove all the comments with @@@ in them from your code. Here's a trick to make sure you did it.
At the Unix prompt, type:
grep @@@ thirdCProgram.c
If you see the following output—i.e. just another Unix prompt—it means that the sequence @@@ does not appear anywhere in your thirdCProgram.c file. You are ready to move on!
-bash-3.2$ grep @@@ thirdCProgram.cIf instead, you see this, then it is a listing of all the lines containing @@@—lines that you need to remove from thirdCProgram.c before you submit it for grading.
-bash-3.2$
-bash-3.2$ grep @@@ thirdCProgram.c // @@@ The function "smaller" below is "deliberately broken" in the version of the file // @@@ you will copy from the website. The first version has, as the body: // @@@ return -1234567; // stub value! // @@@ where it should have something like this: // @@@ if (a <= b) // @@@ return a; // @@@ else // @@@ return b; // @@@ First, run the program with the stub in place, and see all the tests fail. // @@@ Then replace the stub with corrected code, and see all the tests pass. // @@@ Finally, remove this entire comment block. -bash-3.2$
In general, the grep program is a Unix utility program that allows you to find all matches of a particular sequence of a characters in a file.
One last item: use this command to make sure that the first line in the file contains your name:
head -1 thirdCProgram.c
This should show a command with your name, CS16 lab02 and the date, like this:
-bash-3.2$ head -1 thirdCProgram.c // Chris J. Gaucho for CS16 lab02, 10/08/2009 -bash-3.2$
If instead, you see this, then you forgot your header comment—so fix it now!
-bash-3.2$ head -1 thirdCProgram.c // thirdCProgram.c example of test driven development -bash-3.2$
Now, take a look at fourthCProgram.c.
It is similar to thirdCProgram.c, except that:
Specifically, the checkExpect function has a bug in it—one of the most common bugs that C/C++ programmers make. The bug is easy to spot—I've even put a comment on it. But, I don't want you to fix it right away.
Instead, run the program first, and notice the weird output that we get. We would expect, with a stub such as:
// return the larger of two integers a,b---or return a if they are both equal int larger(int a, int b) { return -1234567; // @@@ STUB---DON'T replace with the correct answer at first... }
that we'd see all the tests failing. But instead, we see:
-bash-3.2$ ./fourthCProgram
Test passed for Test case 1
Test passed for Test case 2
Test passed for Test case 3
Test passed for Test case 4
Test failed for Test case 5: expected 0.000000 got 0.000000:
Test failed for Test case 6: expected 0.000000 got 0.000000:
Test passed for Test case 7
Test passed for Test case 8
Test passed for Test case 9
Test passed for Test case 10
-bash-3.2$
As we discussed in lecture, the reason is that in our test function, we have:
if ( check = expect) // @@@ FIX ME A THE APPROPRIATE STEP, BUT NOT RIGHT AWAY
{
printf("Test passed for %s\n",label);
... etc
where instead, what we need is:
if ( check == expect) // test whether we got what we expect
{
printf("Test passed for %s\n",label);
... etc
That much, you may have already guessed—but the more interesting question is: why did we get the results that we got? Specifically:
The answer lies in two curious aspects of C:
The assignment check = expect takes the value on the right hand side, and assigns check to be the same value—then uses that value as the result of the if test. So:
Look back at which tests passed, and which ones failed—can you see now why tests 5 and 6 "failed", while all the rest "passed"?
Here's what to do next
To complete this step, first add your header comment at the top of fourthCProgram.c:
// Chris J. Gaucho for CS16 lab02, 10/08/2009
Then change the line of code in the checkExpect function from:
if ( check = expect) // @@@ FIX ME A THE APPROPRIATE STEP, BUT NOT RIGHT AWAY
where instead, what we need is:
if ( check == expect) // test whether we got what we expect
Then, recompile and run fourthCProgram to see if we now have all tests failing—which, with a stub for the larger function, is what we would hope for!
-bash-3.2$ make fourthCProgram
cc fourthCProgram.c -o fourthCProgram
-bash-3.2$ ./fourthCProgram
Test failed for Test case 1: expected -1234567.000000 got 7.000000:
Test failed for Test case 2: expected -1234567.000000 got 7.000000:
Test failed for Test case 3: expected -1234567.000000 got 3.000000:
Test failed for Test case 4: expected -1234567.000000 got 7.000000:
Test failed for Test case 5: expected -1234567.000000 got 0.000000:
Test failed for Test case 6: expected -1234567.000000 got 0.000000:
Test failed for Test case 7: expected -1234567.000000 got 5.000000:
Test failed for Test case 8: expected -1234567.000000 got 5.000000:
Test failed for Test case 9: expected -1234567.000000 got -3.000000:
Test failed for Test case 10: expected -1234567.000000 got -3.000000:
-bash-3.2$
Then, fix the stub, replacing it with real code so that the test cases pass—and remove all comments that have @@@ in them. When you are done this sequence of commands should produce this output:
-bash-3.2$ make fourthCProgramWhen you finish that, you are ready for step 7
cc fourthCProgram.c -o fourthCProgram
-bash-3.2$ ./fourthCProgram
Test passed for Test case 1
Test passed for Test case 2
Test passed for Test case 3
Test passed for Test case 4
Test passed for Test case 5
Test passed for Test case 6
Test passed for Test case 7
Test passed for Test case 8
Test passed for Test case 9
Test passed for Test case 10
-bash-3.2$ grep @@@ fourthCProgram.c
-bash-3.2$ head -1 fourthCProgram.c
// Chris J. Gaucho for CS16 lab02, 10/08/2009 -bash-3.2$
There is a very useful program on CSIL called Ch that you can use to learn about some of the basic properties of the C programming language.
Ch is an 'interpreter' for C—it allows you to type in C statements one at a time, and have those statements be immediately turned into something that can be executed by the computer, one at a time. This is really helpful for trying out small bits of code to see what they will do.
Ch has many other uses, as we may see later in the course—but for this lab, we'll primarily be using it for little experiments with floating point numbers—float and double—and the printf command.
To bring up Ch on CSIL, simply type ch at the command prompt.
You should see something like this (except the directory will be different)
-bash-3.2$ chThe very long prompt is an indication of what directory you are in. You may want to simply type cd at the prompt to go back to your home directory—this will give you more space to work:
Ch Professional edition, version 6.1.0.13751 (C) Copyright 2001-2009 SoftIntegration, Inc. http://www.softintegration.com /cs/faculty/pconrad/cs16/lab02>
/cs/faculty/pconrad/cs16/lab02> cdNow that we are at the Ch prompt, the first thing we might want to know is how to get out of it. The command to get out of Ch and back to the Unix prompt is exit. Try it now:
/cs/faculty/pconrad>
/cs/faculty/pconrad> exit
-bash-3.2$
That puts us back at the Unix prompt. For now, though lets go back into Ch:
-bash-3.2$ ch
Ch Professional edition, version 6.1.0.13751 (C) Copyright 2001-2009 SoftIntegration, Inc. http://www.softintegration.com /cs/faculty/pconrad/cs16/lab02> cd
/cs/faculty/pconrad>
Now, let's try a few things. First, let's note that we can declare variables, and print out their values. Try these commands:
/cs/faculty/pconrad> int a = 5;
/cs/faculty/pconrad> double x = 2.5;
/cs/faculty/pconrad> a
5
/cs/faculty/pconrad> x
2.5000
/cs/faculty/pconrad> a * x
12.5000
/cs/faculty/pconrad> a/x
2.0000
/cs/faculty/pconrad>
Now, notice that I can declare a variable with lots of precision, but when I print its value, I only see some of those digits:
/cs/faculty/pconrad> double d = 1.23456789
/cs/faculty/pconrad> d
1.2346
/cs/faculty/pconrad>
If I want to see all the digits, I can use a printf statement with a format specifier. As we discussed in lecture, and as you can read about in your textbook:
For example, try typing these at the ch prompt. Note that you can use the "up arrow" to bring back your previous commands and edit them—that will save you some typing:
/cs/faculty/pconrad> d
1.2346
/cs/faculty/pconrad> printf("%lf",d)
1.234568
/cs/faculty/pconrad> printf("%5.2lf",d)
1.23
/cs/faculty/pconrad> printf("%5.3lf",d)
1.235
/cs/faculty/pconrad> printf("%10.8lf",d)
1.23456789
/cs/faculty/pconrad> printf("%12.10lf",d)
1.2345678900
/cs/faculty/pconrad>
Try a few more and see if you can predict in advance what the output will be. You might be asked to do this on a future quiz or exam, so it is worth spending some time to practice your "prediction abilities" here. Note that in some cases, extra spaces are added at the start, and in others, rounding takes place, or the addition of extra zeros.
printf("%6.3lf",d)
printf("%6.2lf",d)
printf("%6.1lf",d)
printf("%6.0lf",d)
printf("%10.5lf",d)
printf("%10.4lf",d)
Once you are comfortable with that, there is another topic to explore—inaccuracy of floating point numbers.
Type in these C variable declarations at the Ch prompt:
/cs/faculty/pconrad> double y=1.0/3000.0
/cs/faculty/pconrad> double z=1.0/2999.0
/cs/faculty/pconrad>
Note that if we type the variable names, these variables seem to have the same value:
/cs/faculty/pconrad> y
0.0003
/cs/faculty/pconrad> z
0.0003
/cs/faculty/pconrad>
But, if we actually use a format specifier to print out more digits, we can see that they are quite different:
/cs/faculty/pconrad> printf("%12.10lf",y)
0.0003333333
/cs/faculty/pconrad> printf("%12.10lf",z)
0.0003334445
/cs/faculty/pconrad>
So, next, check this out:
/cs/faculty/pconrad> (3000.0 == 3000.0)
1
/cs/faculty/pconrad> (3000.0 == 2999.0)
0
/cs/faculty/pconrad>
What we see here is a reminder that C represents true and false with integers:
Now comes the potentially surprising part. We would expect that y * 3000.0 would give us 1.0, and that z * 2999.0 would give us 1.0. And, at first, it might seem that this is indeed the case:
/cs/faculty/pconrad> y * 3000.0
1.0000
/cs/faculty/pconrad> z * 2999.0
1.0000
/cs/faculty/pconrad>
But there is a subtle trap here!
So, if we type in ( y * 3000.0 == 1.0) we expect the answer 1—after all that's why y * 3000.0 is equal to, right?
So, we try it and we get 1 as we expect:
/cs/faculty/pconrad> (y * 3000.0 == 1.0)
1
/cs/faculty/pconrad>
But, when we try this with (z * 2999.0 == 1.0) get a different result:
/cs/faculty/pconrad> (z * 2999.0 == 1.0)
0
/cs/faculty/pconrad>
Ouch! What just happened?
A clue comes if we print out y * 3000.0 and z * 2999.0 with more precision. Compare:
/cs/faculty/pconrad> printf("%22.20lf",z * 2999.0)
0.99999999999999988898
/cs/faculty/pconrad> printf("%22.20lf",y * 3000.0)
1.00000000000000000000
/cs/faculty/pconrad>
Here's what's going on: a double value has many bits of precision—in Ch, we can see that it is 8 bytes, or 64 bits:
/cs/faculty/pconrad> sizeof(double)
8
/cs/faculty/pconrad>
But even that is not enough to store certain numbers in binary. We can understand this if we think about the problem of representing 1/3 exactly in a fixed number of decimal digits. No matter how many digits we use: 0.333, 0.33333, 0.33333333—if it is a finite number of digits, we cannot represent 1/3 exactly.
The same is true of representing certain numbers in binary—the reciprocal of 1/2999 happens to be one that can't be represented exactly in 64 bits. Thus, what we get is an approximation of the number—and when we multiply back by 2999.0, the error that was introduced comes back to bite us.
What is the way out of this? The way out is to never compare floating point numbers for equality.
Instead, we:
Here's an example, illustrated in Ch. The last line shows the proper way to check whether z * 2999 is approximately equal to 1.0—we subtract 1.0 from z * 2999, take the absolute value with fabs(), and then see if that is less than our tolerance value.
/cs/faculty/pconrad> double tol = 1E-9; // 10 to the minus 9
/cs/faculty/pconrad> z * 2999
1.0000
/cs/faculty/pconrad> (z * 2999 == 1.0) // the wrong way to test for equality
0
/cs/faculty/pconrad> ( fabs(z * 2999 - 1.0) < tol ) // the right way to text approximate equality
1
/cs/faculty/pconrad>
There is nothing to turn in for this step—but questions about it may appears on the first midterm, so if you are confused, ask your TA or instructor for additional help with understanding this step.
The idea from this step will also be used in the final program we are writing—though you can still work on this program even if you are hazy on the details of this step—so if that the case don't let it stop you from moving on to step 8 anyway. You can come back to step 7 later.
Now, take a look at fToCWithTests.c—bring it up in a text editor. Look at how this program operates. We find that it contains many similarities with thirdCProgram.c and fourthCProgram.c, but some differences as well. See if you can find all of these:
Go ahead and compile and run this program, and see that it produces this output—i.e. all test cases failing:
-bash-3.2$ make fToCWithTests
cc fToCWithTests.c -o fToCWithTests
-bash-3.2$ ./fToCWithTests
Test 1: fToC(32.0) failed: expected -1234.567890 got 0.000000 (tolerance=0.000000):
Test 2: fToC(212.0) failed: expected -1234.567890 got 100.000000 (tolerance=0.000000):
Test 3: fToC(68.0) failed: expected -1234.567890 got 20.000000 (tolerance=0.000000):
Test 4: fToC(-40.0) failed: expected -1234.567890 got -40.000000 (tolerance=0.000000):
4 tests failed
-bash-3.2$
Then, if you simply remove the stub line from fToC, it should start passing all its tests:
-bash-3.2$ make fToCWithTests
cc fToCWithTests.c -o fToCWithTests
-bash-3.2$ ./fToCWithTests
Test 1: fToC(32.0) passed
Test 2: fToC(212.0) passed
Test 3: fToC(68.0) passed
Test 4: fToC(-40.0) passed
All tests passed!
-bash-3.2$
That was the easy part though. The hard part of this step—though not too hard—is to write a C program called cToFWithTests.c that is exactly like fToCWithTests.c—it contains all the same elements, except that it tests a celsius to fahrenheit function instead of an fToC function.
The purpose of this exercise is to make sure that you really understand the ideas of test-driven development—that's why we are working with a very simple function, and one that you already coded last week.
Soon we'll be moving into more difficult and challenging assignments, and when we do, I want to make sure that the basics of working with C programs, and with the ideas of test-driven development are already very comfortable for you.
You can start by copying fToCWithTests.c to a new name, cToFWithTests.c, and editing it to add your header comment with your name, CS16 lab02, and the date:
cp fToCWithTests.c cToFWithTests.c
emacs cToFWithTests.c
Then, put the stub back, temporarily, change all the fToC reference to cToF references, and write some new test cases. You can probably just 'invert' the existing test cases—that is, switch the Fahrenheit and Celsius values—so it shouldn't be too hard.
Then, try to get your program to first compile, then run with all test cases failing, then run with all test cases passing.
When you've done that, you are almost finished with the lab—just the final checks, scripting, and the turn in step left!
In this step, we create a "transcript" of your work. We only do this after everything else is finished, and you are sure you have a good working product.
Here are the steps to prepare to make a transcript for lab02
Now, to create your script:
When finished, type ls one more time, and you should see a new file in your lab02 directory called lab02.txt.
Use this command to list out the contents of that file:
cat lab02.txt
You should see your life flashing before your eyes, so to speak—a feeling of déjà vu should come over you—because everything in the file will be what you just typed and what came back over the last few minutes.
If so, you are ready to submit!
To submit your assignment, you need to be in the ~/cs16 directory—one level higher than the previous step.
You can use the command cd .. to put yourself there, or cd ~/cs16
Do a pwd command to make sure that you did land in ~/cs16.
When you are in inside your cs16 directory, you are ready for the turnin step.
Type the following at the prompt:
turnin lab02@cs16 lab02
You may see a warning like this one about emacs backup files—those that end in ~
************** WARNINGS ************** lab02/thirdCProgram.c~: NOT TURNED IN: temporary file ...
*** Do you want to continue?
If so, don't worry—just type y and hit return to indicate you want to continue.
You'll be asked if you really want to turn in these files—the message will look just the ones you saw for lab00 and lab01.
You are about to turnin ___ files [___KB] for lab02 to cs16 *** Do you want to continue?
The main thing is to be sure that lab02.txt, thirdCProgram.c, fourthCProgram and cToFWithTests.c appear in the list. (If fToCWithTests.c appears, or the executables that go with any of these programs, that's fine.)
Go ahead and hit y and enter, and you should see a message like this one:
lab02/thirdCProgram.c
lab02/fourthCProgram.c
lab02/lab02.txt
... etc*** TURNIN OF lab02 TO cs16 COMPLETE! ***
Congratulations—you've completed your third lab in cs16!
From now on, I won't give as many detailed instructions about the turnin step—I'll assume that you have the process down, and just give a high-level description of what you need to turn in. I'll assume you are familiar with the details (or can look back at lab00 through lab02 to review whatever you may have forgotten.)
Due Date: You should try to complete this assignment by the end of end of your third discussion section, i.e. before 8:50am on Thursday 10/08/2009 or before 10:50am, 11:50am, or 12:50pm on Friday 10/09/2009 (depending on which section you are enrolled in.)
If you are unable to complete it by the end of your discussion section you may continue to work on it through the week. Please have it completed and turnin command finished by 10pm on Wednesday October 14.
Late assignments will only be accepted (with 20 point penalty) up until the time the TA doing the grading is finished with grading and posting a particular assignment. There is no specific guarantee as to the length of that period of time.
After that, a zero will be recorded, and the only option is to make up the points via extra credit.
Copyright 2009, Phillip T. Conrad, CS Dept, UC Santa Barbara. Permission to copy for non-commercial, non-profit, educational purposes granted, provided appropriate credit is given; all other rights reserved.