TechWorld

An introduction to Ch: Implementing a temperature converter in Ch

Ch is a combined C shell and IDE

Ch is very high-level language environment developed by SoftIntegration, Inc. for Windows, Linux, Solaris, and MacOS X systems. Ch incorporates both a command interpreter (or shell) and an integrated development environment (ChIDE). Ch, unlike other shells, such as the Bourne-Again Shell (bash), the Korn Shell (ksh), or the C Shell (csh), uses the syntax of the C programming language and incorporates a C virtual machine as a shell. This makes Ch a useful tool for learning C programming and for prototyping C programs. This article introduces some of Ch’s features by showing how to write a small Ch program for converting between the Fahrenheit and Celsius temperature scales.

Installing and Starting Ch

Since most Windows, Unix, and MacOS X systems do not come with Ch preinstalled, Ch needs to be downloaded and registered from SoftIntegration’s website. As of this writing, the registration page is located at Softintegration.com.

Once Ch is successfully registered, SoftIntegration will send an email with instructions for downloading and installing Ch. After installing Ch on Windows, Ch’s icon should appear on the desktop. Similarly, on MacOS X, after Ch is installed, its icon should appear in the Dock. Clicking on the Ch icon starts Ch. When Ch starts up it will print out a message similar to the following:

Ch
Standard edition, version 7.0.0.15161
Copyright (C) SoftIntegration, Inc. 2001-2012
http://www.softintegration.com

/>

The > is called the Ch prompt. The / preceding the prompt is the current working directory for Ch. Generally, most familiar commands, such as cd, ls, and cp, work as expected in Ch. In addition to executing such familiar commands, Ch includes the ability to execute statements written in the C language. For example, executing the following statement in Ch:

/> printf("Hello World!")

displays the output:

Hello World!
/>

Here Ch has executed the printf C language function, which is used to print out formatted strings. Once printf finished, Ch displays its prompt (>) again, to indicate that it is ready to execute additional commands or C statements.

A First Ch Program

A basic Ch program consists of a file containing C statements and/or Ch commands. Files containing Ch programs usually end with the .c or .ch suffix. The .c suffix is preferred for Ch programs that contain only C statements, while the .ch suffix is preferred for Ch programs that contain both C statements and Ch commands.

To convert the printf statement from the previous example into a Ch program, create a file named hello_world.ch using an editor, such as vim, emacs, Notepad+, or TextWrangler, that contains the following statements:

#include <stdio.h>

void main () {
printf("Hello World!");
}

The first line of this program—#include <stdio.h>—tells Ch to include or incorporate the contents of file <stdio.h> into the program. The file <stdio.h> includes descriptions of functions, such as the printf function, that can be used to obtain information (input) or print out information (output). In addition the input and output functions described in the <stdio.h> file, the C programing language usually comes several additional files that include descriptions of many other useful functions. The functions these files is sometimes referred to as a library of functions. The examples that follow will describe some of the common files and functions in this library in more detail.

The next line of the program—void main()—defines the main function of the program. When a program is executed, Ch starts by executing the main function. In the example above, the main function includes all the statements between the opening curly bracket ({) following main() and the corresponding closing curly bracket (}) on the last line of the program. In this program, the main function contains just the printf statement. The semicolon (;) at the end of the printf statement indicates the statement is complete. Generally, semicolons are required at the end of statements to indicate where a statement ends.

Assuming the file named “hello_world.ch” containing this program is located in the current directory, the program can be executed as follows:

/> ch ./hello_world.ch

It should display the following output:

Hello World!/>

Unlike in the previous example, notice that the Ch prompt (>) is displayed on the same line as the program’s output. When Ch is running interactively and accepting commands one at a time, it prints its prompt (>) on a new line. But when Ch is not running interactively, such as when it is executing a program, it prints its prompt immediately following the program’s output. To make Ch to print its prompt on a new line, a newline character (\n) needs to be added to the end of the string that the program is printing out:

printf("Hello World!\n");

With this change, the program displays the following output:

Hello World!
/>

Command Line Arguments

When Ch executes a program, additional information can be provided to the program using command line arguments. For example, hello_world.ch can be executed as follows:

/> ch ./hello_world.ch hello world

The strings hello and world following hello_world.ch are the command line arguments that are being supplied to the program. Right now the program will ignore any command line arguments that are supplied to it. To allow the program to handle command line arguments, a few changes need to be made to its main function.

First, the main function’s definition has to be changed to accept command line variables. This can be accomplished by changing the main function’s definition as follows:

