Programmering C: Lösningar till tentamen 2013-08-22

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. En del av lösningarna kanske använder en modernare C-dialekt än vad Visual Studio klarar av.

Uppgift 1 (1 p)

a) 11

b) -1

c) -1

Uppgift 2 (3 p)

*a = 1, b = 2, c = 3
*a = 2, b = 3, c = 5

Uppgift 3 (3 p)

#include <stdio.h>

int main(void) {
    int a, b, c;
    printf("Skriv tre heltal. Avsluta med 0 0 0: \n");
    scanf("%d %d %d", &a, &b, &c);
    while (a != 0 || b != 0 || c != 0) {
        printf("Medelvärde: %f\n", (a + b + c) / 3.0);
        printf("Skriv tre heltal. Avsluta med 0 0 0: \n");
        scanf("%d %d %d", &a, &b, &c);
    }
    return 0;
}
Koden för inläsningen är med två gånger, så den här lösningen bryter mot DRY-principen ("Don't Repeat Yourself"). Vi kan försöka få bort den upprepningen:
#include <stdio.h>

int main(void) {
    int a, b, c;
    a = b = c = 1; // Så alla inte är 0
    while (a != 0 || b != 0 || c != 0) {
        printf("Skriv tre heltal. Avsluta med 0 0 0: \n");
        scanf("%d %d %d", &a, &b, &c);
        if (a != 0 || b != 0 || c != 0)
            printf("Medelvärde: %f\n", (a + b + c) / 3.0);
    }
    return 0;
}
Men där behövde vi i stället upprepa villkoret att minst ett tal måste vara skilt från noll. Vi kan slippa upprepningen genom att placera inläsning och kontroll i en egen funktion:
#include <stdio.h>

int las(int *ap, int* bp, int *cp) {
    printf("Skriv tre heltal. Avsluta med 0 0 0: \n");
    scanf("%d %d %d",ap, bp, cp);
    return *ap != 0 || *bp != 0 || *cp != 0;
}

int main(void) {
    int a, b, c;
    while (las(&a, &b, &c)) {
        printf("Medelvärde: %f\n", (a + b + c) / 3.0);
    }
    return 0;
}
Nu bryter vi inte mot DRY-principen. Men kanske bryter vi i stället mot KISS-principen ("Keep it simple, stupid")? Övning: Diskutera vilken lösning som är bäst!

Uppgift 4 (1 p)

struct Triangel t = { 3, 4, 3.5 };

Uppgift 5 (2 p)

void visa_triangel(struct Triangel *tp) {
    printf("Sida a = %f, b = %f, c = %f\n", tp->a, tp->b, tp->c);
}

Uppgift 6 (3 p)

void las_triangel(struct Triangel* tp) {
    printf("Ange sida a: ");
    scanf("%lf", &tp->a);
    printf("Ange sida b: ");
    scanf("%lf", &tp->b);
    printf("Ange sida c: ");
    scanf("%lf", &tp->c);
    // Notera att nu finns det troligen kvar ett radslutstecken i inmatningsbufferten
}

Uppgift 7 (5 p)

double min(struct Triangel t) {
    if (t.a <= t.b && t.a <= t.c)
        return t.a;
    else if (t.b <= t.c)
        return t.b;
    else
        return t.c;
}

double max(struct Triangel t) {
    if (t.a >= t.b && t.a >= t.c)
        return t.a;
    else if (t.b >= t.c)
        return t.b;
    else
        return t.c;
}

Eller, om man har tråkigt:

int compare_doubles(const void *p1, const void *p2) {
    double d1 = *((double*)p1);
    double d2 = *((double*)p2);
    if (d1 < d2)
        return -1;
    else if (d1 == d2)
        return 0;
    else /* d1 > d2 */
        return 1;
}

double *sort_sides(struct Triangel t) {
    static double a[3];
    a[0] = t.a;
    a[1] = t.b;
    a[2] = t.c;
    qsort(a, 3, sizeof(a[0]), compare_doubles);
    return a;
}

double min(struct Triangel t) {
    return sort_sides(t)[0];
}

double max(struct Triangel t) {
    return sort_sides(t)[2];
}

Uppgift 8 (2 p)

void skala(struct Triangel* tp, double faktor) {
    tp->a *= faktor;
    tp->b *= faktor;
    tp->c *= faktor;
}

Uppgift 9 (3 p)

double area(struct Triangel* tp) {
    double a = tp->a;
    double b = tp->b;
    double c = tp->c;
    return a/2 * sqrt( b*b - pow( (a*a + b*b - c*c) / (2*a), 2 ) );
}

