Code Capsules



March 01, 1995
URL:http://drdobbs.com/code-capsules/184402992

March 1995/Code Capsules

Chuck Allison is a regular columnist with CUJ and a Senior Software Engineer in the Information and Communication Systems Department of the Church of Jesus Christ of Latter Day Saints in Salt Lake City. He has a B.S. and M.S. in mathematics, has been programming since 1975, and has been teaching and developing in C since 1984. His current interest is object-oriented technology and education. He is a member of X3J16, the ANSI C++ Standards Committee. Chuck can be reached on the Internet at 72640.1507@compuserve. com.

This month I conclude this series on the Standard C Library by exploring the headers in Group III (see Table 1, Table 2, and Table 3) .

Group III: For the "Complete" C Programmer

<float.h>

Perhaps nothing varies so widely across different computer environments as floating-point number systems. Some platforms require special hardware to make native floating-point instructions available. Others provide floating-point capability through software, and MS-DOS compilers must support both implementations.

A floating-point number system is a collection of numbers that can be expressed in scientific notation, with a fixed number of digits. To be precise, it is the finite set of numbers of the form ±0.d1d2...dp x be, where m < e < M. The parameters b, p, m, and M represent the radix, precision, minimum exponent, and maximum exponent, respectively. The header <float.h> provides manifest constants for these and other important floating-point parameters (see Table 4; also see the sidebar, "Floating-Point Number Systems"). As Table 4 illustrates, each implementation must support three potentially separate number systems, one each for float, double, and long double numbers.

<math.h>

Although C is not used widely in scientific programming, it provides a full-featured set of mathematical functions (see Table 6) . Most of these functions have the standard names used in mathematics, so if you know what they are, you know how to use them. A few deserve special mention, however. As the program in Listing 4 illustrates, the fmod function computes the remainder of dividing its first argument by its second. The answer to fmod(1234.56, 90.1234) is 62.9558 because

1234.56 - (13 * 90.1234) == 62.9558
The function modf stores the integer part of its first argument at the address given by its second argument, and returns the fractional part. frexp splits a floating-point number as given by its first argument into two parts, mantissa and base-2 exponent. In Listing 4, frexp computes a normalized fraction w and an integer p such that its first argument, y, is equivalent to

w x 2p
frexp's inverse, ldexp, returns a floating-point number given mantissa w and exponent p.

The floor of a number is itself if that number is an integer; otherwise the floor of a number is the next adjacent integer to the "left" on the number line, so the following relationships are valid:

    floor(90.1234) == 90
    floor(-90.1234) == -91
The ceiling of a number is the next integer to the right, so

    ceil(90.1234) == 91
    ceil(-90.1234) == -90
The calculator program in Listing 5 illustrates a number of <math.h> functions.

<errno.h>

<errno.h> implements a simple error reporting facility. It defines a global integer, errno, to hold certain error codes that are generated by a number of library functions. The C Standard requires vendors to provide only two codes, EDOM and ERANGE (see Table 7) . EDOM indicates a domain error, which usually means that you passed bad arguments to the function. For example, the sqrt function in <math. h> complains if you ask for the square root of a negative number. Math functions can set errno to ERANGE to indicate a range error, which mean that a calculation on valid arguments would result in an arithmetic overflow or underflow. Most of the mathematical functions in the standard library use errno to report such errors. As the calculator in Listing 5 illustrates, you should set errno to zero before invoking any function that uses this facility, and then check it immediately after the function returns. The function perror prints its string argument followed by a colon, followed by a string representation of the last error recorded in errno. perror(s) is equivalent to the expression:

    printf("%s: %s\n",s,strerror(errno));
strerror, defined in <string.h>, returns implementation-defined text that corresponds to errno.

The following functions from other standard library headers also set errno upon failure: strod, strtol, strtoul, fgetpos, fsetpos, and signal. The C Standard allows an implementation to provide error codes of its own, beyond EDOM and ERANGE, and to use the errno facility with other functions, but such use is of course non-portable.

<locale.h>

A locale in Standard C is a collection of preferences for the processing and display of information that is sensitive to culture, language, or national origin, such as date and monetary formats. The Standard recognizes five categories of locale-specific information, named by macros in <locale.h> (see Table 8) . Each of these categories can be set to a different locale (e.g., "american", or "italian"). For want of a better term, I call the collection of settings for all five categories the locale profile.

Standard C specifies two functions that deal with locales directly:

    struct lconv *localeconv(void);
    char *setlocale(int category, char *locale);