void main (int argc, string_t argv[]) {

Here, two arguments, int argc and string_t argv[], have been added to the main function. Adding these two arguments gives the main function the ability to accept and process any command line arguments that are supplied to the program. The first argument, int argc, supplies the program the total number of command line arguments that were specified when the program was executed. The second argument, string_t argv[], supplies the program with the command line arguments.

The int keyword that comes before argc defines the argc variable as an integer variable. An integer variable is a variable that can hold a single positive or negative whole number, such as 3 or -57.

The argument string_t argv[] uses the Ch string_t keyword to define a string variable named argv to hold the command line arguments that were specified when the program was executed. A string variable is a variable can hold strings, such as hello and world. The square brackets ([]) following argv further define argv as an array variable. An array variable is a variable that can hold a collection of values instead of just a single value. Thus, string_t argv[] defines argv as a variable that can hold a collection of strings. The first item in this collection can be retrieved using the notation argv[0]. Similarly, the second item can be retrieved as argv[1], and so on.

Second, the main function needs to be changed to process the command line arguments. For example, the printf statement can be modified to print out the first element of the argv array as follows:

printf("%s\n", argv[0]);

Here the string "Hello World!\n" has been replaced with the string "%s\n" and argv[0] has been added to the printf statement. The string "%s\n", which is called the format statement, tells printf how it should format what it prints out. Specifying "%s\n" as the format statement tells printf that it should print out a string followed by a newline character (\n). In addition to %s, printf understands many additional format specifiers, such as %d for printing integers. The argument argv[0] following the format statement tells printf that the string it should print out is the first element of the argv array.

With these changes, the complete program should look like the following:

#include <stdio.h>

void main (int argc, string_t argv[]) {
printf("%s\n", argv[0]);
}

Page Break

Executing this program with the command line arguments “hello” and “world” gives the following result:

/> ch ./hello_world.ch hello world
./hello_world.ch

The output that appears is ./hello_world.ch rather than hello, which was the first command line argument. This is because the first entry of the argv array, argv[0], is always the name of the program being executed. The first command line argument is always stored in argv[1]. The second command line argument is stored in argv[2], and so on. Thus, to print out the first command line argument, the printf statement needs to be changed as follows:

printf("%s\n", argv[1]);

With this change, the program now prints out the first command line argument “hello”:

/> ch ./hello_world.ch hello world
hello

Loops

To print out the second command line argument, another "%s" format specifier along with the second command line argument, argv[2], could be added to the printf statement as follows:

printf("%s %s\n", argv[1], argv[2]);

But this approach is not practical for processing a large number of command line arguments. A better approach is to use one of the C language loop statements. A loop statement allows a set of statements to be executed repeatedly. Ch supports all of the standard C language loop statements, while, for, and do-while, along with a foreach loop. The do-while and foreach loops are similar to the while and for loops, so, for the sake of brevity, the examples that follow will focus on the while and for loops.

The while loop provides a convenient way to print out each of the command line arguments. Its syntax is as follows:

while (condition) {
first statement;
second statement;
…
last statement;
}

The while loop executes each statement, while the specified condition is true. Since the variable argc holds the number of command line arguments, the while loop can be used to print out each of the command line arguments as follows:

int i = 1;
while (i < argc) {
printf("Argument No. %d was '%s'\n", i, argv[i]);
i = i + 1;
}

Here, a new integer variable named i is created and is assigned a value of 1 (int i = 1). The single equal sign (=), in C and Ch, is known as the assignment operator and is used to set the value of a variable. Next, a while loop is then used to print out each of the command line arguments. The condition for this while loop is that the value of the variable i must be less than the value of argc (i < argc). While that condition is true, a printf statement is used to print out a particular command line argument (argv[i]). After printing out the command line argument, the value of i is increased (or incremented) by 1 (i = i + 1) so that the next time the while loop is executed, the next command line argument is printed out. With this while loop the program will look like the following:

#include <stdio.h>

void main (int argc, string_t argv[]) {
int i = 1;
while (i < argc) {
printf("Argument No. %d was '%s'\n", i, argv[i]);
i = i + 1;
}
}

Executing the program now displays the following result:

/> ch ./hello_world.ch hello world
Argument No. 1 was 'hello'
Argument No. 2 was 'world'

Another type of loop that is frequently used is the for loop. This loop can be used to execute a set of statements for a particular number of times (each execution of the statements in a loop is called an iteration). The for loop’s syntax is as follows:

for (initializer; condition; increment) {
first statement;
second statement;
…
last statement;
}

The first time the for loop executes it executes the initializer, which can be used to set (or initialize) the values of any variables that are used in the for loop. Next, the for loop checks if the specified condition is true. If the condition is true, then each statement is executed. Finally the for loop executes the increment statement. After executing the increment statement, the for loop will check if the condition is true. If the condition is true, it will executing each statement again. The for loop will continue executing the increment statement, executing each of the statements as long as the condition remains true.

To see how the for loop operates, let’s reimplement the while loop in our program using a for loop:

int i;
for (i = 1; i < argc; i = i+1) {
printf("Argument No. %d was '%s'\n", i, argv[i]);
}

As with the while loop, a new integer variable i (int i) is created. But, instead of initializing this variable when it was created, the for loop’s initializer statement is used to initialize the variable’s value to 1 (i = 1). Like the while loop, the condition remains the same—as long as the value of i is less than the value of argc (i < argc), the printf statement should be executed. The increment also statement remains the same, since the value of i should be incremented by 1 (i = i+1) after the printf statement is executed. With this for loop the program will look like the following:

#include <stdio.h>

void main (int argc, string_t argv[]) {
int i;
for (i = 1; i < argc; i = i+1) {
printf("Argument No. %d was '%s'\n", i, argv[i]);
}
}

The output from the program remains the same:

/> ch ./hello_world.ch hello world
Argument No. 1 was 'hello'
Argument No. 2 was 'world'

Conditional Statements and Validating Input

The goal of this article is to write a small Ch program for converting between different temperature scales. To accomplish this, the program needs to receive a temperature to convert, what scale that temperature is in, and what scale to covert that temperature to. Command line arguments can be used to obtain all this information from the user. The program can use the first command line argument to receive the temperature to convert, the second command line argument to receive the scale for that temperature, and the third command line argument to receive the scale to convert the temperature to.

Page Break

Let’s start by creating a new file called temp_converter.ch with the following statements:

#include <stdio.h>

void main (int argc, string_t argv[]) {
string_t temp_raw, scale_in_raw, scale_out_raw;

temp_raw = argv[1]; scale_in_raw = argv[2]; scale_out_raw = argv[3];

	printf("Input temperature: '%s' degrees\n", temp_raw);
printf("Input temperature scale: '%s'\n", scale_in_raw);
printf("Output temperature scale: '%s'\n", scale_out_raw);
}

The program first creates three string variables named temp_raw, scale_in_raw, and scale_out_raw, which store the input temperature, the input temperature’s scale, and the output temperature scale, respectively. Next, the program sets the value of temp_raw equal to the first command line argument (temp_raw = argv[1]), the value of scale_in_raw equal to the second command line argument (scale_in_raw = argv[2]), and the value of scale_out equal to the third command line argument (scale_out_raw = argv[3]). Finally, the program uses three printf statements to print out the values of these variables.

Assuming the file temp_converter.ch containing this program is located in the current directory, the program can be executed as follows:

/> ch ./temp_converter.ch

It should display the following output:

Input temperature: '' degrees
Input temperature scale: ''
Output temperature scale: ''

In its current state, the program accepts blank or null values for the input temperature, the input temperature’s scale, and the output temperature. Because the program needs a value for each of these inputs, the program needs to be able to determine if valid inputs have been provided. This can be accomplished this using the conditional if statement. The basic syntax of an if statement is as follows:

if (condition) {
first statement;
second statement;
…
last statement;
} else {
first statement;
second statement;
…
last statement;
}

When an if statement is executed, the condition is first checked. If the condition is true, then each statement until the else statement is executed. If, however, the condition is false, then each statement following the else statement is executed. The else statement and its associated statements are optional and may be omitted when there are no statements to execute when the condition is false.

The first form of input validation the program needs to perform is to check if at least three command line arguments were provided. This can be checked using the following if statement:

if (argc >= 4) {
temp_raw = argv[1];
scale_in_raw = argv[2];
scale_out_raw = argv[3];

	printf("Input temperature: '%s' degrees\n", temp_raw);
printf("Input temperature scale: '%s'\n", scale_in_raw);
printf("Output temperature scale: '%s'\n",
scale_out_raw);
} else {
printf("Please specify the temperature and scales.\n");
}

The program first checks to see if the value of argc is greater than or equal to 4 (argc >= 4) because, when at least three command line arguments are specified, the value of argc will be greater than or equal to 4 (since the first item in the argv array is the name of the program being executed, the value of argc is always one greater than the total number of arguments supplied on the command line). If the value of argc is greater than or equal to 4, the program can safely set the values of the input temperature, the input temperature’s scale, and the output temperature equal to the first three command line arguments:

temp_raw = argv[1];
scale_in_raw = argv[2];
scale_out_raw = argv[3];

Otherwise, the program prints out a helpful error message, instructing the user to supply the required values:

printf("Please specify the temperature and scales.\n");

With this if statement, the complete program will look like the following:

#include <stdio.h>

void main (int argc, string_t argv[]) {
string_t temp_raw, scale_in_raw, scale_out_raw;

	if (argc >= 4) {
temp_raw = argv[1];
scale_in_raw = argv[2];
scale_out_raw = argv[3];

		printf("Input temperature: '%s' degrees\n", temp_raw);
printf("Input temperature scale: '%s'\n", scale_in_raw);
printf("Output temperature scale: '%s'\n",
scale_out_raw);
} else {
printf("Please specify the temperature and scales.\n");
}
}

Now, if this program is executed without any command line arguments, it will display the following output:

/> ch ./temp_converter.ch
Please specify the temperature and scales.

But when this program is executed with the command line arguments 212, F, C, it will display the following output:

/> ch ./temp_converter.ch 212 F C
Input temperature: '212' degrees
Input temperature scale: 'F'
Output temperature scale: 'C'

The next form of input validation is to determine whether the value that was specified for the temperature was number (although the number can be negative or positive, with any number of decimal points, it has to be a number). This can be accomplished using a if statement and the sscanf C function. The sscanf function reads or scans a string to determine whether it is in a particular format.

To determine whether the value that was specified for the temperature was a number, the following if statement can be used:

if (sscanf(temp_raw, "%f", &temp_in) == 1) {
printf("Input temperature: '%f' degrees\n",
temp_in);
} else {
printf("Invalid input temperature: '%s' degrees,\n",
temp_raw);
}

Page Break

Here, the sscanf function is used to determine whether temp_raw contains a valid number— sscanf(temp_raw, "%f", &temp_in). The string "%f", following temp_raw, tells sscanf that temp_raw is expected to contain a positive or negative number with an optional decimal point (such numbers are referred to as floating point numbers and therefore use the format specifier %f). The value, &temp_in, tells sscanf to store the numeric value of temp_raw in the variable temp_in if temp_raw contained a valid number (the temp_in variable, like the other variables in this program, will be created and initialized at the beginning of the program).

If temp_raw contains a valid number, sscanf will return a value of 1, in which case a printf statement prints out the value specified for the temperature. Otherwise, an error message is printed out. Notice that this if statement uses the equals operator (==) to check whether sscanf returned a value of 1. The equals operator (==) is need to compare whether two values are equal because, as you may recall, C and Ch use a single equal sign (=) to assign a value to a variable (for example, temp_raw = argv[1]).

With this if statement, the complete program will look like the following:

#include <stdio.h>

void main (int argc, string_t argv[]) {
string_t temp_raw, scale_in_raw, scale_out_raw;
float temp_in = 0.0;

	if (argc >= 4) {
temp_raw = argv[1];
scale_in_raw = argv[2];
scale_out_raw = argv[3];

		/* check if the specified input temperature contained a
valid number */

		if (sscanf(temp_raw, "%f", &temp_in) == 1) {
printf("Input temperature is '%f' degrees\n",
temp_in);
} else {
printf("Invalid input temperature: '%s' degrees,\n",
temp_raw);
}

		printf("Input temperature scale: '%s'\n", scale_in_raw);
printf("Output temperature scale: '%s'\n",
scale_out_raw);
} else {
printf("Please specify the temperature and scales.\n");
}
}

As mentioned above, the second line of the main function creates the variable temp_in as a floating point variable using the float keyword and initializes it to the value of 0.0 (float temp_in = 0.0). Also added to the program are the lines:

/* check if the specified input temperature contained a
valid number */

These lines are known as comments. Comments consist of text that is added to a program by the programmer to describe what the program is doing in a particular part. Although there is no requirement to add comments to a program (and both C and Ch ignore comments), including comments in a program is a good idea since comments can those who have to later read or edit the program understand what the program is doing (or trying to do). There are no set rules on what to include in comments, but the examples below should help illustrate the types of information that may be useful to include in comments (for the sake of readability, the comments are included only in the full program listings below)

Now, if this program is executed with an invalid value for the temperature, such as hello, it will display the following output:

/> ch ./temp_converter.ch hello F C
Invalid input temperature: 'hello' degrees
Input temperature scale: 'F'
Output temperature scale: 'C'

But when this program is executed with the command line arguments 212, F, C, it will display the following output:

/> ch ./temp_converter.ch 212 F C
Input temperature: '212.000000' degrees
Input temperature scale: 'F'
Output temperature scale: 'C'

The next validation step is to make sure that valid temperature scales were specified. To simplify the program, the valid temperature scales can be limited to either the Fahrenheit (F) or Celsius (C) temperature scales. To check whether the specified temperature scale was either Fahrenheit or Celsius, the strcmp C function, which compares two strings and returns a value of 0 if the strings match, in combination with an if statement can be used:

if (strcmp(scale_in_raw, "Fahrenheit") == 0 ||
strcmp(scale_in_raw, "F") == 0 ||
strcmp(scale_in_raw, "Celsius") == 0 ||
strcmp(scale_in_raw, "C") == 0) {
printf("Input temperature scale: '%s'\n",
scale_in_raw);
} else {
printf("Invalid input temperature scale: '%s'\n",
scale_in_raw);
}

Here, the strcmp function is first used to see whether the input temperature scale, scale_in, equals the string "Fahrenheit" (strcmp(scale_in, "Fahrenheit") == 0). If it does not, the or operator, ||, is used to check whether scale_in equals the string "F" (strcmp(scale_in, "F") == 0). If scale_in does not equal "Fahrenheit" or "F", the or operator, ||, is then used to check whether scale_in equals the strings “Celsius” or “C”:

strcmp(scale_in_raw, "Celsius") == 0 ||
strcmp(scale_in_raw, "C") == 0)