Uppgift 10 (4 p)

int main(void) {
    struct Triangel triangeln;
    double skalfaktor;

    las_triangel(&triangeln);
    visa_triangel(&triangeln);
    printf("Arean = %f\n", area(&triangeln));
    printf("Ange en skalfaktor: ");
    scanf("%lf", &skalfaktor);
    skala(&triangeln, skalfaktor);
    visa_triangel(&triangeln);
    printf("Arean = %f\n", area(&triangeln));

    return 0;
}

Uppgift 11 (3 p)

double max_area(struct Triangel trianglar[], int antal_trianglar) {
    double max_area_so_far = area(&trianglar[0]);
    for (int i = 1; i < antal_trianglar; ++i) {
        double this_area = area(&trianglar[i]);
        if (this_area > max_area_so_far) {
            max_area_so_far = this_area;
        }
    }
    return max_area_so_far;
}

Uppgift 12 (3 p)

int main(void) {
    struct Triangel a[] = {
        { 3, 4, 5 },
        { 4, 5, 3 },
        { 5, 3, 4 },
        { 3, 4, 4 },
    };
    if (max_area(a, 4) != area(&a[2]))
        printf("*** FEL svar från funktionen max_aree!\n");

    return 0;
}
Tänk på att beräkningar med flyttal inte alltid ger exakta svar. Nu borde funktionen area ge samma svar för triangeln a[2] varje gång man anropar den, men egentligen kan man inte vara säker ens på det.

Uppgift 13 (3 p)

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

// ...

int main(void) {
    struct Triangel triangel;
    printf("Mata in en triangel.\n");
    las_triangel(&triangel);
    FILE *tsut = fopen("triangel.txt", "w");
    if (tsut == NULL) {
        fprintf(stderr, "Kunde inte skriva filen 'triangel.txt'.\n");
        exit(EXIT_FAILURE);
    }
    fprintf(tsut, "%f %f %f", triangel.a, triangel.b, triangel.c);
    fclose(tsut);
    printf("Nu är triangeln sparad på filen 'triangel.txt'.\n");

    return EXIT_SUCCESS;
}

En kommentar om felhantering: Egentligen kan både fprintf och fclose misslyckas, så i ett riktigt program bör man kontrollera returvärdet från dessa funktionsanrop.

Uppgift 14 (3 p)

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

// ...

int main(void) {
    struct Triangel triangel;
    FILE *tsin = fopen("triangel.txt", "r");
    fscanf(tsin, "%lf %lf %lf", &triangel.a, &triangel.b, &triangel.c);
    fclose(tsin);
    printf("Triangeln som är sparad på filen 'triangel.txt':\n");
    visa_triangel(&triangel);

    return EXIT_SUCCESS;
}

Uppgift 15 (1 p)

När jag provkör på min dator får jag den här utskriften:
1.200000
1.200000
Va? Inte lika!
Men utskriften kan också bli så här:
1.200000
1.200000
Lika, förstås.

Datorns flyttal lagras med binära siffor, även kallade bitar, men ett begränsat antal. Därför kan inte alla tal lagras exakt. Jämför med talet 1/3, som inte kan skrivas exakt med ändligt många vanliga decimala siffror (0-9). Det blir 0.3333333333... med treor i all oändlighet.

Om man vill förenkla lite kan man tänka sig att alla flyttal kan få ett litet, slumpmässigt fel mot slutet av decimalerna. (På riktigt är det inte slumpmässigt, och inte alla tal, men man kan tänka så.) 0.2 lagras kanske inte som exakt 0.2, utan som 0.200000000000000011102230246251565404236316680908203125. Därför måste man vara försiktig med exakta jämförelser av flyttal. 0.2 * 6 blir inte nödvändigtvis exakt lika med 1.2.

Prova själv att ändra utskrifterna i programmet så talen skrivs ut med 100 decimaler:

printf("%.100f\n", x * y);
printf("%.100f\n", z);

Tänk också på att även ett tal som går att skriva exakt med decimala siffror, som till exempel 0.2 och 1.2, inte nödvändigtvis kan lagras exakt med binära siffror.

Det gäller både float och double. double lagras med fler siffror än float, men det är fortfarande ändligt många.

Läs mer i David Goldbergs artikel What Every Computer Scientist Should Know About Floating-Point Arithmetic. Den finns till exempel här: http://docs.oracle.com/cd/E19957-01/806-3568/ncg_goldberg.html


Thomas Padron-McCarthy (thomas.padron-mccarthy@oru.se), 26 augusti 2013