The members of the lconv structure appear in Listing 6. localeconv returns a static lconv object containing current settings for LC_MONETARY and LC_NUMERIC, and setlocale changes the locale for the given category to that specified in its second argument. You can set all categories to the given locale by specifying a category of LC_ALL (see Listing 7) . If locale is NULL, setlocale returns the current locale string for the category. All implementations must support the minimalist "C" locale, and a native locale named by the empty string (which may be the same as the "C" locale). Unfortunately, few U.S. vendors provide any additional locale support.

<setjmp.h>

When you encounter an exceptional condition deep within a set of nested function calls, you need a "super goto" that branches to a safe point higher up in the function call stack. That's what the setjmp/longjmp mechanism is for. You mark that safe return point with setjmp, and branch to it with longjmp. Here's the syntax:

   #include <setjmp.h>

   jmp_buf recover;

   main()
   {

      volatile int i = 0;
      for(;;)
      {
         if (setjmp(recover) != 0)
         {
            /* Recover from error in f() */
         }

         /* Get deeply nested... */
      }
      return 0;
   }

   . . .

   void f()
   {
      /* Do some risky stuff */

      if (<things go crazy>)
         longjmp(recover,1);

      /* else carry on */
   }
A jmp_buf (jump buffer) is an array that holds the system information necessary to restore execution at the setjmp point. For obvious reasons, a jump buffer must typically be global. When you call setjmp, the system stores the calling environment parameters, such as the contents of the stack and instruction pointer registers, in recover. setjmp always returns a zero when called directly. A call to longjmp restores the calling environment, so execution continues back at the setjmp call point, with one difference: it appears as if setjmp has returned the second argument from the longjmp call, in this case a 1. (One small quirk: if you give longjmp a second argument of zero, it returns a 1 anyway. Zero is the only argument with this behavior.) Since a longjmp performs an alternate return from a function, it interrupts the normal flow of a program, so you should use it only to handle unusual conditions.

When longjmp is called, the function containing the setjmp target must still be active (i.e., must not yet have returned to its own caller), otherwise the calling environment will no longer be valid. And of course, it is a bad idea to have more than one setjmp target with the same jmp_buf variable. Since calling environments typically involve registers, and since a compiler is free to store automatic variables in registers, you have no idea if an automatic object will have the correct value when you return from a longjmp. To get around this problem, you should declare automatics in any function containing a setjmp with the volatile qualifier, which guarantees that the inner workings of longjmp will leave them undisturbed. For a more detailed example of the setjmp/longjmp mechanism, see Listing 9 in "Code Capsules, File Processing, Part 2," CUJ, June 1993.

<signal.h>

A signal occurs when an unusual event interrupts the normal execution of a program, such as a divide-by-zero error or when the user presses an attention key, such as Control-C or DEL. The header <signal. h> defines six "standard signals," shown in Table 9. These signals originated on the PDP architecture under UNIX, so some of them may not apply to your environment. An implementation may also define other signals, or may ignore signals altogether, so signal handling is inherently non-portable.

Signals come in two flavors: synchronous and asynchronous. A synchronous signal is one that your program raises, such as dividing by zero, overflowing during a floating-point operation, or issuing a call to the abort function. You can also raise a signal explicitly with a call to raise, for example:

    raise(SIGABRT);
An asynchronous signal occurs as a result of events that your program can't foresee, such as a user pressing the attention key.

The default response to most signals is usually to abort the program, but you can either arrange for signals to be ignored or provide your own custom signal handlers. The following statement from Listing 8

    signal(SIGINT,SIG_IGN);
tells the environment to ignore any keyboard interrupt requests (Control-C on my machine). The keyboard input process still echoes the ^C token on the display, but it does not pass control to the default signal-handling logic that terminates the program. The statement

    signal(SIGINT,SIG_DFL)
restores the default behavior, so you can halt the program from the keyboard.

For more on signal handling, see Listing 11 - Listing 13 in the Code Capsule "Control Structures," in the June 1994 issue of CUJ.

<stdarg.h>

This header provides a facility for defining functions with variable-length argument lists, like printf's (see Table 10) . printf's function prototype in your compiler's stdio.h should look something like

    int printf(const char *, ...);
The ellipsis tells the compiler to allow zero or more arguments of any type to follow the first argument in a call to printf. For printf to behave correctly, the arguments that follow the format string must match the types of the corresponding edit descriptors in the format string. If there are fewer arguments than the format string expects, the result is undefined. If there are more arguments, they are ignored. The bottom line is that when you use the ellipsis in a function prototype you are telling the compiler not to type-check your optional arguments because you think you know what you're doing — so be sure that you do.

The program in Listing 9 shows how to use the va_list mechanism to find the largest integer in a variable-length argument list. For more on <stdarg.h>, see the Code Capsule, "Variable-length Argument Lists" in the Feb. 1994 issue of CUJ.