If scale_in equals at least one of these values, then its value is printed out. If not, an error message is printed. With this if statement, the complete program will look like the following:

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

void main (int argc, string_t argv[]) {
string_t temp_raw, scale_in_raw, scale_out_raw;
float temp_in = 0.0;

	if (argc >= 4) {
temp_raw = argv[1];
scale_in_raw = argv[2];
scale_out_raw = argv[3];

		/* check if the specified input temperature contained a
valid number */

		if (sscanf(temp_raw, "%f", &temp_in) == 1) {
printf("Input temperature: '%f' degrees\n",
temp_in);
} else {
printf("Invalid input temperature: '%s' degrees\n",
temp_raw);
}

		/* check if the input temperature scale is either
Fahrenheit or Celsius */

		if (strcmp(scale_in_raw, "Fahrenheit") == 0 ||
strcmp(scale_in_raw, "F") == 0 ||
strcmp(scale_in_raw, "Celsius") == 0 ||
strcmp(scale_in_raw, "C") == 0) {
printf("Input temperature scale: '%s'\n",
scale_in_raw);
} else {
printf("Invalid input temperature scale: '%s'\n",
scale_in_raw);
}

		printf("Output temperature scale: '%s'\n",
scale_out_raw);
} else {
printf("Please specify the temperature and scales.\n");
}
}

