Ada Programming/Input Output/Text Example

Introduction
Those of us with American friends often have to deal with the Fahrenheit temperature scale. While the rest of the world has adopted the Celsius temperature scale, America and a few other countries have stayed true to Fahrenheit.

While this is both a small and insignificant problem when dealing with friends, it is nevertheless a problem worthy of having its own program: A Fahrenheit to Celsius converter.

The necessary files and directories
Before we start, we need to do a little preparation. First create the following directory/file structure somewhere on your computer:

The above should leave you with the following contents in the  directory:

exe/ objects/ ftoc.adb ftoc.gpr

We will use these two directories and files in the following manner:


 * exe/ This is the directory where the  executable is placed when compiling the program
 * objects/ When compiling a program, the compiler creates ALI files, object files and tree files. These files are placed in this directory.
 * ftoc.adb The main Ada source file for the ftoc program.
 * ftoc.gpr This is an Ada project file. This file controls various properties of the program, such as how to compile it, what sources to include, where to put things, and so on.

The Project File
Add this to the  file:

Please go here for an explanation on the different parts of the above project file.

The Actual Program
With the project file out of the way, we turn our attention to the actual  code. Add this to the  file:

Save the file, and lets move on to the final step: Compiling.

Compile And Run FtoC
If you use the excellent GNAT Studio IDE, the compiling is a simple matter of pressing  to compile the project, and   to execute it. If you're not using this IDE, then do this instead:

You should see some output scroll by:

gcc -c -gnatwa -gnaty3abcdefhiklmnoprstux -Wall -O2 -I- -gnatA /home/thomas/FtoC/ftoc.adb gnatbind -I- -x /home/thomas/FtoC/objects/ftoc.ali gnatlink /home/thomas/FtoC/objects/ftoc.ali -o /home/thomas/FtoC/exe/ftoc

You now have an executable in the / directory. When you run it, you get this (somewhat abbreviated here):

0 -17.78 1 -17.22  2 -16.67  ...  26  -3.33  27  -2.78  28  -2.22  29  -1.67  30  -1.11  31  -0.56  32   0.00  33   0.56  34   1.11  35   1.67  ...  48   8.89  49   9.44  50  10.00  51  10.56  52  11.11  ...  67  19.44  68  20.00  69  20.56  70  21.11  71  21.67  ...  84  28.89  85  29.44  86  30.00  87  30.56  ...  210  98.89  211  99.44  212 100.00

Now we know that 0 degrees Fahrenheit equals -17.78 Celsius and at 212 degrees Fahrenheit water boils, which to us Celsius users is known as 100 degrees Celsius. The program works! Lets go over it in detail, to figure out how it works.

Note: All output in the following examples will be heavily abbreviated, because printing 213 lines of output for every little example is madness.

How Does The FtoC Program Work?
Lets start with the first three lines:

These are called  clauses and   declarations. An Ada  clause can be likened to a C. When you see, it means the utilities of the   package are being made available to the program. When next you see the declaration, it means that the utilities of   are directly visible to the program, ie. you do not have to write

to call the  procedure. Instead you simply do

In the FtoC program we utilize, and make visible, three packages:,   and. These packages provide exactly what their names imply: Text IO capabilities for different types.



Looking at the source, you might wonder where the need for  comes in, as we're only outputting numbers. We will get to that in a minute, so stay tuned.

The FtoC declarations
Next we have this code:

This is called the declarative part of an Ada program. It is here we declare our variables, constants, types and whatnot. In the case of FtoC we have 5 declarations. Lets talk a bit about the first two:

That right there is one of the biggest selling points of Ada: The ability to create your own types, with your own constraints. It might not seem like a big deal but, believe me, it is. In this case we've created a new subtype of the built-in subtype Natural. We've constrained the type's range to, meaning that objects declared as a   can never go below 0 or above 212. If a value outside this range is assigned to an object of the  type, a   exception is raised.

With the  type in place, we direct our attention to the declaration and assignment of the   variable. If you've never seen this syntax before or it makes no sense to you, please read the Constants article on this Wiki, and then return here.

The  variable is of the   type and it's initial value is 0. Why 0? Because we assign it the value, and the   part equals the first value in the range constraint of the type, in this case 0. Consequently the value for  is 212.

Let's take a look at the final three object declarations:

What's going on here? Why are there no  associated with any of those declarations? Well, if you've read the Constants article, you will know that these constants are called named numbers and their  is called an universal type. Named numbers can be of any size and precision.

The  and   constants are used in the Fahrenheit-to-Celsius calculation, and   define how many conversions we do in the program.

The FtoC body
With the declarations out of the way, we turn our attention to the body of the program:

The reserved word  signifies the beginning of the   and that same body ends with the final. Between those two, we have a bunch of statements.