Conclusion

In this three-part series I have attempted to convey the overall flavor of the Standard C library, especially in the functionality it provides. It is foolish to waste time reinventing this functionality. Although I have classified the library into three groups of decreasing priority, my priorities may not match yours. If you make your living programming in C or C++, you will do well to master the entire library. Enough said.

March 1995/Code Capsules/Listing 1

Listing 1 Shows roundoff error in computing powers of e

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

double e(double x);

main()
{
   printf("e(55.5) == %g, exp(55.5) == %g\n",
         e(55.5), exp(55.5));
   printf("e(-55.5) == %g, exp(-55.5) == %g\n",
         e(-55.5), exp(-55.5));
   printf("1/e(55.5) == %g\n",1.0 / e(55.5));
   return 0;
}

double e(double x)
{
   double sum1 = 1.0;
   double sum2 = 1.0 + x;
   double term = x;
   int i = 1;

   /* Calculate exp(x) via Taylor Series */
   while (sum1 != sum2)
   {
      sum1 = sum2;
      term = term * x / ++i;
      sum2 += term;
   }
   return sum2;
}

/* Output:
e(55.5) == 1.26866e+24, exp(55.5) == 1.26866e+24
e(-55.5) == -6.76351e+06, exp(-55.5) == 7.88236e-25
1/e(55.5) == 7.88236e-25
*/

/* End of File */
March 1995/Code Capsules/Listing 2

Listing 2 Illustrates use of machine epsilon in a root-finding algorithm

/* root.c:
 *
 *   To use a different precision, change ftype
 *   and the suffix of the floating-point constants
 */

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

#define sign(x) ((x < 0.0) ? -1 : (x > 0.0) ? 1 : 0)

#define PREC DBL_DIG
#define EPS DBL_EPSILON

typedef double ftype;

ftype root(ftype a, ftype b, ftype (*f)(ftype))
{

   ftype fofa = f(a);
   ftype fofb = f(b);
   assert(a < b);
   assert(fofa * fofb < 0.0);

   /* Close-in on root via bisection */
   while (fabs(b - a) > EPS*fabs(a))
   {
      ftype x = a + (b-a)/2.0;
      ftype fofx = f(x);
      if (x <= a || x >= b || fofx == 0.0)
         return x;
      if (sign(fofx) == sign(fofa))
      {
         a = x;
         fofa = fofx;
      }
      else
      {
         b = x;
         fofb = fofx;
      }
   }
   return a;
}

main()
{
   extern ftype f(ftype);
   printf("root == %.*f\n",PREC,root(-1.0,1.0,f));
   return 0;
}

ftype f(ftype x)
{
   return x*x + x - 1.0;
}

/* Output:
root == 0.618033988749895
*/
/* End of File */
March 1995/Code Capsules/Listing 3

Listing 3 Computes Machine Floating-point Parameters

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

main()
{
   int beta, p;
   double a, b, eps, epsp1, sigma, nums;

   /* Discover radix */
   a = 1.0;
   do
   {
      a = 2.0 * a;
      b = a + 1.0;
   } while ((b - a) == 1.0);

   b = 1.0;
   do
      b = 2.0 * b;
   while ((a + b) == a);
   beta = (int) ((a + b) - a);
   printf("radix:\n");
   printf("\talgorithm: %d\n",beta);
   printf("\tprovided: %d\n",FLT_RADIX);

   /* Compute precision in bits */
   p = 0;
   a = 1.0;
   do
   {
      ++p;
      a *= (double) beta;
      b = a + 1.0;
   } while ((b - a) == 1.0);
   printf("precision:\n");
   printf("\talgorithm: %d\n",p);
   printf("\tprovided: %d\n",DBL_MANT_DIG);

   /* Compute machine epsilon */
   eps = 1.0;
   do
   {
      eps = 0.5 * eps;
      epsp1 = eps + 1.0;
   } while (epsp1 > 1.0);
   epsp1 = 2.0 * eps + 1.0;
   eps = epsp1 - 1.0;
   printf("machine epsilon:\n");
   printf("\talgorithm: %g\n",eps);
   printf("\tformula: %g\n",pow(FLT_RADIX,1.0-DBL_MANT_DIG));
   printf("\tprovided: %g\n",DBL_EPSILON);

   /* Compute smallest normalized magnitude */
   printf("smallest non-zero magnitude:\n");
   printf("\tformula: %g\n",pow(FLT_RADIX,DBL_MIN_EXP-1));
   printf("\tprovided: %g\n",DBL_MIN);

   /* Compute larget normalized magnitude */
   printf("largest non-zero magnitude:\n");
   printf("\tformula: %g\n",
         pow(FLT_RADIX,DBL_MAX_EXP-1) * FLT_RADIX *
         (1.0 - pow(FLT_RADIX,-DBL_MANT_DIG)));
   printf("\tprovided: %g\n",DBL_MAX);

   printf("smallest exponent: %d\n",DBL_MIN_EXP);
   printf("largest exponent: %d\n",DBL_MAX_EXP);

   nums = 2 * (FLT_RADIX - 1)
       * pow(FLT_RADIX,DBL_MANT_DIG-1)
       * (DBL_MAX_EXP - DBL_MIN_EXP + 1);
   printf("This system has %g numbers\n",nums);
   return 0;
}