Page Break

One additional change to this program is the first line—#include <string.h>—which tells Ch to incorporate the file string.h into the program. This file includes descriptions of string manipulation functions, like strcmp. Now, if this program is executed with an invalid input temperature scale, such as hello, it will display the following output:

/> ch ./temp_converter.ch 212 hello C
Input temperature: '212.000000' degrees
Invalid input temperature scale: 'hello'
Output temperature scale: 'C'

But when this program is executed with the command line arguments 212, F, C, it will display the following output:

/> ch ./temp_converter.ch 212 F C
Input temperature: '212.000000' degrees
Input temperature scale: 'F'
Output temperature scale: 'C'

Observe, however, that if the input temperature scale is specified as fahrenheit instead of Fahrenheit, the program will reject this as an invalid input temperature scale:

/> ch ./temp_converter.ch 212 F C
Input temperature: '212.000000' degrees
Invalid input temperature scale: 'fahrenheit'
Output temperature scale: 'C'

This behavior can be easily fixed by replacing the strcmp function with the strcasecmp function as follows:

if (strcasecmp(scale_in_raw, "Fahrenheit") == 0 ||
strcasecmp(scale_in_raw, "F") == 0 ||
strcasecmp(scale_in_raw, "Celsius") == 0 ||
strcasecmp(scale_in_raw, "C") == 0) {
printf("Input temperature scale: '%s'\n",
scale_in_raw);
} else {
printf("Invalid input temperature scale: '%s'\n",
scale_in_raw);
}

Functions

To verify whether a valid output temperature scale was specified, a similar if statement could easily be added to the program, but whenever any changes needed to be made to one if statement, the same changes would likely have to be made to the other if statement. Forgetting to make this change could introduce unintended errors to the program. A better approach would be to create a function that performed the task of validating whether a valid output temperature scale was specified. The basic version of such a function can be created as follows:

int which_scale(string_t scale) {
return -1;
}

These statements create a function called which_scale. This function takes as its input a single string variable named scale. The int keyword that precedes the function name, which_scale, indicates that this function returns an integer value when it completes. In the initial version, shown above, the function uses the C language return statement to return a value of -1.

To make the function more useful, it can be changed to return a value of 1 when the specified scale is Fahrenheit and 2 when specified scale is Celsius as follows:

if (strcasecmp(scale, "Fahrenheit") == 0 ||
strcasecmp(scale, "F") == 0) {
return 1;
}

if (strcasecmp(scale, "Celsius") == 0 ||
strcasecmp(scale, "C") == 0) {
return 2;
}

With this change, the complete function is as follows:

int which_scale(string_t scale) {
if (strcasecmp(scale, "Fahrenheit") == 0 ||
strcasecmp(scale, "F") == 0) {
return 1;
}

	if (strcasecmp(scale, "Celsius") == 0 ||
strcasecmp(scale, "C") == 0) {
return 2;
}

	return -1;
}

To use this function to check whether the input temperature scale, stored in the variable scale_in_raw, was specified in Fahrenheit or Celsius, the following statements can be added to the main function:

scale_in = which_scale(scale_in_raw);
if (scale_in == 1 || scale_in == 2) {
printf("Input temperature scale: '%s'\n",
scale_in_raw);
} else {
printf("Invalid input temperature scale: '%s'\n",
scale_in_raw);
}

Here, the which_scale function is executed with the input temperature scale, stored in the variable scale_in_raw, as its input and the return value from the which_scale is stored in the variable scale_in (scale_in = which_scale(scale_in_raw)). The value returned by the which_scale function is then checked to see if it was either 1 (indicating that Fahrenheit was specified) or 2 (indicating that Celsius was specified). If the value returned by the which_scale function corresponds to either Fahrenheit or Celsius, then a message indicating which temperate scale was specified is printed out. Otherwise, an error message indicating that the specified temperature scale was invalid is printed out.

