structs and Fractions in C


Introduction: This page uses a the specific example of fractions (rational numbers) to introduce structs in C.


structs: A struct in C allows one to group any number of data types together as a unit, and handle them all at the same time as a unit. (In contrast arrays group together all data of the same type.) The parts of a struct can be any basic type, or any array, or another struct. Thus these objects can be arbitrarily complicated. Here is a simple example:

Here is a short program that makes use of this struct:

Program to illustrate a struct Output of Program
 1 #include <stdio.h> /* for input/output */
 2 struct student { /* no storage allocated */
 3    int num;    /* student number */
 4    double gpa; /* grade point average */
 5    char sex;   /* sex (M or F) */
 6 };
 7
 8 void printstudent(struct student s); /* prototype */
 9
10 int main() {
11    /* allocate storage and initialize it */
12    struct student joe = {243167, 3.4, 'M'};
13    struct student jim; /* just allocate storage */
14    struct student jeb; /* just allocate storage */
15    jim.num = 93745; /* initialize first field of jim */
16    jim.gpa = 2.9;   /* initialize second field of jim */
17    jim.sex = 'M';   /* initialize third field of jim */
18
19    jeb = jim; /* copy jim's fields into jeb's storage */
20    printstudent(joe);
21    printstudent(jim);
22    printstudent(jeb);
23 }
24
25 /* print the fields of an input struct student */
26 void printstudent(struct student s) {
27    printf("Student Number: %i\n", s.num);
28    printf("  Grade Point Ave: %0.2f\n", s.gpa);
29    printf("  Sex (M or F): %c\n\n", s.num);
30 }
Output:

Student Number: 243167
  Grade Point Ave: 3.40
  Sex (M or F): ß

Student Number: 93745
  Grade Point Ave: 2.90
  Sex (M or F): 1

Student Number: 93745
  Grade Point Ave: 2.90
  Sex (M or F): 1