/* Output (Borland C++):
radix:
   algorithm: 2
   provided: 2
precision:
   algorithm: 53
   provided: 53
machine epsilon:
   algorithm: 2.22045e-16
   formula: 2.22045e-16
   provided: 2.22045e-16
smallest non-zero magnitude:
   formula: 2.22507e-308
   provided: 2.22507e-308
largest non-zero magnitude:
   formula: 1.79769e+308
   provided: 1.79769e+308
smallest exponent: -1021
largest exponent: 1024
This system has 1.84287e+19 numbers
*/
/* End of File */
March 1995/Code Capsules/Listing 4

Listing 4 Illustrates several <math.h> functions

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

main()
{
   double x = 1234.56, y = 90.1234, z, w;
   int p;

   printf("x == %g, y == %g\n",x,y);
   printf("fmod(x,y) == %g\n",fmod(x,y));
   printf("floor(y) == %g\n",floor(y));
   printf("ceil(y) == %g\n",ceil(y));
   w = modf(y,&z);
   printf("after modf(y,&z): w == %g, z == %g\n",w,z);
   w = frexp(y,&p);
   printf("after frexp(y,&p): w == %g, p == %d\n",w,p);
   printf("ldexp(w,p) == %g\n",ldexp(w,p));
   return 0;
}

/* Output:
x == 1234.56, y == 90.1234
fmod(x,y) == 62.9558
floor(y) == 90
ceil(y) == 91
after modf(y,&z): w == 0.1234, z == 90
after frexp(y,&p): w == 0.704089, p == 7
ldexp(w,p) == 90.1234
*/
/* End of File */
March 1995/Code Capsules/Listing 5

Listing 5 A simple calculator that illustrates some <math.h> functions

/* calc.c: Lame-brain Calculator-
 *
 *  For simplicity in parsing, this program
 *  reads lines of the form:
 *
 *     value operation
 *
 *  where the value is optional in some cases.
 *  For example, the following script computes
 *  the integer part of sqrt(1 + 3.4*3.4):
 *
 *      3.4 =
 *      3.4 *
 *      1 +
 *      sqrt
 *      floor
 */

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#define LINSIZ 40

char *getline(char *);

main()
{
   double  reg = 0.0;
   char    line[LINSIZ];
   while (getline(line) != NULL)
   {
      char *op;
      double val;

      /* Parse command string */
      val = strtod(line,&op);
      while (isspace(*op))
         ++op;
      strupr(op);

      /* Perform operation */
      errno = 0;
      if (*op == '+')
         reg += val;
      else if (*op == '-')
         reg -= val;
      else if (*op == '*')
         reg *= val;
      else if (*op == '/')
       {
         if (val != 0)
            reg /= val;
         else
         {
            puts("ERROR>>> invalid divisor");
            continue;
         }
      }
      else if (*op == '=')
         reg = val;
      else if (*op == '^')
      {
         if (val == 0.0)
            reg = 1.0;
         else if (val == 0.5)
            reg = sqrt(reg);
         else
            reg = pow(reg,val);
      }
      else if (strncmp(op,"NEGATE",1) == 0)
         reg = -reg;
      else if (strncmp(op,"MOD",1) == 0)
      {
         if (val == 0.0)
         {
            puts("ERROR>>> invalid modulus");
            continue;
         }
         else
            reg = fmod(reg,val);
      }
      else if (strncmp(op,"CEIL",1) == 0)
         reg = ceil(reg);
      else if (strncmp(op,"FLOOR",1) == 0)
         reg = floor(reg);
      else if (strncmp(op,"ROUND",1) == 0)
         reg = (reg < 0.0) ? ceil(reg - 0.5)
                        : floor(reg + 0.5);
      else if (strncmp(op,"SQRT",1) == 0)
         reg = sqrt(reg);
      else if (strncmp(op,"QUIT",1) == 0)
         exit(0);
      else if (*op != '\0')
      {
         puts("ERROR>>> invalid operation");
         continue;
      }

      if (errno)
         perror("ERROR>>>");
      else
         printf("\t%s => %g\n",line,reg);
   }
   return 0;
}

