Critique my assignment please

W

warint

My lecturer gave us an assignment. He has a very "mature" way of
teaching in that he doesn't care whether people show up, whether they
do the assignments, or whether they copy other people's work.
Furthermore, he doesn't even mark the assignments, but rather gives
tips and so forth when going over students' work. To test students'
capabilities for the purpose of state exams and qualifications though,
he actually sits down with us at a computer and watches us write code.

We were issued with an assignment a yesterday. I have attempted the
assignment, and have re-produced the code below. I'd appreciate if
you'd give me advice, suggestions, etc. on the code. Again, I must
stress that you won't simply be "doing my homework for me".

Here's the assignment the lecturer gave us:

[begin quote]
Write a program to check the validity of a serial number found on a
Euro bank note. The program must be fully portable and compliant with
the C89 Standard. The program should exploit the standard library
where possible. The program should also be expected to perform
efficiently, both in terms of resource consumption and execution
speed, on a wide variety of platforms. The program should be well-
commented and easily understood where possible. Debug-mode facilities
such as "assert" should be exploited no matter how much redundancy
they add -- however the efficiency of the release-mode executable
should not be burdened by debug-mode features. The student must find
out for themselves how to check the validity of a Euro bank note
serial number. The program should not malfunction on account of bad
user input. Best of luck.
[end quote]

Before I begin, here's an excerpt from Wikipedia which discusses how
to check the validity of the serial number:

[begin excerpt]
Replace the initial letter by its position in the alphabet (that is L
is 12, M is 13,..., Z is 26).
Add up this number and every digit of the serial number. For example:
U08217383936 is 21 + 0 + 8 + 2 + 1 + 7 + 3 + 8 + 3 + 9 + 3 + 6 = 71
Add up all the digits of this new number, redo as many times as
necessary until you obtain a one-digit number. Alternatively computer
programmers may find the familiar MOD 9 function easier to implement.
This gives the same result.
The resulting number must be 8 - in the example above, 7 + 1 = 8 or 71
MOD 9 = 8, so it's correct.
[end excerpt]

Now here's my code. You're probably gonna kill me for this, but I
don't have a compiler to hand, so, even though I've checked over the
code, I can't say with 100% certainty that it will compile.

#include <assert.h> /* For assert */
#include <ctype.h> /* For stuff like isupper */
#include <stdlib.h> /* For EXIT_FAILURE */
#include <stdio.h> /* For puts and gets */


int const serial_len = 12; /* Serial number = one letter followed
by eleven digits */


/* Function: DigitFromChar

Converts '0' to 0, '5' to 5, '3' to 3, etc..

Exploits C89 feature that '0' through '9' must be consecutive.

Release Mode: UNSAFE because behaviour is undefined if input is
invalid
Debug Mode: SAFE because assertion fails if input is invalid

*/

unsigned DigitFromChar(char const x)
{
assert( x >= '0' && x <= '9' );

return x - '0';
}


/* Function: NumberFromUpperLetter

Converts 'A' to 1, 'B' to 2, 'C' to 3... and so on.

Uses a simple switch statement.

Release Mode: UNSAFE because behaviour is undefined if input is
invalid
Debug Mode: SAFE because assertion fails if input is invalid

*/

unsigned NumberFromUpperLetter(char const x)
{
assert(isupper(x));

switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26;
}
}


/* Function: IsValidEuroSerial

Returns false if serial is invalid, otherwise true.

Loops through the characters, summing with each iteration.

Release Mode: UNSAFE because behaviour is undefined if input is
invalid
Debug Mode: SAFE because assertion fails if input is invalid
*/

int IsValidEuroSerial(char const *p)
{
int const assert_dummy = assert(p);

char const *const pend = p + serial_len;

unsigned sum = NumberFromUpperLetter(*p++);

do
{
assert(isnum(*p));
sum += DigitFromChar(*p++);
} while (pend != p);

return 8 == sum%9;
}


int main(void)
{
char input[serial_len + 1],
*p = input + 1,
const *const pnull = input + (sizeof input / sizeof *input -
1);

puts("Enter Euro banknote serial number: ");

gets(input);

if (!isupper(*input)) goto Bad; /* Check first char is upper
letter */

do if (!isnum(*p++)) goto Bad; /* Check each char is a digit */
while (pnull != p);

puts(IsValidEuroSerial(input) ? "\n\nValid\n" : "\n\nINVALID\n");
return 0;

Bad:
puts("\n\nInvalid Input. Input must consist of an uppercase
letter followed by eleven digits only.\n");
return EXIT_FAILURE;
}

The shortcomings I can see so far are:

1) The char array is looped through twice, once in main and once in
"IsValidEuroSerial". I should consider writing an
"IsValidEuroSerial_Safe" function so that it can be reduced to one
loop.
2) I'm sure you will all jump down my throat about "gets", but please
just give suitable alternatives.