Similarly, the which_scale function can be used to check whether the output temperature scale, stored in the variable scale_out_raw, was specified in Fahrenheit or Celsius, by adding the following statements to the main function:

scale_out = which_scale(scale_out_raw);
if (scale_out == 1 || scale_out == 2) {
printf("Output temperature scale: '%s'\n",
scale_out_raw);
} else {
printf("Invalid output temperature scale: '%s'\n",
scale_out_raw);
}

Now that the program has been updated to check the values supplied for both the input and output temperature scales, one final input validation can be performed—to check whether the same value was specified for both the input and the output temperature scales. This can be accomplished using the following if statement:

if (scale_in == scale_out) {
printf("Input and output scales are the same\n");
}

With these changes, the complete program looks like the following:

#include <strings.h>
#include <string.h>
#include <stdio.h>

int which_scale(string_t scale);

int which_scale(string_t scale) {

	/* return 1 if the temperature scale is Fahrenheit or
F; use strcasecmp for case insensitive comparisons */

	if (strcasecmp(scale, "Fahrenheit") == 0 ||
strcasecmp(scale, "F") == 0) {
return 1;
}

	/* return 2 if the temperature scale is Celsius or
C; use strcasecmp for case insensitive comparisons */

	if (strcasecmp(scale, "Celsius") == 0 ||
strcasecmp(scale, "C") == 0) {
return 2;
}

	return -1;
}

void main (int argc, string_t argv[]) {
string_t temp_raw, scale_in_raw, scale_out_raw;
int scale_in = -1, scale_out = -1;
float temp_in = 0.0;

	if (argc >= 4) {
temp_raw = argv[1];
scale_in_raw = argv[2];
scale_out_raw = argv[3];

		/* check if the specified input temperature contained a
valid number */

		if (sscanf(temp_raw, "%f", &temp_in) == 1) {
printf("Input temperature: '%f' degrees\n",
temp_in);
} else {
printf("Invalid input temperature: '%s' degrees\n",
temp_raw);
}

		/* verify that the input temperature scale was either
Fahrenheit or Celsius */

		scale_in = which_scale(scale_in_raw);
if (scale_in == 1 || scale_in == 2) {
printf("Input temperature scale: '%s'\n",
scale_in_raw);
} else {
printf("Invalid input temperature scale: '%s'\n",
scale_in_raw);
}

		/* verify that the output temperature scale was either
Fahrenheit or Celsius */

		scale_out = which_scale(scale_out_raw);
if (scale_out == 1 || scale_out == 2) {
printf("Output temperature scale: '%s'\n",
scale_out_raw);
} else {
printf("Invalid output temperature scale: '%s'\n",
scale_out_raw);
}

		if (scale_in == scale_out) {
printf("Input and output scales are the same\n");
}

	} else {
printf("Please specify the temperature and scales.\n");
}
}

One additional change to the program is the addition of a statement just before the first line of the which_scale function:

int which_scale(string_t scale);

