Before you start, check your feedback for last week. Go to the place where you submitted your work and look for a link to feedback.html. You should also be able to confirm your mark on your general progress page (go to the department page on SAFE and follow the progress link)
The coursework this week will not count towards your credit for the unit. The main aims are for you to get acquainted with the university marking scale, and to practice writing a simple program. You may need information from the lecture notes up to the early parts of the statements chapter, before loops.
When you do a coursework assignment or an exam, you get a mark for it. The mark is a number from 0 to 100. It is often called a percentage, but it isn't a percentage of anything. The mark is not on a linear scale. The first few marks are easy to get, and higher marks are harder and harder to get. Roughly speaking, 50% represents an average for university-level work, and 100% represents the absolute theoretical limit of human achievement.
The marks from the various components of a unit are combined into a weighted average to form a unit mark. The marks for the units you take in a year are combined into an average, weighted by the number of credits, to form a year mark. The year marks you gain over the course of your degree are combined into an average weighted by contribution (no contribution from the first year, and the highest contribution coming from the final year). The final mark is converted into a grade, with various borderline rules. For more information about all this, see the aside on submission and marks.
Your task is to write a program in C called grade.c
which takes
in an integer percentage between 0 and 100 on the command line, representing the
final average mark for a degree, and prints out the corresponding grade. The
program prints First
for first class (a mark of 70 or more),
Upper
second
for upper second class (60 to 69),
Lower
second
for lower second class (50 to 59),
Third
for third class (40 to 49), Fail
(0 to 39), or
Invalid mark
if the input is not a valid mark. The program should
ignore borderline issues, and other outcomes (ordinary degree, certificate,
diploma, aegrotat, credit transfer).
To help you get started, a skeleton program and Makefile have been provided for you to download or copy-paste:
Rename Makefile.txt
back to Makefile
after
downloading, if necessary. Here are some features of the program for you to
check:
If you type make
on its own, this will compile
grade.c
to grade
(or grade.exe
)
If you have any other program, in program.c
for example, then
you can type make program
to compile it.
In the first line of a rule, %
is a pattern matching any program
name. In the other lines of a rule, $@
stands for the characters
matched by the pattern in the first line.
Try compiling it and running it like this:
$ clang -std=c11 -Wall grade.c -o grade $ ./grade 0 Invalid mark
or better like this:
$ make $ ./grade 0 Invalid mark
The options -std=c11 -Wall
are the minimum you should be using
when you compile, and then only if you are typing the compile command by
hand. Replace grade
by grade.exe
at the end if you
are using clang
on Windows.
Compile using the make
command to add more options. The extra
options help by providing more compile-time warnings, detecting run-time errors
sooner, and allowing you to use a debugger. This may not be important yet, but
it will be in later assignments.
The program works already, but not correctly. It prints out
Invalid mark
all the time. Your aim should be to improve it gradually.
It looks long, but that's mostly because of the functions which have been provided for you. You won't have to write much yourself.
The first part of the program is the part you are supposed to work on. The second part is user interface and testing functions which have been written for you, and which you should not change.
Try running it like this with no argument:
$ ./grade ... assertion fails on line ...
This triggers the testing, and this is how you should most often run the
program while working on it. The exact error message you get
depends on your compiler and programming environment, but it should include
somewhere the line number in the program of the test which fails.
The main
function
(which you don't have to understand fully) checks how many arguments you have
typed in. If none, it calls the test
function. If one, it calls
the convert
and grade
functions, and prints the
result.
There is no function longer than about a dozen lines, and you won't need to write a function longer than that either.
There are enumerated constant names G1
etc. for the grades.
These are integer codes. Nothing in the program should depend on exactly what
codes are used, and you shouldn't change them. To see what they mean,
look at the print
function which prints them out.
grade
and convert
functionsThe first few tests help you to develop the grade
function.
Then the remaining tests help you develop the convert
function.
The tests use
the assert
function which takes a boolean argument. If the
argument is
true
, it does nothing, but if the argument is false
, it
crashes with an error message which includes the line number of the failed
assertion. Other functions such as print
aren't tested, because it
is difficult to test functions that do input or output. You will have to trust
them.
You can probably write the grade
function in one piece. But the
convert
function may be too complex for that. It is strongly
suggested that you break it down into smaller functions. Suggestions might be to
write a function digit
which checks whether a character is a digit,
and a separate function to check validity, or perhaps functions
which separately handle the cases where the string is 1, 2 or 3 characters long.
Never stuff too much code into one function, making it too
big and unwieldy.
You can do the assignment without library functions or loops (though you can use either if you want to).
A mark string is passed to the convert
function as an array of
characters (avoiding pointer notation). The first argument n
is
the number of characters in the string. It has been added so that you don't
have to call any library functions if you don't want to. (Normally, strlen
would be
used.)
For example, if n
is 2
then the two
characters are mark[0]
and mark[1]
.
The const
keyword is included to help you, by making the
compiler give an error message if you incorrectly try to change the characters
in the string.
The tests for grade
only check the boundary points. It should
be obvious what the grade
function is expected to do on the
numbers in between.
If you look at the first three tests in testValidity
, you might
think you should check for decimal points, letters and negative numbers. But if
you read the comment, these are all just examples of input containing
non-digits. So, what you should be checking is that every character is a
digit.
The autotests have several purposes. They set a good example, provide a precise specification of some details of how the functions operate, provide some example snippets of code, and they allow automarking.
The number of tests your program passes before one fails, when you run it, tells you what mark you are going to get. Passing all the tests is worth 50%, and passing only some of them is proportionally less. Everybody can aim for perfection on this half of the assignment. (I will use my own copy of the tests, just in case you have changed them.)
You should follow some golden rules of software development:
Take small steps
That means: start with a working program. That's why you are given a working skeleton. Then write or change only a couple of lines before re-compiling and re-running the program. This is very efficient because you get the maximum amount of help from the computer and, if there is a problem, you know exactly where to look for it.
Write tiny functions
Divide a program into very small functions. The whole of a function should be visible at a glance in your editor, without scrolling. A function should be a bite-sized chunk of programming that you can tackle with confidence. The less confident you are, the smaller the functions should be.
Automate the testing
Don't repetitively and boringly keep trying out your program in different ways to see if it is working. Build the testing into the program itself, rerunning all the tests frequently to make sure you haven't broken anything. The assignments in this unit are designed to get you into the good habit of doing this for your whole career.
Your task is to change the grade
and convert
functions, and any supporting functions you define, until all the tests pass.
It is a good idea to concentrate on the first test that fails, get that to pass,
incidentally checking that all the previous tests still pass. Each extra test
that passes represents good progress.
To start with, you should get the grade
function to pass its
tests, one by one. You need to use some common sense. The tests say that
0
and 39
should give the answer Fail
. You
should make sure all the numbers in between also give the answer
Fail
but not by dealing with them individually one by
one.
Once those tests pass, start on the convert
function. For
validity, the number of characters in the array should be 1
or
2
or 3
. You can deal with each of the three cases
separately. Each character should be a digit, if there are two digits the first
should not be zero, and if there are three digits, they should be exactly
'1'
and '0'
and '0'
.
Here are some details that might help you:
The hardest parts are probably to check if a string is valid, and convert it into an integer.
To check validity of a string, you can first check the length. It should be 1, 2, or 3. For each of these lengths, you can check the individual characters. This is special-purpose rather than general-purpose validity checking, but it avoids loops and you don't need to call any library functions. You can convert the string to an integer in a similar way by dealing with the three cases separately.
Alternatively, you can use more general validity checking or conversion of arbitrary integer strings, and/or you can use standard functions to do some of the work.
The type for a character is char
. A character value is written
using single quotes, e.g. '5'
for the digit 5. A character is
really an integer code, e.g. 'A'
is just a synonym for
65
, but you shouldn't write functions which depend on knowing what
the codes are.
Suppose the user types in ./grade 56
. Then the argument is
first represented in the program as a piece of text "56"
. That is
really an array containing the digits '5'
and '6'
. The
digits are codes for the decimal symbols. They are not yet numbers. The code
for the digit '5'
is not the number
5
. You are not supposed to write functions based on knowing
what the code for '5'
is. However, you are
allowed to use the fact that the codes for the digits are next to each other.
So, if you have a char
variable ch
containing one of
the ten digits, you can write ch - '0'
to convert it into a
number.
The mark that the user types in is passed to the convert
function
in an array called mark
. This array is
a string, such
as "56"
. The number of characters in the string, e.g.
2
is passed in another argument n
. (The array is
actually one character longer {'5','6','\0'}
because a string
always has a terminating 'null' character at the end, so that the length can be
counted instead of being passed in, usually using the strlen
function.)
The digits can be extracted from the array using
mark[0]
, mark[1]
..., but you should be careful not to
access mark[i]
unless you know that that the length of the
array is at least i+1
, because that could access memory that
doesn't belong to the program, causing it to crash with a segfault.
The assignment is arranged so that you can do everything without calling any library functions, if you want. This can be a useful exercise, to make sure you understand all the details. But if you are comfortable that you could handle the details if you needed to, and you want to take a shortcut by calling a library function, that's fine.
The relevant headers (stdlib.h
, ctype.h
) have been
included in the skeleton program to make these functions available.
A call atoi(mark)
converts a valid mark string into an
integer. Type "C atoi" into Google to find documentation for the
atoi
("ascii to int") function. You will eventually need to
find out what it does in unusual cases.
A call isdigit(ch)
checks whether a character is a digit. It is
equivalent to '0' <= ch && ch <= '9'
.
You can temporarily add a call to the printf
function, e.g.
printf("n=%d\n",n);
or printf("m0=%c\n",mark[0]);
at
any point to check the values of variables. It is normal to do this when you
are unsure what's happening, but don't forget to delete or comment out these
calls before submitting.
This is the open ended part of the assignment, for the other 50% of the marks. Don't attempt it unless you have successfully completed the grade program, and you have time left. In general, you are not expected to spend more than 5 hours per week on assignments (partly in the lab session and partly on your own).
Write another C program of your choosing, of roughly the same scope as the
grade program. Also write a brief text file readme.txt
to say
what the program does, or how to use it, or to explain anything else you want
to about the program.
The ideal topic is something you come up with for yourself. These extras are an opportunity to build up a portfolio of little programs that interest you. If you are not feeling creative, try some random Google searches.
If you are still not feeling creative, possibilities of a similar kind (simple classification or calculation, no loops) are: obesity classification based on body mass index, checking validity of university usernames, checking whether a password is sufficiently strong, classification of characters as letters or digits or signs or punctuation or controls, temperature or other unit conversion, checking whether three numbers form a pythagorean triple, or playing a game of rock-paper-scissors against the computer.
It is also possible to continue development of the grade program (e.g. handling non-integer marks, printing out grade descriptions, forming weighted averages, converting to GPAs). However, you should do the extra work on a separate copy of the program with a different name, to avoid the risk of spoiling the automarking process on the original.
Another possibility is to start something more ambitious, submit it (as far as you get) this week, then develop it further and submit the improved version next week, when the assignment counts for credit. It is always fine to submit an ambitious but incomplete program, as long it works up to some point.
Submit your source file grade.c
. Also submit any extra program
you write, with any (other) filename, and a readme.txt
file with
any comments you want to make.
Don't submit Grade.c
or grade-v7.c
or
grade-final.c
, because it seriously hampers efficient auto-marking.
A mark of zero ought to be awarded but, to be generous, your program will be
manually renamed and 10% will be deducted from your mark. You have been
warned!
Note: on Windows, case-insensitive filename matching is used so that
Grade.c
works the same as grade.c
, but that isn't true
on other computers, e.g. the one used to mark your work.
Warning messages from the compiler seriously slow down the marking process. We can't penalize them very much, because they depend on the compiler and platform, but we may reduce your marks a little. To minimize the problem, please use the full set of compiler options, as in the provided Makefile.
A program that doesn't compile or doesn't work is worth nothing.
However, to be generous, if your program doesn't compile or doesn't work, a quick check will be made to see how serious it is, and reduced marks may be given.
If you added print statements for debugging, don't forget to delete them or comment them out before submitting, otherwise marks may be taken off.
Don't submit the compiled file grade
or grade.exe
because it will be ignored. We always want to see what you've written, and
because we recompile your program ourselves to test it.
If you improve your work before the deadline, feel free to re-submit, using the same filename. The new version will replace the old (but the old will be kept).
Automatic marking of the grade program will be used to form half of your
marks. That means you will get a clear pass if you do a good job of the grade
program, without doing anything extra. Automarking works by replacing your
main
and test
functions by a clean copy, and counting
the number of tests that pass before one fails. So it is possible to get
partial marks without finishing the last of the tests, provided that you keep
the program in a working state. Automarking can't check your programming
style, so ask for feedback on that.
The other half of your marks will come from an assessment of what your extra
work is worth (knowledge, skill, style, creativity, effort), based on a
very brief look. A small readme.txt
(or
readme.pdf
) file helps.