Programmering C: Lösningar till tentamen 2006-01-14

Observera att detta är förslag på lösningar. Det kan finnas andra lösningar som också är korrekta, och det kan hända att en del av lösningarna är mer omfattande än vad som krävs för full poäng på uppgiften. En del av lösningarna är kanske inte fullständiga, utan hänvisar bara till var man kan läsa svaret.

Uppgift 1 (1 p)

Alternativ 1:
z = x + y / sqrt(x*x + y*y);
Alternativ 2:
z = x + y / sqrt(pow(x, 2) + pow(y, 2));

Uppgift 2 (1 p)

a) 9

b) 2

Uppgift 3 (1 p)

struct spelare s = { "Kalle", 95.0, 18 };

Uppgift 4 (1 p)

-1

Uppgift 5 (1 p)

Alternativ 1:
talstrangen[0] = '0' + (int)talet;
talstrangen[1] = '.';
talstrangen[2] = '0' + (int)(10 * talet) % 10;
talstrangen[3] = '\0';
Alternativ 2:
talstrangen[0] = '0' + (int)talet;
talstrangen[1] = '.';
talstrangen[2] = '0' + (int)((talet - (int)talet ) * 10);
talstrangen[3] = '\0';

Överkurs:

I själva verket kommer båda dessa algoritmer på de flesta datorer att ge fel svar just för värdet 5.1! (Provkör själv och se!)

Det beror på att reella tal inte (brukar) representeras exakt i en dator, utan med datatypen float, som lagras som ett binärt tal. En tiondel (0.1) går inte att representera exakt med binära flyttal, på samma sätt som en tredjedel inte går att representera exakt med vanliga decimala tal.

Om man tänker sig att en del flyttalsberäkningar ibland ger ett litet, slumpmässigt fel, och försöker skriva sina program så att de fungerar i alla fall, så brukar det bli rätt. I det här fallet kan man göra en avrundning, kanske så här:

talstrangen[0] = '0' + (int)talet;
talstrangen[1] = '.';
talstrangen[2] = '0' + (int)(10 * (talet + 0.05)) % 10;
talstrangen[3] = '\0';

Uppgift 6 (2 p)

int dagnummer(int datum) {
  return datum % 100;
}

Uppgift 7 (2 p)

void visa_spelare(struct spelare b) {
  printf("Namn: %s\n", b.namn);
  printf("Vikt: %f\n", b.vikt);
  printf("Styrka: %d\n", b.styrka);
}

Uppgift 8 (2 p)

int is_ok_pnr_siffror(char *pnr) {
  for (int i = 0; i < 11; ++i)
    if (i != 6 && (pnr[i] < '0' || pnr[i] > '9'))
      return 0;
  return 1;
}
Om man inkluderar header-filen ctype.h, får man tillgång till makrot isdigit:
int is_ok_pnr_siffror(char *pnr) {
  for (int i = 0; i < 11; ++i)
    if (i != 6 && !isdigit(pnr[i]))
      return 0;
  return 1;
}
Ytterligare ett alternativ, som jag själv kanske skulle välja i verkligheten:
int is_ok_pnr_siffror(char *pnr) {
  return isdigit(pnr[0]) && isdigit(pnr[1]) && isdigit(pnr[2])
         && isdigit(pnr[3]) && isdigit(pnr[4]) && isdigit(pnr[5])
         && isdigit(pnr[7]) && isdigit(pnr[8]) && isdigit(pnr[9])
         && isdigit(pnr[10]);
}
Kommentar: Samtliga dessa lösningsförslag förutsätter att strängen är minst lika lång som ett riktigt personnummer. Strängar som är kortare kan orsaka felaktiga resultat eller programkrascher.

Uppgift 9 (2 p)

int is_ok_pnr_tecken(char *pnr) {
  return is_ok_pnr_siffror(pnr) && (pnr[6] == '-' || pnr[6] == '+');
}

Uppgift 10 (2 p)

Alternativ 1:
struct spelare las_spelare() {
  struct spelare s;

  printf("Ange spelarens namn: ");
  scanf("%s", s.namn);
  printf("Ange spelarens vikt: ");
  scanf("%f", &s.vikt);
  printf("Ange spelarens styrka: ");
  scanf("%d", &s.styrka);

  return s;
}
Alternativ 2:
void las_spelare(struct spelare* p) {
  printf("Ange spelarens namn: ");
  scanf("%s", p->namn);
  printf("Ange spelarens vikt: ");
  scanf("%f", &p->vikt);
  printf("Ange spelarens styrka: ");
  scanf("%d", &p->styrka);
}

Uppgift 11 (5 p)

#include <stdio.h>

int main() {
  char startbokstav;

  printf("Ge en stor bokstav: ");
  scanf("%c", &startbokstav);

  for (char bokstav = startbokstav; bokstav <= 'Z'; ++bokstav)
    printf("%c", bokstav);
  printf("\n");
  
  return 0;
}
Kommentarer:
Kursmaterialet använder void main, trots att C-standarden (i de flesta sammanhang) egentligen kräver int main. Jag använder int main i mina lösningar, men ger inga poängavdrag för void main.

Uppgift 12 (5 p)