Thanks a lot for your time,

Martin
 
U

user923005

On Aug 23, 4:12 pm, (e-mail address removed) wrote:
[snip]
do if (!isnum(*p++)) goto Bad; /* Check each char is a digit */
[snip]

I guess you mean isdigit() instead of isnum()

P.S.
Of course we're going to roast you over gets().
In this context:
fgets(input, sizeof input, stdin);
would be perfectly fine and not have dire consequences.
 
C

cpedant

Your code fails to compile on my GCC with -ansi -pedantic.

On Aug 23, 6:12 pm, (e-mail address removed) wrote:
[snip]
unsigned NumberFromUpperLetter(char const x)
{
assert(isupper(x));

switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26;
}

}

A possibly minor point: there is no return statement executed in the
case that x is not one of 'A'-'Z'.

[snip]
int IsValidEuroSerial(char const *p)
{
int const assert_dummy = assert(p);

This is a syntax error since assert expands to a void expression.
char const *const pend = p + serial_len;

unsigned sum = NumberFromUpperLetter(*p++);

do
{
assert(isnum(*p));

isnum is not a function in standard C library. I think you're looking
for isdigit.
sum += DigitFromChar(*p++);
} while (pend != p);

return 8 == sum%9;

}

int main(void)
{
char input[serial_len + 1],

Variable-length arrays are not permitted in C89. Although you've
declared serial_len with the const qualifier, this does not make
"serial_len" a constant expression.
*p = input + 1,
const *const pnull = input + (sizeof input / sizeof *input -
1);

This is a syntax error. The leading "const" must be grouped directly
with "char". Here you have it following a comma in a list of
declarators, which is illegal.
puts("Enter Euro banknote serial number: ");

gets(input);

It is widely accepted that using fgets is much better than using gets.
if (!isupper(*input)) goto Bad; /* Check first char is upper
letter */

do if (!isnum(*p++)) goto Bad; /* Check each char is a digit */

Again, you probably want isdigit here.
while (pnull != p);

puts(IsValidEuroSerial(input) ? "\n\nValid\n" : "\n\nINVALID\n");
return 0;

Bad:
puts("\n\nInvalid Input. Input must consist of an uppercase
letter followed by eleven digits only.\n");
return EXIT_FAILURE;

}
[snip]
 
E

Eric Sosman

[...]
assert(isupper(x));

switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26;

Gyuuggh ...

A naive programmer who hasn't yet learned that all the
world isn't ASCII might write

if ('A' <= x && x <= 'Z')
return x - 'A' + 1;
else
vomit();

A slightly more sophisticated programmer might use a
look-up table. But a programmer who knows what's in his
library -- and assiduous use of the library was, I believe,
part of the assignment -- would write something more like

static const char alphabet[]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *px = strchr(alphabet, x);
if (px != NULL)
return px - alphabet + 1;
else
vomit();
 
U

user923005

[...]
assert(isupper(x));
switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26;

Gyuuggh ...

A naive programmer who hasn't yet learned that all the
world isn't ASCII might write

if ('A' <= x && x <= 'Z')
return x - 'A' + 1;
else
vomit();

A slightly more sophisticated programmer might use a
look-up table. But a programmer who knows what's in his
library -- and assiduous use of the library was, I believe,
part of the assignment -- would write something more like

static const char alphabet[]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *px = strchr(alphabet, x);
if (px != NULL)
return px - alphabet + 1;
else
vomit();

/* Since I wanted to do some math directly from the switch, I used a
switch anyway like this: */

#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#define VALID_SERIAL_NUMBER (0)
#define INVALID_LENGTH (-1)
#define INVALID_FORMAT (-2)
#define INVALID_CHECKSUM (-3)

static unsigned letter_to_sum(int letter, int *err)
{
int sum = 0;
assert(isalpha(letter));
switch (letter) {
case 'A':
sum = 2;
break;
case 'B':
sum = 3;
break;
case 'C':
sum = 4;
break;
case 'D':
sum = 5;
break;
case 'E':
sum = 6;
break;
case 'F':
sum = 7;
break;
case 'G':
sum = 8;
break;
case 'H':
sum = 9;
break;
case 'I':
sum = 1;
break;
case 'J':
sum = 2;
break;
case 'K':
sum = 3;
break;
case 'L':
sum = 4;
break;
case 'M':
sum = 5;
break;
case 'N':
sum = 6;
break;
case 'O':
sum = 7;
break;
case 'P':
sum = 8;
break;
case 'Q':
sum = 9;
break;
case 'R':
sum = 1;
break;
case 'S':
sum = 2;
break;
case 'T':
sum = 3;
break;
case 'U':
sum = 4;
break;
case 'V':
sum = 5;
break;
case 'W':
sum = 6;
break;
case 'X':
sum = 7;
break;
case 'Y':
sum = 8;
break;
case 'Z':
sum = 9;
break;
default:
*err = 1;
break;
}
return sum;
}

static unsigned digit_to_sum(int digit, int *err)
{
int sum = 0;
assert(isdigit(digit));
switch (digit) {
case '0':
sum = 0;
break;
case '1':
sum = 1;
break;
case '2':
sum = 2;
break;
case '3':
sum = 3;
break;
case '4':
sum = 4;
break;
case '5':
sum = 5;
break;
case '6':
sum = 6;
break;
case '7':
sum = 7;
break;
case '8':
sum = 8;
break;
case '9':
sum = 9;
break;
default:
*err = 1;
break;
}
return sum;
}


static int compute_checksum(const char *const serial)
{
unsigned sum = 0;
int err = 0;
sum += letter_to_sum(serial[0], &err);
sum += digit_to_sum(serial[1], &err);
sum += digit_to_sum(serial[2], &err);
sum += digit_to_sum(serial[3], &err);
sum += digit_to_sum(serial[4], &err);
sum += digit_to_sum(serial[5], &err);
sum += digit_to_sum(serial[6], &err);
sum += digit_to_sum(serial[7], &err);
sum += digit_to_sum(serial[8], &err);
sum += digit_to_sum(serial[9], &err);
sum += digit_to_sum(serial[10], &err);
sum += digit_to_sum(serial[11], &err);
if (err != 0)
return 1;
return sum % 9;
}

int validate_euro_serial(const char *const serial)
{
if (strlen(serial) != 12)
return INVALID_LENGTH;
if (!isalpha(serial[0]))
return INVALID_FORMAT;
if (!isdigit(serial[1]))
return INVALID_FORMAT;
if (!isdigit(serial[2]))
return INVALID_FORMAT;
if (!isdigit(serial[3]))
return INVALID_FORMAT;
if (!isdigit(serial[4]))
return INVALID_FORMAT;
if (!isdigit(serial[5]))
return INVALID_FORMAT;
if (!isdigit(serial[6]))
return INVALID_FORMAT;
if (!isdigit(serial[7]))
return INVALID_FORMAT;
if (!isdigit(serial[8]))
return INVALID_FORMAT;
if (!isdigit(serial[9]))
return INVALID_FORMAT;
if (!isdigit(serial[10]))
return INVALID_FORMAT;
if (!isdigit(serial[11]))
return INVALID_FORMAT;
if (compute_checksum(serial) == 0)
return VALID_SERIAL_NUMBER;
return INVALID_CHECKSUM;
}

#ifdef UNIT_TEST
#include <stdio.h>
#include <string.h>


const char *const valid_serials[] = {
"X01587275921",
"N67010337633",
"L09914094383",
"V10427872648",
NULL
};

const char *const invalid_serials[] = {
"A12345678901",
"D12345678901",
"E12345678901",
"Z12345678901",
NULL
};

const char *const stupid_serials[] = {
"Abz980769671",
"D178901",
"1aE12345678901",
"!@#$%^&*1",
NULL
};

/* Due to Jack Klein: */
char *getsafe(char *buffer, int count)
{
char *result = buffer,
*np;
assert(buffer);
if ((buffer == NULL) || (count < 1))
result = NULL;
else if (count == 1)
*result = '\0';
else if ((result = fgets(buffer, count, stdin)) != NULL)
if (np = strchr(buffer, '\n'))
*np = '\0';
return result;
}

void explain(int result, const char *string)
{
assert(string);
switch (result) {
case VALID_SERIAL_NUMBER:
printf("%s is a valid serial number\n", string);
break;
case INVALID_LENGTH:
printf("%s has an invalid length\n", string);
break;
case INVALID_FORMAT:
printf("%s has an invalid format\n", string);
break;
case INVALID_CHECKSUM:
printf("%s has an invalid checksum\n", string);
break;
default:
printf("I am an idiot, because I never imagined: %s\n",
string);
break;

}
}
char string[32767];
int main(void)
{
char *p;
size_t index;
int result;
for (index = 0; valid_serials[index]; index++) {
result = validate_euro_serial(valid_serials[index]);
explain(result, valid_serials[index]);
}
for (index = 0; invalid_serials[index]; index++) {
result = validate_euro_serial(invalid_serials[index]);
explain(result, invalid_serials[index]);
}
for (index = 0; stupid_serials[index]; index++) {
result = validate_euro_serial(stupid_serials[index]);
explain(result, stupid_serials[index]);
}

puts("Enter a serial number from a Euro note:");
p = getsafe(string, sizeof string);
if (p) {
result = validate_euro_serial(string);
explain(result, string);
}
return 0;
}
#endif
 
K

Keith Thompson

Eric Sosman said:
[...]
assert(isupper(x));
switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26;

Gyuuggh ...

A naive programmer who hasn't yet learned that all the
world isn't ASCII might write

if ('A' <= x && x <= 'Z')
return x - 'A' + 1;
else
vomit();

A slightly more sophisticated programmer might use a
look-up table. But a programmer who knows what's in his
library -- and assiduous use of the library was, I believe,
part of the assignment -- would write something more like

static const char alphabet[]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *px = strchr(alphabet, x);
if (px != NULL)
return px - alphabet + 1;
else
vomit();

Well, strchr does a linear search over the 'alphabet' string, whereas
the switch statement most likely uses a jump table for O(1)
performance. It's not a huge deal for only 26 letters, and worrying
about it almost seems like microoptimization, but it's something to
consider.
 
R

Richard Heathfield

(e-mail address removed) said:

[begin quote]
Write a program to check the validity of a serial number found on a
Euro bank note. The program must be fully portable and compliant with
the C89 Standard.

Okay, sounds like a job for comp.lang.c - although he says "fully
portable", he is unlikely to be quite as picky as we are, so you should
be okay.

<more "write it properly" stuff>

All reasonable.
The program should not malfunction on account of bad
user input. Best of luck.

Mark this well.
[end quote]

Before I begin, here's an excerpt from Wikipedia which discusses how
to check the validity of the serial number:

[begin excerpt]
Replace the initial letter by its position in the alphabet (that is L
is 12, M is 13,..., Z is 26).
Add up this number and every digit of the serial number. For example:
U08217383936 is 21 + 0 + 8 + 2 + 1 + 7 + 3 + 8 + 3 + 9 + 3 + 6 = 71

Okay, 21 % 9 is 3, + 0 + 8 is 11, % 9 is 2, + 2 + 1 is 5, + 7 is 12, % 9
is 3, + 3 is 6, + 8 is 14, % 9 is 5, + 3 is 8, + 9 % 9 is 8, + (3 + 6)
% 9 is still 8.
Now here's my code. You're probably gonna kill me for this, but I
don't have a compiler to hand, so, even though I've checked over the
code, I can't say with 100% certainty that it will compile.

Consider yourself dead, then. :)