The loop
The first one is the  statement. Loops come in many different shapes in Ada, each of which obeys the basic premise of

Loops can be terminated using the  keyword:

This construction is so common that a shorter version has been made available:

It is this last version we use in the FtoC program and we exit the loop when  equals the   value of the   type.

FtoC output - putting integers on the screen
Immediately after the  statement we encounter the   procedure:

We know from the declaration that  is a subtype of , which in turn is a subtype of  , so the call to   on this line actually calls. As you can see, we give  two parameters:   and. The meaning of  should be obvious: It's the integer we want to output, in our case the   variable.

on the other hand does not make as much sense. The  parameter gives the minimum number of characters required to output the integer type or, in our case, subtype, as a literal, plus one for a possible negative sign. If the  parameter is set too high, the integer literal is padded with whitespace. If it's set too low, it is automatically expanded as necessary. Setting the  parameter to 0, results in the field being the minimum width required to contain the integer.

The  attribute returns the maximum width of the type, so if we change the   later on, we wouldn't have to do a single thing about this call to  ; it would simply adjust itself accordingly.

Let's do some tests with various  parameters:

The output now looks like this:

0 -17.78          1 -17.22           2 -16.67           3 -16.11           4 -15.56           ...

Lots of wasted space there. This is because  now sets aside space for the , which is the type   is derived from. Let's try with 0:

And the output:

0 -17.78 1 -17.22  2 -16.67  3 -16.11  9 -12.78  10 -12.22  11 -11.67  99  37.22  100  37.78  101  38.33  ...

Unfortunately for readability, the  integer literal is no longer right-justified. Each number is given the exact width necessary to hold it and no more.

Let's try it with a  that is wide enough for some of the   values, but not all of them:

This outputs:

0 -17.78  1 -17.22   2 -16.67   9 -12.78  10 -12.22  11 -11.67  98  36.67  99  37.22  100  37.78  101  38.33  102  38.89  103  39.44  ...

As you can see, the single digit values are right-justified and padded with 1 space, the two-digit values come out even, but the rest of the results are expanded to hold the third character.

FtoC output - now with floats
With  for integer types out of the way, we move on to the next  >

The  parameter for this call to   is a float, because   is a named number that contains a decimal point and the expression   is converted to a float using the   expression. So when calling this, we're actually calling.

The  parameter gives the minimum character count necessary to output the value preceding the decimal point. As with  for the integer types,   will automatically expand if necessary. sets the precision after the decimal point, in this case 2. And finally  sets the exponent field size. A value of  signifies that no exponent will be output. Anything other than zero will output the exponent symbol "E", a +/-, and the digit(s) of the exponent. Note: The value of  should not be less than zero!

Let's try a few different combinations:

The output:

0      -17.7778    1       -17.2222   15        -9.4444   16        -8.8889   17        -8.3333   26        -3.3333   27        -2.7778  100        37.7778  101        38.3333  102        38.8889  103        39.4444  104        40.0000  ...

Or how about this:

And the output:

0 -1.78E+1 1 -1.72E+1 2 -1.67E+1 8 -1.33E+1 9 -1.28E+1 10 -1.22E+1 11 -1.17E+1 18 -7.78E+0 37  2.78E+0 98  3.67E+1 99  3.72E+1 100  3.78E+1 101  3.83E+1 ...

And finally:

This outputs:

0-17.8   8-13.3    9-12.8   10-12.2   11-11.7   31-0.6   320.0   499.4   9836.7   9937.2  10037.8  10138.3  10238.9  ...

Which obviously isn't very pretty to look at.

A new line, an exit strategy, and a step
The last four lines of the FtoC program finish our formatting, get us out of here if we are done, and, if not, set the next value of  to be converted:

The single call to  is the reason we have to make   available to the FtoC program using. What  does is output a single line feed. Running the program without this call to  would result in output looking like this:

0 -17.78  1 -17.22   2 -16.67   3 -16.11   4 -15.56   5 -15.00   6 -14.44   7 -13.89   8 -13.33   9 -12.78  10 -12.22  11 -11.67 ...

The  procedure accepts a   parameter, meaning you can do this to output consecutive line feeds:

Or simply

We've already discussed the  method of terminating a loop, and the final statement is merely a simple counter. The value of  is incremented with   on each iteration of the loop. When  equals , the loop is terminated.

The final line,, signifies the end of the program. There's nothing more to do and nothing more to see. Control is handed back to whatever called the program in the first place, and life goes on.

Conclusion
I hope you've enjoyed this little tutorial on building a Fahrenheit to Celsius conversion table. It is left to the reader to figure out how to add Kelvin to the mix. Have fun!

Wikibook

 * Ada Programming/Input Output