#include <stdio.h>
#include <math.h>

int main() {
  float min, max, tal;

  printf("Ange minsta värdet: ");
  scanf("%f", &min);

  while (min >= 0) {
    printf("Ange största värdet: ");
    scanf("%f", &max);

    printf("Tal     Rot\n");
    printf("\n");

    tal = min;
    while (tal <= max) {
      printf("%-6.2f  %-6.2f\n", tal, sqrt(tal));
      tal += 0.1;
    }

    printf("Ange minsta värdet: ");
    scanf("%f", &min);
  }
  
  return 0;
}

Överkurs:

I själva verket kommer programmet ovan ibland att missa att skriva ut det sista värdet. (Provkör själv och se!)

Precis som problemen i uppgift 5 ovan, beror det på att reella tal inte (brukar) representeras exakt i en dator, utan med datatypen float, som lagras som ett binärt tal. En tiondel (0.1) går inte att representera exakt med binära flyttal, på samma sätt som en tredjedel inte går att representera exakt med vanliga decimala tal.

En regel är att man ska undvika likhetsjämförelser med flyttal. I programmet ovan ingår det en likhetsjämförelse i villkoret tal <= max, och den ger ibland ett oväntat resultat, just eftersom flyttal inte är en exakt korrekt representation av reella tal.

Hur ska man göra i stället? Jo, eftersom man ska undvika likhetsjämförelser med flyttal, bör vi använda ett heltal som styrvariabel i loopen. Heltal representeras alltid exakt, så där kan det aldrig bli några avrundningsfel.

#include <stdio.h>
#include <math.h>

int main() {
  float min, max, tal;
  int antal_steg;

  printf("Ange minsta värdet: ");
  scanf("%f", &min);

  while (min >= 0) {
    printf("Ange största värdet: ");
    scanf("%f", &max);

    printf("Tal     Rot\n");
    printf("\n");

    antal_steg = 10 * (max - min) + 1;
    // Varför "+ 1"? Jo:
    // Mellan 1 och 2 finns 10 intervall, men 11 utskrifter!
    tal = min;
    for (int i = 0; i < antal_steg; ++i) {
      printf("%-6.2f  %-6.2f\n", tal, sqrt(tal));
      tal += 0.1;
    }

    printf("Ange minsta värdet: ");
    scanf("%f", &min);
  }
  
  return 0;
}

Uppgift 13 (5 p)

#include <stdio.h>

struct spelare {
  char namn[10];  // Spelarens namn
  float vikt;     // Spelarens vikt
  int styrka;     // Spelarens styrka
};

// Stoppa in "las_spelare" och "visa_spelare" här

int main() {
  struct spelare s1, s2;

  s1 = las_spelare();
  s2 = las_spelare();

  if (s1.vikt < s2.vikt || (s1.vikt == s2.vikt && s1.styrka <= s2.styrka)) {
    visa_spelare(s1);
    visa_spelare(s2);
  }
  else {
    visa_spelare(s2);
    visa_spelare(s1);
  }
  
  return 0;
}
Om man gjort den andra varianten av las_spelare i uppgift 10, den som tar en pekare som argument, ska de två anropen till las_spelare i stället se ut så här:
  las_spelare(&s1);
  las_spelare(&s2);

Uppgift 14 (5 p)

#include <stdlib.h>
#include <stdio.h>

struct spelare {
  char namn[10];  // Spelarens namn
  float vikt;     // Spelarens vikt
  int styrka;     // Spelarens styrka
};

int main() {
  int antal1 = 0, antal2 = 0, antal3 = 0, antal4 = 0;
  struct spelare s;
  FILE* tsin;

  tsin = fopen("spelare.txt", "r");
  while (fscanf(tsin, "%s %f %d", s.namn, &s.vikt, &s.styrka) == 3) {
    if (s.vikt < 0) {
      printf("Otillåten vikt.\n");
      exit(EXIT_FAILURE);
    }
    else if (s.vikt < 40)
      ++antal1;
    else if (s.vikt < 80)
      ++antal2;
    else if (s.vikt < 120)
      ++antal3;
    else
      ++antal4;
  }
  fclose(tsin);

  printf("0.0 - 39.9: %d\n", antal1);
  printf("40.0 - 79.9: %d\n", antal2);
  printf("80.0 - 119.9: %d\n", antal3);
  printf("120.0 - : %d\n", antal4);
  
  return 0;
}

Uppgift 15 (5 p)

En korrekt version av programmet, med felen från uppgiften rödmarkerade:
#include <stdio.h>

int main(void) {
  int x, y, z, max;
  printf("Skriv tre heltal: ");
  scanf("%d", &x);
  scanf("%d", &y);
  scanf("%d", &z);
  if (x >= y && x > z)		/* Här räknar vi ut */
    max = x;                    /* vilket av de tre */
  else if (y > x && y > z)      /* talen x, y och z */
    max = y;                    /* som är störst, */
  else                          /* och lägger det i max. */
    max = z;
  printf("Största talet: %d\n", max);
  return 0;
}


Thomas Padron-McCarthy (Thomas.Padron-McCarthy@tech.oru.se) 11 januari 2007