The definition of the struct (its form or pattern) is given in green above. No storage is allocated by this code. A statement such as struct student suzy; actually allocates storage, which is not initialized. The program illustrates three ways to initialize the fields of a struct: with {} iniitalization (as with an array), with assignments for each field (which also works for an array), and with a single assignment (which does not work for an array.

When a struct is passed as a parameter, it is copied into the function (unlike an array, whose address is passed). When a struct is assigned or returned from a function, again it is copied. Normally in C, one uses pointers to structs, but we won't do that in this course because of the additional complexity.

What is presented here is only one of several ways to do structs in C. With this way, you always have to write struct student as if it were a type such as int or double. The keyword struct must always come before the name of the struct. Although the situation in C is more complicated that what we are describing here, what happens in C++ is much more complicated yet. The C struct is generalized to the C++ class, which is the foundation for C++.


A program to handle fractions (rational numbers): Here we are working with the following struct:

Notice that this struct has two integers as its fields, so this example could in theory be handled using an array of size 2. However, we are trying to teach about structs, and there are some other advantages of using structs.

First look at a stripped-down version of the function init that takes two integers, and builds a struct frac out of them, which it returns, for other functions to use.

Following this style, the function add, which adds two fractions and returns the sum, as a fraction, might look as follows:

However, it is more convenient to cleverly use the init function to help with the definition of add:

Then we have a function reduce that reduces a function to lowest terms, using the gcd (greatest common divisor), and returns the reduced fraction. This function can be placed at the end of init, so it will be called by all the other functions that use init.

The example below gives the example of a function expfrac that will use the series we had before to calcuate approximate values for exp(x), for a fraction x.

Program to Implement Fractions in C Rest of the Program
#include <stdio.h> /* for input/output */
struct frac { /* a fraction, numer/denom */
   int numer; /* numerator of fraction */
   int denom; /* denominator of fraction */
};
int gcd(int a, int b);
struct frac init(int a, int b);
struct frac add(struct frac x, struct frac y);
struct frac mul(struct frac x, struct frac y);
struct frac intmul(struct frac x, int r);
struct frac intdiv(struct frac x, int r);
struct frac sub(struct frac x, struct frac y);
struct frac inv(struct frac x);
struct frac divid(struct frac x, struct frac y);
double todouble(struct frac x);
int fraccomp(struct frac x, struct frac y);
struct frac expfrac(struct frac x, int n);
void writefrac(struct frac x);
struct frac readfrac();

int main() {
   int a, b;
   struct frac p = readfrac();
   struct frac q = expfrac(p,12);
}

/* greatest common divisor */
int gcd(int a, int b) {
   while (b != 0) {
      int temp = b;
      b = a%b;
      a = temp;
   }
   return a;
}

/* reduce z to lowest terms */
struct frac reduce(struct frac x) {
   struct frac z;
   int c = gcd(x.numer, x.denom);
   z.numer = x.numer/c;
   z.denom = x.denom/c;
   return z;
}

/* create fraction a/b, in lowest terms */
struct frac init(int a, int b) {
   struct frac z;
   if (b == 0) { /* can't have 0 denom */
      printf("WARNING! Zero denom!\n");
      b = 1;
   }
   if (b < 0) { /* put sign in the numerator */
      a = -a;
      b = -b;
   }
   z.numer = a;
   z.denom = b;
   z = reduce(z);
   return z;
}

/* convert fraction x to a double */
double todouble(struct frac x) {
   return (double)x.numer/(double)x.denom;
}

/* conpare fraction x to fraction y */
int fraccomp(struct frac x, struct frac y) {
   struct frac z = sub(x, y);
   if (z.numer == 0) return 0; /* x == y */
   else if (z.numer < 0) return -1; /* x < y */
   else return 1; /* x > y */
}
/* add fractions x and y, answer in l. terms */
struct frac add(struct frac x, struct frac y) {
   /* not doing lazy way here, lcm in denom */
   int c = gcd(x.denom, y.denom);
   return init(x.denom/c*y.numer +
      y.denom/c*x.numer,
      x.denom/c  * y.denom);
}

/* multiply fractions x and y, answer in l.t. */
struct frac mul(struct frac x, struct frac y) {
   return init(x.numer*y.numer, x.denom*y.denom);
}

/* multiply fraction x by int r, answer in l.t.*/
struct frac intmul(struct frac x, int r) {
   return init(r*x.numer, x.denom);
}

/* divide fraction x by int r, answer in l.t. */
struct frac intdiv(struct frac x, int r) {
   return init(x.numer, x.denom * r);
}

/* fraction x minus fraction y, answer in l.t. */
struct frac sub(struct frac x, struct frac y) {
   return add(x, intmul(y, -1));
}

/* reciprocal of fraction x, answer in l.t. */
struct frac inv(struct frac x) {
   return init(x.denom, x.numer);
}

/* divide fraction x by y, answer in l.t. */
struct frac divid(struct frac x, struct frac y) {
   return mul(x, inv(y));
}

/* write the fraction, not necessarily in l.t. */
void writefrac(struct frac x) {
   printf("%i/%i", x.numer, x.denom);
}

/* read two ints, create fraction, reduce */
struct frac readfrac() {
   int a, b;
   scanf("%i %i", &a, &b);
   return init(a, b);
}

/* for fraction x, calc. exp(x) as fraction */
struct frac expfrac(struct frac x, int n) {
   int i;
   struct frac coeff = init(1, 1);
   struct frac zpower = init(1, 1);
   struct frac term = init(1, 1);
   struct frac sum = init(1, 1);
   for (i = 1; i <= n; i++) {
      coeff = intdiv(coeff, i);
      zpower = mul(zpower, x);
      term = mul(coeff, zpower);
      sum = add(sum, term);
      printf("# %i\n  coeff: ", i);
      writefrac(coeff);
      printf("\n  term: ");
      writefrac(term);
      printf("\n  sum: ");
      writefrac(sum);
      printf("\n  double: %0.12f\n",
         todouble(sum));
   }
   return sum;
}


Four runs: These runs show successive approximations of exp(x) by fractions, using x = 1/1, x = 1/2, x = 2/3, and x = 3/4. If an individual integer in a calculation gets bigger than 231 - 1 = 2147483647, then overflow will occur, and the calculations are no good afterward. In each case below, overflow occurred in the next term, so this was as far as the computations could proceed with this software and with 32-bit integers.

First run with x == 1/1 Second run with x == 1/2 Third run with x == 2/3 Fourth run with x == 3/4
1  1
# 1
  coeff: 1/1
  term: 1/1
  sum: 2/1
  double: 2.000000000000
# 2
  coeff: 1/2
  term: 1/2
  sum: 5/2
  double: 2.500000000000
# 3
  coeff: 1/6
  term: 1/6
  sum: 8/3
  double: 2.666666666667
# 4
  coeff: 1/24
  term: 1/24
  sum: 65/24
  double: 2.708333333333
# 5
  coeff: 1/120
  term: 1/120
  sum: 163/60
  double: 2.716666666667
# 6
  coeff: 1/720
  term: 1/720
  sum: 1957/720
  double: 2.718055555556
# 7
  coeff: 1/5040
  term: 1/5040
  sum: 685/252
  double: 2.718253968254
# 8
  coeff: 1/40320
  term: 1/40320
  sum: 109601/40320
  double: 2.718278769841
# 9
  coeff: 1/362880
  term: 1/362880
  sum: 98641/36288
  double: 2.718281525573
# 10
  coeff: 1/3628800
  term: 1/3628800
  sum: 9864101/3628800
  double: 2.718281801146
# 11
  coeff: 1/39916800
  term: 1/39916800
  sum: 13563139/4989600
  double: 2.718281826198
# 12
  coeff: 1/479001600
  term: 1/479001600
 sum: 260412269/95800320
  double:2.718281828286
exp(1) = 2.7182818284590
1  2
# 1
  coeff: 1/1
  term: 1/2
  sum: 3/2
  double: 1.500000000000
# 2
  coeff: 1/2
  term: 1/8
  sum: 13/8
  double: 1.625000000000
# 3
  coeff: 1/6
  term: 1/48
  sum: 79/48
  double: 1.645833333333
# 4
  coeff: 1/24
  term: 1/384
  sum: 211/128
  double: 1.648437500000
# 5
  coeff: 1/120
  term: 1/3840
  sum: 6331/3840
  double: 1.648697916667
# 6
  coeff: 1/720
  term: 1/46080
  sum: 75973/46080
  double: 1.648719618056
# 7
  coeff: 1/5040
  term: 1/645120
  sum: 354541/215040
  double: 1.648721168155
# 8
  coeff: 1/40320
  term: 1/10321920
  sum: 17017969/10321920
  double: 1.648721265036
# 9
  coeff: 1/362880
  term: 1/185794560
sum: 306323443/185794560
  double:1.648721270418
exp(1/2)=1.6487212707001
2  3
# 1
  coeff: 1/1
  term: 2/3
  sum: 5/3
  double: 1.666666666667
# 2
  coeff: 1/2
  term: 2/9
  sum: 17/9
  double: 1.888888888889
# 3
  coeff: 1/6
  term: 4/81
  sum: 157/81
  double: 1.938271604938
# 4
  coeff: 1/24
  term: 2/243
  sum: 473/243
  double: 1.946502057613
# 5
  coeff: 1/120
  term: 4/3645
  sum: 7099/3645
  double: 1.947599451303
# 6
  coeff: 1/720
  term: 4/32805
  sum: 12779/6561
  double: 1.947721383935
# 7
  coeff: 1/5040
  term: 8/688905
  sum: 1341803/688905
  double: 1.947732996567
# 8
  coeff: 1/40320
  term: 2/2066715
  sum: 4025411/2066715
  double:1.947733964286
exp(2/3)=1.9477340410546
3  4
# 1
  coeff: 1/1
  term: 3/4
  sum: 7/4
  double: 1.750000000000
# 2
  coeff: 1/2
  term: 9/32
  sum: 65/32
  double: 2.031250000000
# 3
  coeff: 1/6
  term: 9/128
  sum: 269/128
  double: 2.101562500000
# 4
  coeff: 1/24
  term: 27/2048
  sum: 4331/2048
  double: 2.114746093750
# 5
  coeff: 1/120
  term: 81/40960
  sum: 86701/40960
  double: 2.116723632813
# 6
  coeff: 1/720
  term: 81/327680
  sum: 693689/327680
  double: 2.116970825195
# 7
  coeff: 1/5040
  term: 243/9175040
  sum: 3884707/1835008
  double:2.116997310093
exp(3/4)=2.1170000166126