Coursework: Week 2, Grade

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.

understand university marking

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.

read the specification

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).

study the skeleton program

To help you get started, a skeleton program and Makefile have been provided for you to download or copy-paste:

grade.c
Makefile

Rename Makefile.txt back to Makefile after downloading, if necessary. Here are some features of the program for you to check:

Improved Makefile

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.

It can be compiled and run

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 is in two parts

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.

It includes testing

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.

It has small functions

There is no function longer than about a dozen lines, and you won't need to write a function longer than that either.

It names the grades

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.

It tests the grade and convert functions

The 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 should define support functions

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 don't need library functions or loops

You can do the assignment without library functions or loops (though you can use either if you want to).

Strings are handled as arrays

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.

Common sense is needed

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.)

choose a strategy

You should follow some golden rules of software development:

Rule 1

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.

Rule 2

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.

Rule 3

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.

write the program

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:

design

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.

characters

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.

digits

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.

arrays

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.

library functions

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.

write something extra

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.

Come up with your own idea

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.

Here are some suggestions

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.

Extend the grade program

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.

Start something more ambitious

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

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.

Make sure the filename is correct

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.

Make sure the program compiles without warnings

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.

Make sure the program runs

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.

Make sure the program doesn't print unnecessarily

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.

Only submit source files

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.

You can improve your work

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.