After fixing your line wrap, but making no other changes, I get the
following output from a C89-conforming implementation:

foo.c:24: warning: no previous prototype for `DigitFromChar'
foo.c:44: warning: no previous prototype for `NumberFromUpperLetter'
foo.c: In function `NumberFromUpperLetter':
foo.c:59: warning: control reaches end of non-void function
foo.c: At top level:
foo.c:74: warning: no previous prototype for `IsValidEuroSerial'
foo.c: In function `IsValidEuroSerial':
foo.c:75: void value not ignored as it ought to be
foo.c:83: warning: implicit declaration of function `isnum'
foo.c:75: warning: unused variable `assert_dummy'
foo.c: In function `main':
foo.c:93: warning: ANSI C forbids variable-size array `input'
foo.c:93: size of array `input' is too large
foo.c:95: parse error before `const'
foo.c:106: `pnull' undeclared (first use in this function)
foo.c:106: (Each undeclared identifier is reported only once
foo.c:106: for each function it appears in.)
foo.c:93: warning: unused variable `input'
make: *** [foo.o] Error 1
The shortcomings I can see so far are:

....irrelevant until it compiles.
2) I'm sure you will all jump down my throat about "gets", but please
just give suitable alternatives.

fgets
 
O

Old Wolf

After fixing your line wrap, but making no other changes, I get the
following output from a C89-conforming implementation:

foo.c:24: warning: no previous prototype for `DigitFromChar'
[...]