int which_scale(string_t scale) {

Although this statement looks nearly identical to the first line of the which_scale function, it has a different purpose. This statement is called a function prototype and defines what inputs a function will accept and what value a function returns. Although Ch does not require function prototypes, it is a good practice to include them in Ch programs because prototypes are required in well-written C programs.

Page Break

Now, if this program is executed with an invalid input or output temperature scale, such as hello, it will display the following output:

/> ch ./temp_converter.ch 212 hello hello
Input temperature: '212.000000' degrees
Invalid input temperature scale: 'hello'
Invalid output temperature scale: 'hello'

Likewise, when the program is executed with the same input and output temperature scale, it will display the following output:

/> ch ./temp_converter.ch 212 F F
Input temperature: '212.000000' degrees
Input temperature scale: 'F'
Output temperature scale: 'F'
Input and output scales are the same

When, however, this program is executed with the command line arguments 212, F, C, it will display the following output:

/> ch ./temp_converter.ch 212 F C
Input temperature: '212.000000' degrees
Input temperature scale: 'F'
Output temperature scale: 'C'

Enumerations

Although the program can correctly determine whether the input and/or output temperature scale was specified as either Fahrenheit or Celsius, the program relies on the hard-coded values of 1 and 2 to represent Fahrenheit or Celsius, respectively. To make the program easier to maintain (and extend), a better approach would be to use named integer constants to represent these values. These types of constants can be created using C language enumerations.

A C language enumeration can be created using the enum keyword, which has the following syntax:

enum {
first constant = first value,
second constant = second value,
…
last constant = last value
};

The first constant is assigned the first value, the second constant is assigned the second value, and so on. (The values are optional, and if omitted, start from 0 and increase by 1).

Using the enum keyword, constant values to represent Fahrenheit or Celsius can be created as follows:

enum {
SCALE_F = 1, /* constant value for Fahrenheit */
SCALE_C = 2 /* constant value for Celsius */
};

Once these constants have been crated, the which_scale function can be updated to use them as follows:

int which_scale(string_t scale) {

	if (strcasecmp(scale, "Fahrenheit") == 0 ||
strcasecmp(scale, "F") == 0) {
return SCALE_F;
}

	if (strcasecmp(scale, "Celsius") == 0 ||
strcasecmp(scale, "C") == 0) {
return SCALE_C;
}

	return -1;
}

Next, the two if statements in the main function that check the value returned by the which_scale function can be updated as follows:

if (scale_in == SCALE_F || scale_in == SCALE_C) {
…
if (scale_out == SCALE_F || scale_out == SCALE_C) {

With these changes, the complete program looks like the following:

#include <strings.h>
#include <string.h>
#include <stdio.h>

enum {
SCALE_F = 1, /* constant value for Fahrenheit */
SCALE_C = 2 /* constant value for Celsius */
};

int which_scale(string_t scale) {

	/* return SCALE_F if the temperature scale is Fahrenheit or
F; use strcasecmp for case insensitive comparisons */

	if (strcasecmp(scale, "Fahrenheit") == 0 ||
strcasecmp(scale, "F") == 0) {
return SCALE_F;
}

	/* return SCALE_C if the temperature scale is Celsius or
C; use strcasecmp for case insensitive comparisons */

	if (strcasecmp(scale, "Celsius") == 0 ||
strcasecmp(scale, "C") == 0) {
return SCALE_C;
}

	return -1;
}

void main (int argc, string_t argv[]) {
string_t temp_raw, scale_in_raw, scale_out_raw;
int scale_in = -1, scale_out = -1;
float temp_in = 0.0;

	if (argc >= 4) {
temp_raw = argv[1];
scale_in_raw = argv[2];
scale_out_raw = argv[3];

		/* check if the specified input temperature contained a
valid number */

		if (sscanf(temp_raw, "%f", &temp_in) == 1) {
printf("Input temperature: '%f' degrees\n",
temp_in);
} else {
printf("Invalid input temperature: '%s' degrees\n",
temp_raw);
}

		/* verify that the input temperature scale was either
Fahrenheit or Celsius */

		scale_in = which_scale(scale_in_raw);
if (scale_in == SCALE_F || scale_in == SCALE_C) {
printf("Input temperature scale: '%s'\n",
scale_in_raw);
} else {
printf("Invalid input temperature scale: '%s'\n",
scale_in_raw);
}

		/* verify that the output temperature scale was either
Fahrenheit or Celsius */

		scale_out = which_scale(scale_out_raw);
if (scale_out == SCALE_F || scale_out == SCALE_C) {
printf("Output temperature scale: '%s'\n",
scale_out_raw);
} else {
printf("Invalid output temperature scale: '%s'\n",
scale_out_raw);
}

		if (scale_in == scale_out) {
printf("Input and output scales are the same\n");
}

	} else {
printf("Please specify the temperature and scales.\n");
}
}

Exiting on Errors

As noted above, there are several situations in which invalid or erroneous inputs may be supplied to the program. First, the user may not supply the input temperature, the input temperature’s scale, and/or the output temperature scale. Second, the specified temperature may not be a valid numerical value. Third, the input and output temperature scales may not be specified in Fahrenheit or Celsius. Finally, the input and output temperature scales may be the same.

When these situations occur, instead of continuing to execute, the program should report the error and terminate using the exit C function. For example, if the user does not supply the input temperature, the input temperature’s scale, and the output temperature scale, argc will have a value less than 4 (argc < 4). If this occurs, the program can terminate using the following if statement:

if (argc < 4) {
printf("Please specify the temperature and scales.\n");
exit(1);
}

Here, the exit function is executed if argc has a value less than 4. The value of 1 that is supplied to the exit function (exit(1)), is known as the exit code. Generally, when a program completes without encountering any errors, it exits with an exit code of 0. If any errors occur, a program generally exits with an exit code of greater than 0. Because the failure to specify the input temperature, the input temperature’s scale, and the output temperature scale is an error, the exit function is supplied with an exit code of 1.

Similarly, the program can detect when a specified temperature is not a valid numerical value and terminate using the following if statement:

if (sscanf(temp_raw, "%f", &temp_in) != 1) {
printf("Invalid input temperature: '%s' degrees\n",
temp_raw);
exit(1);
}

Because sscanf will return a value of 1 if a valid numerical value was supplied for the temperature (stored in temp_raw), this if statement uses the not-equal-to operator (!=) to check whether sscanf did not return a value of 1 (sscanf(temp_raw, "%f", &temp_in) != 1) and causes the program to exit with an exit code of 1 (exit(1)) whenever sscanf returns a value that does not equal 1.

To have the program exit when the input temperature scale is not specified in Fahrenheit or Celsius, the following if statement can be used:

if (scale_in != SCALE_F && scale_in != SCALE_C) {
printf("Invalid input temperature scale: '%s'\n",
scale_in_raw);
exit(1);
}

This if statement uses the and operator (&&) to check whether (1) the input temperature scale, stored in scale_in, does not equal the temperature scale constant corresponding to Fahrenheit (scale_in != SCALE_F), and whether the input temperature scale does not equal the temperature scale constant corresponding to Celsius (scale_in != SCALE_C). If the input temperature scale does not equal either of these values, then an invalid temperature scale was specified, so the if statement causes the program to exit with an exit code of 1 (exit(1)). A similar if statement can be used to make the program exit when the output temperature scale is not specified in Fahrenheit or Celsius:

if (scale_out != SCALE_F && scale_out != SCALE_C) {
printf("Invalid output temperature scale: '%s'\n",
scale_out_raw);
exit(1);
}

The program can also use the following if statement to exit when the input and output temperature scales are the same (scale_in == scale_out):

if (scale_in == scale_out) {
printf("Input and output scales are the same\n");
exit(1);
}

Finally, if every input was acceptable, the program can exit successfully with an exit code of 0, as follows:

exit(0);

Since the program now exits with an exit code, the return type of the main function also needs to be changed to return an integer to indicate that the main function will be return an integer value (such as 0 or 1) when it finishes:

int main (int argc, string_t argv[]) {

Page Break

Also, the program needs to include the file stdlib.h (#include <stdlib.h>), which includes a description of the exit function. With these changes, the complete program looks like the following:

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

enum {
SCALE_F = 1, /* constant value for Fahrenheit */
SCALE_C = 2 /* constant value for Celsius */
};

int which_scale(string_t scale) {

	/* return SCALE_F if the temperature scale is Fahrenheit or
F; use strcasecmp for case insensitive comparisons */

	if (strcasecmp(scale, "Fahrenheit") == 0 ||
strcasecmp(scale, "F") == 0) {
return SCALE_F;
}

	/* return SCALE_C if the temperature scale is Celsius or
C; use strcasecmp for case insensitive comparisons */

	if (strcasecmp(scale, "Celsius") == 0 ||
strcasecmp(scale, "C") == 0) {
return SCALE_C;
}

	return -1;
}

int main (int argc, string_t argv[]) {
string_t temp_raw, scale_in_raw, scale_out_raw;
int scale_in = -1, scale_out = -1;
float temp_in = 0.0;

	if (argc < 4) {
printf("Please specify the temperature and scales.\n");
exit(1);
}

	temp_raw = argv[1];
scale_in_raw = argv[2];
scale_out_raw = argv[3];

	/* check if the specified input temperature contained a
valid number */

	if (sscanf(temp_raw, "%f", &temp_in) != 1) {
printf("Invalid input temperature: '%s' degrees\n",
temp_raw);
exit(1);
}

	printf("Input temperature: '%f' degrees\n", temp_in);

	/* verify that the input temperature scale was either
Fahrenheit or Celsius */

	scale_in = which_scale(scale_in_raw);
if (scale_in != SCALE_F && scale_in != SCALE_C) {
printf("Invalid input temperature scale: '%s'\n",
scale_in_raw);
exit(1);
}

	printf("Input temperature scale: '%s'\n",
scale_in_raw);

	scale_out = which_scale(scale_out_raw);
if (scale_out != SCALE_F && scale_out != SCALE_C) {
printf("Invalid output temperature scale: '%s'\n",
scale_out_raw);
exit(1);
}

	/* verify that the output temperature scale was either
Fahrenheit or Celsius */

	printf("Output temperature scale: '%s'\n",
scale_out_raw);

	if (scale_in == scale_out) {
printf("Input and output scales are the same\n");
exit(1);
}

	exit(0);
}

Now when the program is executed with an invalid temperature, it displays the following output:

/> ch ./temp_converter.ch hello F C
Invalid input temperature: 'hello' degrees

When the program is executed with an invalid input temperature scale, it displays the following output:

/> ch ./temp_converter.ch 212 hello F
Input temperature: '212.000000' degrees
Invalid input temperature scale: 'hello'

Likewise, when the output temperature scale in invalid, the program displays the following output:

/> ch ./temp_converter.ch 212 F hello
Input temperature: '212.000000' degrees
Input temperature scale: 'F'
Invalid output temperature scale: 'hello'

When the input and output temperature scales are the same, the program displays the following output:

/> ch ./temp_converter.ch 212 F F
Input temperature: '212.000000' degrees
Input temperature scale: 'F'
Output temperature scale: 'F'
Input and output scales are the same

When the inputs are all valid, however, the program displays the following output:

/> ch ./temp_converter.ch 212 F C
Input temperature: '212.000000' degrees
Input temperature scale: 'F'
Output temperature scale: 'C'

Converting Temperatures

Now that the program is able to validate its inputs, it is finally possible to implement the actual process of converting from one temperature scale to another. This can be done by creating a pair of functions: one for converting Celsius temperatures to Fahrenheit temperatures, and another for converting Celsius temperatures to Fahrenheit temperatures.

To convert a Celsius temperature to a Fahrenheit temperature, the Celsius temperature is multiplied by 1.8 and 32 is added to the result: (1.8 × °C) + 32 = °F. The following function can be used to perform this calculation:

float celsius_to_fahrenheit(float c_temp) {
float f_temp = 0.0;
f_temp = (1.8 * c_temp) + 32;
return f_temp;
}

This function takes as its input a floating point value as containing a Celsius temperature (float c_temp) returns a floating point value (float) corresponding to the equivalent Fahrenheit temperature. First, this function creates and initializes a floating point variable, f_temp, to hold the Fahrenheit temperature (float f_temp = 0.0). Next, the function uses the multiplication operator (*) to multiply the input Celsius temperature, c_temp, by 1.8 and adds 32 to the result ((1.8 * c_temp) + 32). The parenthesis, (), surrounding 1.8 * c_temp ensures that this calculation will be performed first and then 32 will be added to the result. The result of the entire calculation is then assigned to f_temp (f_temp = (1.8 * c_temp) + 32) and this value is returned (return f_temp).

Similarly, to convert a Fahrenheit temperature to a Celsius temperature, 32 is subtracted from the Fahrenheit temperature and the result is divided by 1.8: (°F – 32) ÷ 1.8 = °C. The following function can be used to perform this calculation:

float fahrenheit_to_celsius(float f_temp) {
float c_temp = 0.0;
c_temp = (f_temp - 32) / 1.8;
return c_temp;
}

This function takes as its input a floating point value as containing a Fahrenheit temperature (float f_temp) returns a floating point value (float) corresponding to the equivalent Celsius temperature. First, this function creates and initializes a floating point variable, c_temp, to hold the Celsius temperature (float c_temp = 0.0). Next, the function subtracts 32 from the input Fahrenheit temperature, f_temp, and then uses the divide operator (/) divides the result ((f_temp - 32) / 1.8). The parenthesis, (), surrounding f_temp - 32 ensures that this calculation will be performed first and then the result will be divided by 1.8. The result of the entire calculation is then assigned to c_temp (c_temp = (f_temp - 32) / 1.8) and this value is returned (return c_temp).

To make use of these two functions the main function needs to be updated to use them as appropriate. The program should use the celsius_to_fahrenheit function when the input temperature scale was specified as Celsius and the output temperature scale was specified as Fahrenheit. This can be checked for using the following if statement:

if (scale_in == SCALE_C && scale_out == SCALE_F) {
temp_out = celsius_to_fahrenheit(temp_in);
printf("%f degrees C is %f degrees F" ,
temp_in, temp_out);
exit(0);
}

Here, the program checks whether the input temperature scale equals the constant value corresponding with the Celsius scale (scale_in == SCALE_C) and whether the output temperature scale, scale_out, equals the constant value corresponding with the Fahrenheit scale (scale_out == SCALE_F). If both of these are true, the program executes the celsius_to_fahrenheit function with the supplied temperature value (temp_in) and assigns the resulting value to the variable temp_out (which of course must be created and initialized at the beginning of the main function). A printf statement is used to print out the result, and then the program exits with an exit code of 0 (exit(0)), indicating that the program executed without encountering any problems.

Likewise, the following if statement determines whether input temperature scale was specified as Fahrenheit and the output temperature scale was specified as Celsius, and executes the fahrenheit_to_celsius function to do the conversion:

if (scale_in == SCALE_F && scale_out == SCALE_C) {
temp_out = fahrenheit_to_celsius(temp_in);
printf("%f degrees F is %f degrees C",
temp_in, temp_out);
exit(0);
}

Here, the program checks whether the input temperature scale equals the constant value corresponding with the Fahrenheit scale (scale_in == SCALE_F) and whether the output temperature scale, scale_out, equals the constant value corresponding with the Celsius scale (scale_out == SCALE_C). If both of these are true, the program executes the Fahrenheit_to_celsius function with the supplied temperature value (temp_in) and assigns the resulting value to the variable temp_out. A printf statement is used to print out the result, and then the program exits with an exit code of 0 (exit(0)), indicating that the program executed without encountering any problems.

Since the program does not allow the input and output temperature scales to be the same, the program should exit after executing one of these two if statements. If, however, some error occurs and the program continues executing, the program should exit with an exit code of 1 to indicate that an unexpected error occurred (exit(1)). In addition, the printf statements that display the input temperature, and input and output temperature scales are not needed any more, so they can be eliminated. With these changes, the complete program looks like the following:

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

int which_scale(string_t scale);
float celsius_to_fahrenheit(float c_temp);
float fahrenheit_to_celsius(float f_temp);

enum {
SCALE_F = 1, /* constant value for Fahrenheit */
SCALE_C = 2 /* constant value for Celsius */
};

float celsius_to_fahrenheit(float c_temp) {
float f_temp = 0.0;
f_temp = (1.8 * c_temp) + 32;
return f_temp;
}

float fahrenheit_to_celsius(float f_temp) {
float c_temp = 0.0;
c_temp = (f_temp - 32) / 1.8;
return c_temp;
}

int which_scale(string_t scale) {

	/* return SCALE_F if the temperature scale is Fahrenheit or
F; use strcasecmp for case insensitive comparisons */

	if (strcasecmp(scale, "Fahrenheit") == 0 ||
strcasecmp(scale, "F") == 0) {
return SCALE_F;
}

	/* return SCALE_C if the temperature scale is Celsius or
C; use strcasecmp for case insensitive comparisons */

	if (strcasecmp(scale, "Celsius") == 0 ||
strcasecmp(scale, "C") == 0) {
return SCALE_C;
}

	return -1;
}

int main (int argc, string_t argv[]) {
string_t temp_raw, scale_in_raw, scale_out_raw;
int scale_in = -1, scale_out = -1;
float temp_in = 0.0, temp_out = 0.0;

	if (argc < 4) {
printf("Please specify the temperature and scales.\n");
exit(1);
}

	temp_raw = argv[1];
scale_in_raw = argv[2];
scale_out_raw = argv[3];

	/* check if the specified input temperature contained a
valid number */

	if (sscanf(temp_raw, "%f", &temp_in) != 1) {
printf("Invalid input temperature: '%s' degrees\n",
temp_raw);
exit(1);
}

	/* verify that the input temperature scale was either
Fahrenheit or Celsius */

	scale_in = which_scale(scale_in_raw);
if (scale_in != SCALE_F && scale_in != SCALE_C) {
printf("Invalid input temperature scale: '%s'\n",
scale_in_raw);
exit(1);
}

	/* verify that the output temperature scale was either
Fahrenheit or Celsius */

	scale_out = which_scale(scale_out_raw);
if (scale_out != SCALE_F && scale_out != SCALE_C) {
printf("Invalid output temperature scale: '%s'\n",
scale_out_raw);
exit(1);
}

	/* no point in converting values, if the input and
output scales are the same */

	if (scale_in == scale_out) {
printf("Input and output scales are the same\n");
exit(1);
}

	/* if the input scale is Celsius and the output scale
is Fahrenheit, use the celsius_to_fahrenheit function

	if (scale_in == SCALE_C && scale_out == SCALE_F) {
temp_out = celsius_to_fahrenheit(temp_in);
printf("%f degrees C is %f degrees F\n",
temp_in, temp_out);
exit(0);
}

	/* if the input scale is Fahrenheit and the output scale
is Celsius, use the Fahrenheit_to_celsius function
to convert the temperature */

	if (scale_in == SCALE_F && scale_out == SCALE_C) {
temp_out = fahrenheit_to_celsius(temp_in);
printf("%f degrees F is %f degrees C\n",
temp_in, temp_out);
exit(0);
}

	exit(1);
}

Now, the program now correctly converts between Fahrenheit and Celsius:

/> ch ./temp_converter.ch 212 F C
212.000000 degrees F is 100.000000 degrees C
/> ch ./temp_converter.ch 0 C F
0.000000 degrees C is 32.000000 degrees F

Improvements

There are several minor improvements that could be made to this program to make it more useful. For example, the output, while correct, may be a little hard to read because of the number of decimal places that are displayed after the input and output temperatures. The output can be made slightly easier to read by limiting the number of decimal places displayed by changing the format specifier used by the printf statements. For values printed with a decimal point, such as those printed by the %l format specifier, the minimum length of the number to be printed and the number of decimal places to display can be change as follows:

%length.accuracyl

Here, length is the minimum length of the number, while accuracy is the number of decimal places to print out (to specify no minimum length for the number, a value of 0 can be used). For example, to limit the output to just 2 decimal places, the value of %0.2l can be used:

printf("%0.2f degrees C is %0.2f degrees F\n",
temp_in, temp_out);
printf("%0.2f degrees F is %0.2f degrees C\n",
temp_in, temp_out);

With these changes, the output now looks like the following:

/> ch ./temp_converter.ch 212 F C
212.00 degrees F is 100.00 degrees C
/> ch ./temp_converter.ch 0 C F
0.00 degrees C is 32.00 degrees F

Another improvement that could be made to the program would be to add support to and from temperatures specified in the Kelvin and/or Rankine temperature scales. While adding support for these additional temperature scales, another change that could be made would be to check for input temperature values that are below absolute zero.

Conclusion

As this article has shown Ch provides a very nice environment for learning C and for quickly developing and testing C programs features by showing how to easily write a small and useful Ch program.