char *getline(char *buf)
{
   fputs ("Calc> ",stdout);
   fflush(stdout);
   return gets(buf);
}

/* Output:
Calc> 3.4 =
       3.4 = => 3.4
Calc> 3.4 *
       3.4 * => 11.56
Calc> 1 +
       1+ => 12.56
Calc> sqrt
       SQRT => 3.54401
Calc> floor
       FLOOR => 3
Calc> q
*/

/* End of file */
March 1995/Code Capsules/Listing 6

Listing 6 The members of struct lconv

struct lconv
{
   char *decimal_point;
   char *thousands_sep;
   char *grouping;
   char *int_curr_symbol;
   char *currency_symbol;
   char *mon_decimal_point;
   char *mon_thousands_sep;
   char *mon_grouping;
   char *positive_sign;
   char *negative_sign;
   char int_frac_digits;
   char frac_digits;
   char p_cs_precedes;
   char p_sep_by_space;
   char n_cs_precedes;
   char n_sep_by_space;
   char p_sign_posn;
   char n_sign_posn;
};
March 1995/Code Capsules/Listing 7

Listing 7 Shows the effect of locale settings on the decimal point character and time formatting

/*  tlocale.c: Illustrates locales-
 *
 *      Compiled in Visual C++ under Windows NT 3.5
 */

#include <locale.h>
#include <stdio.h>
#include <time.h>

void print_stuff(void);

main()
{
   /* First in the C locale */
   puts("In the C locale:");
   print_stuff();

   /* Now try German */
   puts("\nIn the German locale:");
   setlocale(LC_ALL,"german");
   print_stuff();

   /* Now try American */
   puts("\nIn the American locale:");
   setlocale(LC_ALL,"american");
   print_stuff();

   /* Now try Italian */
   puts("\nIn the Italian locale:");
   setlocale(LC_ALL,"italian");
   print_stuff();
   return 0;
}

void print_stuff(void)
{
   char text[81];
   time_t timer = time(NULL);
   struct lconv *p = localeconv();

   printf("decimal pt. == %s\n",
         p->decimal_point);
   printf("currency symbol == %s\n",
         p->int_curr_symbol);
   printf("%.2f\n",l.2);
   strftime(text,sizeof text,"%A, %B, %d,
           %Y (%x)\n", localtime(&timer));
   puts(text);
}

In the C locale:
decimal pt. == ,
currency symbol ==
1.20
Tuesday, January 03, 1995 (01/03/95)


In the German locale:
decimal pt. == .
currency symbol == DEM
1,20
Dienstag, Januar 03, 1995 (03.01.95)


In the American locale:
decimal pt. == .
currency symbol == USD
1.20
Tuesday, January 03, 1995 (01/03/95)


In the Italian locale:
decimal pt. == ,
currency symbol == ITL
1,20
marted, gennaio 03, 1995 (03/01/95)
/* End of File */
March 1995/Code Capsules/Listing 8

Listing 8 Shows how to register a signal

/* ignore.c: Ignores interactive attention
key as */
#include <stdio.h>
#include <signal.h>

main()
{
   char buf[BUFSIZ];
   int i;

   /* Ignore keyboard interruptions */
   signal(SIGINT,SIG_IGN);

   while (gets(buf))
      puts(buf);

   /* Restore default attention key handling
      so the user can abort form the keyboard */
   signal(SIGINT,SIG_DFL);
   for (i = 1; ; ++i)
      printf("%d%c",i,(i%15 ? ' ' : '\n'));
}

/* Sample Execution
c:>ignore
hello
hello
^C
there
there
^C
^Z
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
46 45 48 49 50 51 52 53 54 55 56 57 58 59 60
61 62 63 64 65 66 67 ^C
*/
/* End of file*/
March 1995/Code Capsules/Listing 9

Listing 9 Uses the <stdarg.h> macros to search a variable-length list of integers

/* max.c */
#include <stdio.h>
#include <stdarg.h>

int maxn(size_t count, ...)
{
   int n, big;
   va_list numbers;

   va_start(numbers,count);

   big = va_arg(numbers,int);
   while (count--)
   {
      n = va_arg(numbers,int);
      if (n > big)
         big = n;
   }

   va_end(numbers);
   return big;
}

main()
{
   printf("max = %d\n",maxn(3,1,3,2));
   return 0;
}

/* Output:
max = 3
*/

/* End of File */

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.