No suffusion of yellow this time?
 
R

Richard Heathfield

Old Wolf said:
After fixing your line wrap, but making no other changes, I get the
following output from a C89-conforming implementation:

foo.c:24: warning: no previous prototype for `DigitFromChar'
[...]

No suffusion of yellow this time?

No, because it doesn't compile yet. Suffusions may follow in due course,
but right now they would be premature.
 
E

Eric Sosman

Keith said:
Eric Sosman said:
[...]
assert(isupper(x));
switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26;
Gyuuggh ...

A naive programmer who hasn't yet learned that all the
world isn't ASCII might write

if ('A' <= x && x <= 'Z')
return x - 'A' + 1;
else
vomit();

A slightly more sophisticated programmer might use a
look-up table. But a programmer who knows what's in his
library -- and assiduous use of the library was, I believe,
part of the assignment -- would write something more like

static const char alphabet[]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *px = strchr(alphabet, x);
if (px != NULL)
return px - alphabet + 1;
else
vomit();

Well, strchr does a linear search over the 'alphabet' string, whereas
the switch statement most likely uses a jump table for O(1)
performance.

Although the Standard says nothing about the algorithm
used by strchr() nor about its time complexity, I am confident
that most implementations exhibit O(1) time in the case shown.

Hint: Can you think of a simpler way to write O(27)?
It's not a huge deal for only 26 letters, and worrying
about it almost seems like microoptimization, but it's something to
consider.

At what time should you consider it?

Jackson's Laws Of Program Optimization:

First Law: Don't Do It.

Second Law (for experts only): Don't Do It Yet.

Which of the laws applies to you?
 
C

CBFalconer

On Aug 23, 6:12 pm, (e-mail address removed) wrote:
[snip]
unsigned NumberFromUpperLetter(char const x) {
assert(isupper(x));

switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26;
}
}

A possibly minor point: there is no return statement executed in
the case that x is not one of 'A'-'Z'.

Foully overlong and tricky to write code, IMO. Try:

unsigned NumberFromUpperLetter(char const x) {
static const char UC = " ABCDEFGHIJKLMNOPQRSTUVWXYZ";
int ch;
char *p;

ch = toupper((unsigned char)x);
if (p = strchr(UC, ch)) return p - &UC[0];
else return 0;
} /* untested */

Note the absence of any foul assert calls. All non-letters return
zero. By changing the function name and the string UC you can
search and convert any collection of chars. Works regardless of
the char set in effect.
 
T

Thad Smith

Here's the assignment the lecturer gave us:

[begin quote]
Write a program to check the validity of a serial number found on a
Euro bank note. The program must be fully portable and compliant with
the C89 Standard. The program should exploit the standard library
where possible. The program should also be expected to perform
efficiently, both in terms of resource consumption and execution
speed, on a wide variety of platforms. The program should be well-
commented and easily understood where possible. Debug-mode facilities
such as "assert" should be exploited no matter how much redundancy
they add -- however the efficiency of the release-mode executable
should not be burdened by debug-mode features. The student must find
out for themselves how to check the validity of a Euro bank note
serial number. The program should not malfunction on account of bad
user input. Best of luck.
[end quote]

First, my critique of the assignment (since you asked): I think it is a
well-defined assignment. I especially like the part that /you/ need to
determine how to check validity.

int const serial_len = 12; /* Serial number = one letter followed
by eleven digits */

This should be a #define constant in C. Your C++ is showing. ;-)
/* Function: DigitFromChar

Converts '0' to 0, '5' to 5, '3' to 3, etc..

Exploits C89 feature that '0' through '9' must be consecutive.

Release Mode: UNSAFE because behaviour is undefined if input is
invalid
Debug Mode: SAFE because assertion fails if input is invalid

*/

First, I like your function header. I personally like to specify the
domain of the inputs, so I would say that the input is a digit '0'
though '9', but you imply that.

I would quibble with your designation of an assertion exit as safe,
while returning an undefined value when called with an input outside the
specified domain as unsafe, but that's minor.

/* Function: NumberFromUpperLetter

Converts 'A' to 1, 'B' to 2, 'C' to 3... and so on.

Uses a simple switch statement.

Release Mode: UNSAFE because behaviour is undefined if input is
invalid
Debug Mode: SAFE because assertion fails if input is invalid

*/

unsigned NumberFromUpperLetter(char const x)
{
assert(isupper(x));

switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26;
}
}

Others has mentioned that no value is returned for a domain error. For
this function, I would probably return 0 for a non-uppercase character.
/* Function: IsValidEuroSerial

Returns false if serial is invalid, otherwise true.

Loops through the characters, summing with each iteration.

Release Mode: UNSAFE because behaviour is undefined if input is
invalid
Debug Mode: SAFE because assertion fails if input is invalid
*/

int IsValidEuroSerial(char const *p)
{
int const assert_dummy = assert(p);

char const *const pend = p + serial_len;

unsigned sum = NumberFromUpperLetter(*p++);

do
{
assert(isnum(*p));
sum += DigitFromChar(*p++);
} while (pend != p);

return 8 == sum%9;
}

My most important suggestion is with this function. According to the
description, it determines whether a specified string is a valid Euro
(bank note) serial number. It fails to determine whether the string is
in the proper format. I see that you test that elsewhere, but the
description of this function does not match the functionality. I think
your program would be much better organized and capable of being used
elsewhere if IsValidEuroSerial() did a full validity check.
> puts(IsValidEuroSerial(input) ? "\n\nValid\n" : "\n\nINVALID\n");
> return 0;
>
> Bad:
> puts("\n\nInvalid Input. Input must consist of an uppercase
> letter followed by eleven digits only.\n");
> return EXIT_FAILURE;

Here you have too types of responses for invalid serial numbers. One
says only INVALID and returns a value of 0, which the other returns with
a longer message and returns EXIT_FAILURE. Why are the two error
responses so different?

Your code also does not check for an input which is too long.
 
C

cpedant

Keith said:
Eric Sosman said:
(e-mail address removed) wrote:
[...]
assert(isupper(x));
switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26; [snip]
static const char alphabet[]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *px = strchr(alphabet, x);
if (px != NULL)
return px - alphabet + 1;
else
vomit();
Well, strchr does a linear search over the 'alphabet' string, whereas
the switch statement most likely uses a jump table for O(1)
performance.

Although the Standard says nothing about the algorithm
used by strchr() nor about its time complexity, I am confident
that most implementations exhibit O(1) time in the case shown.

Hint: Can you think of a simpler way to write O(27)?

Sure. Let A = O(27). A. :)

The string search is a nice solution in this case. However, I think an
important observation to make is that the niceness of it is dependent
on
the fact that we are mapping 'A'-'Z' onto {1, 2, ..., 26} (or some
other
nice set). In a hypothetical general case where we may need to map
'A'-'Z' onto nastier sets, the switch does the job easily while the
string search breaks down.

[snip]
 
A

Army1987

On Thu, 23 Aug 2007 22:53:24 -0400, Eric Sosman wrote:

[snip]
Although the Standard says nothing about the algorithm
used by strchr() nor about its time complexity, I am confident
that most implementations exhibit O(1) time in the case shown.

Hint: Can you think of a simpler way to write O(27)?
Yes. O(1). O(27) means bounded time. Any algorithm which has O(27)
runtime has also O(1) runtime, but with a constant 27 times
larger, and viceversa. You were thinking about O(n)?
 
A

Army1987

[...]
assert(isupper(x));
switch (x)
{
case 'A': return 1; case 'B': return 2; case 'C': return 3;
case 'D': return 4; case 'E': return 5; case 'F': return 6;
case 'G': return 7; case 'H': return 8; case 'I': return 9;
case 'J': return 10; case 'K': return 11; case 'L': return 12;
case 'M': return 13; case 'N': return 14; case 'O': return 15;
case 'P': return 16; case 'Q': return 17; case 'R': return 18;
case 'S': return 19; case 'T': return 20; case 'U': return 21;
case 'V': return 22; case 'W': return 23; case 'X': return 24;
case 'Y': return 25; case 'Z': return 26;

Gyuuggh ...

A naive programmer who hasn't yet learned that all the
world isn't ASCII might write

if ('A' <= x && x <= 'Z')
return x - 'A' + 1;
else
vomit();

A slightly more sophisticated programmer might use a
look-up table. But a programmer who knows what's in his
library -- and assiduous use of the library was, I believe,
part of the assignment -- would write something more like

static const char alphabet[]
= "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
const char *px = strchr(alphabet, x);
if (px != NULL)
return px - alphabet + 1;
else
vomit();

/* Since I wanted to do some math directly from the switch, I used a
switch anyway like this: */

#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>

#define VALID_SERIAL_NUMBER (0)
#define INVALID_LENGTH (-1)
#define INVALID_FORMAT (-2)
#define INVALID_CHECKSUM (-3)

static unsigned letter_to_sum(int letter, int *err)
{
int sum = 0;
assert(isalpha(letter));
switch (letter) {
case 'A':
sum = 2;
break;
case 'B':
sum = 3;
break; [snip]
case 'H':
sum = 9;
What would be wrong with sum = 0 here, then?
break; [snip]
default:
*err = 1;
break;
}
return sum;
}

static unsigned digit_to_sum(int digit, int *err)
{
int sum = 0;
assert(isdigit(digit));
switch (digit) {
Whoa. How is it better than sum = digit - '0'?
case '0':
sum = 0;
break;
case '1':
sum = 1;
break;
case '2':
sum = 2;
break;
case '3':
sum = 3;
break;
case '4':
sum = 4;
break;
case '5':
sum = 5;
break;
case '6':
sum = 6;
break;
case '7':
sum = 7;
break;
case '8':
sum = 8;
break;
case '9':
sum = 9;
break;
default:
*err = 1;
break;
}
return sum;
}


static int compute_checksum(const char *const serial)
{
unsigned sum = 0;
int err = 0;
sum += letter_to_sum(serial[0], &err);
sum += digit_to_sum(serial[1], &err);
sum += digit_to_sum(serial[2], &err);
sum += digit_to_sum(serial[3], &err);
sum += digit_to_sum(serial[4], &err);
sum += digit_to_sum(serial[5], &err);
sum += digit_to_sum(serial[6], &err);
sum += digit_to_sum(serial[7], &err);
sum += digit_to_sum(serial[8], &err);
sum += digit_to_sum(serial[9], &err);
sum += digit_to_sum(serial[10], &err);
sum += digit_to_sum(serial[11], &err);
I don't think the OP's teacher means *this* by "the code must be
efficient. How many nanoseconds it is going to save compared to a
loop if all the optimizations are turned off? How many compilers
won't turn a loop into this if they knew it'd be faster and the
optimizations are turned to a decent level?
if (err != 0)
return 1;
return sum % 9;
} [snip]
char string[32767];
"The program must be fully portable and compliant with
the C89 Standard. The program should exploit the standard library
where possible. The program should also be expected to perform
efficiently, both in terms of resource consumption and execution
speed, on a wide variety of platforms." IOW, 32 KB are much, much
more than needed.
Also, identifiers starting with str followed by a lowercase letter
are reserved.
 
A

Army1987

My lecturer gave us an assignment. He has a very "mature" way of
teaching in that he doesn't care whether people show up, whether they
do the assignments, or whether they copy other people's work.
Furthermore, he doesn't even mark the assignments, but rather gives
tips and so forth when going over students' work. To test students'
capabilities for the purpose of state exams and qualifications though,
he actually sits down with us at a computer and watches us write code.

We were issued with an assignment a yesterday. I have attempted the
assignment, and have re-produced the code below. I'd appreciate if
you'd give me advice, suggestions, etc. on the code. Again, I must
stress that you won't simply be "doing my homework for me".

Here's the assignment the lecturer gave us:

[begin quote]
Write a program to check the validity of a serial number found on a
Euro bank note. The program must be fully portable and compliant with
the C89 Standard. The program should exploit the standard library
where possible. The program should also be expected to perform
efficiently, both in terms of resource consumption and execution
speed, on a wide variety of platforms. The program should be well-
commented and easily understood where possible. Debug-mode facilities
such as "assert" should be exploited no matter how much redundancy
they add -- however the efficiency of the release-mode executable
should not be burdened by debug-mode features. The student must find
out for themselves how to check the validity of a Euro bank note
serial number. The program should not malfunction on account of bad
user input. Best of luck.
[end quote]

Before I begin, here's an excerpt from Wikipedia which discusses how
to check the validity of the serial number:

[begin excerpt]
Replace the initial letter by its position in the alphabet (that is L
is 12, M is 13,..., Z is 26).
Add up this number and every digit of the serial number. For example:
U08217383936 is 21 + 0 + 8 + 2 + 1 + 7 + 3 + 8 + 3 + 9 + 3 + 6 = 71
Add up all the digits of this new number, redo as many times as
necessary until you obtain a one-digit number. Alternatively computer
programmers may find the familiar MOD 9 function easier to implement.
This gives the same result.
The resulting number must be 8 - in the example above, 7 + 1 = 8 or 71
MOD 9 = 8, so it's correct.
[end excerpt]

Now here's my code. You're probably gonna kill me for this, but I
don't have a compiler to hand, so, even though I've checked over the
code, I can't say with 100% certainty that it will compile.
[snip]
The shortcomings I can see so far are:

1) The char array is looped through twice, once in main and once in
"IsValidEuroSerial". I should consider writing an
"IsValidEuroSerial_Safe" function so that it can be reduced to one
loop.
Write the function which returns false even if the format or the
length are wrong.
BTW, identifiers starting with "is" followed by a lowercase letter
are reserved, and identifiers with external linkage needn't be
case-sensitive or have more than 6 significant characters, so
IsValidEuroSerial is equivalent to isvali which is a reserved
identifier. Either declare the function as static, so that the
identifier will have internal linkage and will be required to be
case sensitive, or call it Is_ValidEuroSerial (or, as my religion
suggests, is_valid_euro_serial).
2) I'm sure you will all jump down my throat about "gets", but please
just give suitable alternatives.

Well, the assignment doesn't require to read from stdin, so you
could pass the serial as a command line argument. (Note that
implementations where command line is case insensitive will
pass it in lowercase, so ignore the case of the first letter.)
#include "stuff.h"
int main(int argc, char *argv[])
{
if (argv < 2) {
fprintf(stderr, "Usage:\t%s serial_no\n", argv[0]);
return EXIT_FAILURE;
} else {
argv[1][0] = toupper((unsigned char)argv[1][0]);
printf("%s %s a valid serial number for a euro "
"banknote.\n", argv[1],
is_valid_euro_serial(argv[1]) ? "is" : "is not");
}
return 0;
}
 
W

warint

Thanks everyone for your replies. I'll give it a second try:

#include <assert.h> /* For assert */
#include <ctype.h> /* For stuff like isupper */
#include <stdio.h> /* For puts and gets */
#include <string.h> /* For strchr */


#define SERIAL_LEN 12 /* Serial number = one letter followed by
eleven digits */


/* Function: DigitFromChar

Converts '0' to 0, '5' to 5, '3' to 3, etc..

Exploits C89 feature that '0' through '9' must be consecutive.

Release Mode: UNSAFE because behaviour is undefined if input is
invalid
Debug Mode: SAFE because assertion fails if input is invalid
*/


unsigned DigitFromChar(char const x)
{
assert( x >= '0' && x <= '9' );

return x - '0';
}


/* Function: NumberFromUpperLetter

Converts 'A' to 1, 'B' to 2, 'C' to 3... and so on.

Uses "ABCDEF..." and strchr.

Release Mode: UNSAFE because behaviour is undefined if input is
invalid
Debug Mode: SAFE because assertion fails if input is invalid
*/


unsigned NumberFromUpperLetter(char const x)
{
static char const letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";

assert(isupper(x));

return strchr(letters, x) - letters + 1;
}


/* Function: IsValidEuroSerial

Returns 1 if valid, 0 if invalid, or -1 if syntax error.

Loops through the characters, summing with each iteration.

Release Mode: UNSAFE because behaviour is undefined if pointer is
invalid
Debug Mode: SAFE because assertion fails if pointer is invalid
*/


int IsValidEuroSerial(char const *p)
{
int const dummy = ( assert(p), 0 );

char const *const pend = p + SERIAL_LEN;

unsigned sum;


if(!isupper(*p)) return -1;

sum = NumberFromUpperLetter(*p++);

do
{
if(!isdigit(*p)) return -1;
sum += DigitFromChar(*p++);
} while (pend != p);

if (*pend) return -1;

if (8 == sum%9) return 1;
else return 0;
}


int main(void)
{
char input[SERIAL_LEN + 1];

char const *output_string;


puts("Enter Euro banknote serial number: ");

gets(input);


switch (Is_ValidEuroSerial(input))
{
case -1: output_string = "\n\nInvalid Input. Input must consist "
"of an uppercase letter followed by "
"eleven digits only.\n";
break;

case 0: output_string = "\n\nINVALID\n";

break;

case 1: output_string = "\n\nValid\n";

break;
}

puts(output_string);

return 0;
}
 
R

Richard Heathfield

(e-mail address removed) said:
Thanks everyone for your replies. I'll give it a second try:

Getting better, but not quite there yet.

foo.c:24: warning: no previous prototype for `DigitFromChar'
foo.c:44: warning: no previous prototype for `NumberFromUpperLetter'
foo.c:66: warning: no previous prototype for `IsValidEuroSerial'
foo.c: In function `IsValidEuroSerial':
foo.c:67: warning: unused variable `dummy'
foo.c: In function `main':
foo.c:103: warning: implicit declaration of function
`Is_ValidEuroSerial'
foo.c:95: warning: `output_string' might be used uninitialized in this
function
foo.o: In function `main':
foo.c:100: the `gets' function is dangerous and should not be used.
foo.c:103: undefined reference to `Is_ValidEuroSerial'
collect2: ld returned 1 exit status
make: *** [foo] Error 1
 
E

Eric Sosman

Army1987 said:
On Thu, 23 Aug 2007 22:53:24 -0400, Eric Sosman wrote:

[snip]
Although the Standard says nothing about the algorithm
used by strchr() nor about its time complexity, I am confident
that most implementations exhibit O(1) time in the case shown.

Hint: Can you think of a simpler way to write O(27)?
Yes. O(1). O(27) means bounded time. Any algorithm which has O(27)
runtime has also O(1) runtime, but with a constant 27 times
larger, and viceversa. You were thinking about O(n)?

<topicality level="dubious">

O(27) means bounded time (if we're writing about time,
which in this case we are). So do O(1) and O(n) and O(n*n)
and O(exp(n)) and O(f(n)) for arbitrary f.

O(27), like O(f(n)), implies only that a proportionality
constant exists (approximately, in the limit as n increases)
and that the value is positive, but implies nothing at all
about the magnitude of the value. I repeat: Nothing at all.
Hence O(27) and O(1) mean exactly the same thing, just as
O(log N) and O(ln N) and O(lg N) mean exactly the same thing.

Reference (one of many):

http://en.wikipedia.org/wiki/Big_O_notation

The article begins with what seems to me an unnecessarily
circuitous introduction, but page down a ways and you'll
find a section titled "Formal definition" that might prove
helpful.

</topicality>
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,995
Messages
2,570,226
Members
46,815
Latest member
treekmostly22

Latest Threads

Top