Using the “Processing” language

Today I stumbled onto a language called Processing. It seems to be an OO language similar to Java in construct (but with a simplified syntax), coupled with an IDE – built for media art and visual design. It appeared in 2001, so it has been around for a while, just flying under the radar I imagine. I downloaded the OSX IDE and it ran without fuss, which was nice. It took a couple of hours to get the hang of how it works, but there seems to be a good amount of functionality. I found Processing because I was looking for a language to implement visual recursion algorithms, such as Hilbert curves. Due to the fact that I do like Julia, I tried to install Luxor, a Julia package that does vector drawing, but the install was *horrible*, and it still didn’t run (this is always my fear when people create packages – they become so overwhelm, I fear installing them). I’ve never like Java, but I don’t mind the language structure of Processing. Below is a program to generate a Hilbert curve.

float turtleangle = 0;
float cx;
float cy;
float length;
 
void setup() {
  size(400, 400);
  cx = width/2;
  cy = height/2;
}
 
void draw() {
  length = 10;
  background(255);
  stroke(0);
  hilbert(4,90);
  noLoop();
}

void hilbert(int level, float angle) {
  if (level == 0)
    return;
  rotation(angle);
  hilbert(level-1,-angle);
  forward(length);
  rotation(-angle);
  hilbert(level-1, angle);
  forward(length);
  hilbert(level-1, angle);
  rotation(-angle);
  forward(length); 
  hilbert(level-1,-angle);
  rotation(angle);
}

void forward(float amount) {
  float newX = cx + cos(radians(turtleangle)) * amount;
  float newY = cy + sin(radians(turtleangle)) * amount;
 
  line(cx, cy, newX, newY);
  fill(0);  
  cx = newX;
  cy = newY;
}

void rotation(float degrees) {
  turtleangle = turtleangle + degrees;
}

The system works by applying the setup() function (sets up the drawing board, in this case a 400×400 board), and draw() functions, which are the standard functions. The function hilbert() generates the curve, using forward() to move a certain distance from a point, and rotation() to rotate a certain amount. The only real problem I had was realizing that draw() needs a call to noLoop(), otherwise it continues running in a loop. Here is the output:

Overall, the experience of programming in Processing was quite good. The only problem is that using Turtle Graphics is super easy, and Processing requires you to write functions to perform some of the moving operations. Next I’m going to try and build something a little more complicated: a sunflower spiral.

P.S. I don’t really like the name. I *get* where they were going, but the term processing is far too generic to use as a programming name (it’s also hard to Google stuff).

 

 

Advertisements

Timing and efficiency in C programs (ii)

TIMING WITH NANO SECONDS

Now a nanosecond is one-billionth of a second. That’s pretty small, and essentially regardless of the system, a version of clock_gettime() can be cobbled together. The library time.h contains a structure timespec, which has the following members:

time_t tv_sec    /* seconds */
long   tv_nsec   /* nanoseconds */

If the system is OSX, then the following code will include the appropriate libraries:

#ifdef __APPLE__
#include <mach/clock.h>
#include <mach/mach.h>
#endif

Then a function can be built to return the time, stored in a timespec structure, using the Mach clock.

void get_Mach_time(struct timespec *ts) {
 
#ifdef __APPLE__ // OS X does not have clock_gettime, use clock_get_time
   clock_serv_t cclock;
   mach_timespec_t mts;
   host_get_clock_service(mach_host_self(), CALENDAR_CLOCK, &cclock);
   clock_get_time(cclock, &mts);
   mach_port_deallocate(mach_task_self(), cclock);
   ts->tv_sec = mts.tv_sec;
   ts->tv_nsec = mts.tv_nsec;
#else
   clock_gettime(CLOCK_REALTIME, ts);
#endif

}

On line 6, the code gets a Mach clock port using the function host_get_clock_service(). The parameter CALENDAR_CLOCK is time since the epoch (1970-01-01). The port is returned in cclock. The function clock_get_time() is then used to get the time, which is returned in the variable mts, a structure which is the same as the timespec struct from time.h. The code on line 8 deallocates the port. The code on lines 9 and 10 assign the seconds and nanoseconds data to the timespec struct ts, which is returned from the function. This is used in the following manner:

get_Mach_time(&ts1);

// code to do something

get_Mach_time(&ts2);

ts1nano = ts1.tv_sec * 1000000000 + ts1.tv_nsec;
ts2nano = ts2.tv_sec * 1000000000 + ts2.tv_nsec;
diffN = ts2nano - ts1nano;

printf("%lu nanoseconds\n", diffN);
printf("%.6lf milliseconds\n", diffN/1000000.0);

The calculates are performed in nanoseconds,  and then output in both nanoseconds and milliseconds.

EXPERIMENTS

Consider some experiments using Ackermann’s function. Calculating ackerman(4,1) using all three methods results in the following:

clock           8565 milliseconds

gettimeofday    8700 milliseconds

get_Mach_time   8593.151 milliseconds

Timing is obviously somewhat different because the algorithm had to be timed separately three times.

Timing and efficiency in C programs (i)

What would algorithms be without time. Efficiency. Sure, there will always be faster machines to run algorithms on, so efficiency may not be that big a deal. Or is it? Certainly resources on a mobile device, or a probe going into space are limited. More resources = more energy requirements = bigger batteries = less room for increased functionality (i.e. higher resolution cameras).

So how do we calculate how fast an algorithm runs? Firstly, what are the properties of the speed calculation – real/user/system time? seconds/milliseconds/microseconds?

THE MOST BASIC TIMING REGIME

The most basic timing uses the time.h library, and the clock_t structure. It allows timing using seconds, and the function clock(), which provides a minimum amount of feedback.

The function clock() returns the sum of user and system time represented as CPU time in cycles, but modern standards require CLOCKS_PER_SEC to be 1000000, giving a maximum possible precision of 1 µs. Here is a sample piece of code:

clock_t start, end;
double timeshift;

start = <strong>clock()</strong>;

// Perform some operation

end = <strong>clock()</strong>;

timeshift = (end - start) / CLOCKS_PER_SEC;

The variable timeshift will contain the timing for the algorithm in seconds. This works okay, but if the time resolution between algorithms is finer, then chances are this will produce the same time. To calculate the value in milliseconds:

timeshift = ((end - start)*1000) / CLOCKS_PER_SEC;

TIMING WITH FINER RESOLUTION

A better measure of timing can be had using the sys/time.h library and the timeval structure. The structure looks like this:

struct {
   int32_t tv_sec;         /* Seconds */
   int32_t tv_usec;        /* Microseconds */
} ut_tv;                   /* Time entry was made */

struct timeval ut_tv;      /* Time entry was made */

The function gettimeofday() returns the current wall clock time.

struct timeval t1, t2;
long sec, msec, tmilli;

gettimeofday(&t1,NULL);

// Perform some operation

gettimeofday(&t2,NULL);

sec = (t2.tv_sec - t1.tv_sec); 
msec = (t2.tv_usec - t1.tv_usec); 

tmilli = (1000 * sec) + (msec * 0.001);

printf("%ld milliseconds\n", tmilli);

This timer has 1 microsecond resolution, where 1,000,000 microseconds = 1 second.

Are there problems with this approach? Yes, some to do with NTP (network time protocol) and the fact that processes on the system can potentially change the timer, making it jump backward and forward in time.

Sometimes it returns a negative value. That’s right, your program was *so* efficient it executed before you even ran it. The problem is that gettimeofday() still can’t really tell the difference between a 2 microseconds and 3 microseconds difference. The problem lies in deriving the code. If the difference in time is derived as above, the code will work as long as the relationship t2.tv_usec > t1.tv_usec holds. However although the relationship t2.tv_sec > t1.tv_sec holds, the same is not true for microseconds. For example consider the following two timestamps:

t1 = 1370282687 434738
t2 = 1370282711 289326

Calculating the differences results in 24 for seconds, and -145412 for microseconds. A better way is to convert both elements of the structure to microseconds, before calculating the difference.

long t1_msec, t2_msec, diff;

t1_msec = (1000000 * t1.tv_sec) + t1.tv_usec; 
t2_msec = (1000000 * t2.tv_sec) + t2.tv_usec; 

diff = t2_msec - t1_msec;

Or, even better, use the function timersub(), also found in sys/time.h. It basically works like this:

timersub(&t2, &t1, &tv);
tMilli = (1000000 * tv.tv_sec + tv.tv_usec) / 1000;
printf("%ld milliseconds\n", tMilli);

The difference is calculated and stored in the structure tv, which can then be output in microseconds. Note that microseconds may be too fine a resolution, however dividing by 1000 gives a measure in milliseconds.

Note that on some systems (Linux, BSD) gettimeofday() has been replaced with clock_gettime() (not OSX).

 

While C is a surfer dude, Ada is Thor

I like to think of C and Ada as two different personalities. C is the surfer dude of programming languages, sitting on the beach, being cool with everything, “Hey, I’ll compile the array that’s out of bounds… no problem”. It has a very easy-going way about things. C thinks using pointers is gnarly, and that you’re not really cool until your program is obfuscated (which really just means that the code is now speaking a different language, like surfer slang). With Surfer C there is never a problem, well, until there is. Programming in C is sometimes like being lured to sea by Sirens, we are bewitched by their song, and then getting caught in a rip, dragged out to sea. Ada on the other hand is the Thor of programming languages. Array out of bounds? Ada will smash down its hammer. It is a different experience. Ada enforces type compatibility which makes sure we don’t use incompatible types.

Now all this has to do with how they were brought up. C is “weakly typed”, and Ada is “strongly typed”, like its predecessors Pascal, Euclid and Modula. If you have never heard these concepts before, let’s take a moment to understand them. Typing, i.e. whether a language is weak or strong, which are somewhat vague terms. The quasi difference between a strongly typed language and a weakly typed one is that a weakly typed one makes conversions between unrelated types implicitly. A strongly typed language, on the other hand, typically disallows implicit conversions between unrelated types. Strong typing means that it is impossible to concatenate a string with a floating-point number. In C, not every type is checked, and so it is considered fairly weakly typed. Consider the following piece of code in Ada:

i : integer;
u, v : float;

v := 5.6;
i := 2;
u := v + i;

This code will fail to compile, resulting in an error message of the form:

typeeg.adb:15:11: invalid operand types for operator "+"
typeeg.adb:15:11: left operand has type "Standard.Float"
typeeg.adb:15:11: right operand has type "Standard.Integer"

To fix this problem, you actually have to explicitly convert i, i.e. float(i). Ada will not do an implicit type conversion from integer to float, C on the other hand will do an implicit conversion. Fussy? Maybe, but it does lead to fewer inadvertent mathematical errors. Conversely, consider this convoluted piece of code in C:

int x = 0;
void *v = &x;
char *c = v;

printf("=%c=\n", *c);

Code it, run it. The result is interesting. Not the sort of code you really want people to create. (You cannot create a void variable, but you *can* create a pointer to void).

It’s also about the job they do. C is a systems programming language, which is often used to build things like embedded systems, or even compilers for other programming languages. C is considered by some to be a WYSISWYG language – looking at a C program gives a good indication of what the lower level combined code will look like. That’s why C is such a good language for lower-level software that interacts with hardware. On the other hand, it is not really known how well such languages scale to large systems (we develop large systems, but who has ever evaluated the software development process for  programs 50 million lines long?). In addition, C’s focus is on efficiency, and as such it sacrifices checks that other languages make – reliability, safety and security can sometimes be compromised. Ada on the other hand was designed for embedded systems, multi-tasking, and dealing with real-time programming. For instance in the aerospace, or transport industries – driverless trains anyone?

People will argue that C is better because it is, well, C! Ada is too unwieldy. But I know I would prefer to drive in a car/plane/train built in Ada over one built in C (well, I would be okay if the programmers were truly experts), but better C than C++, or heaven forbid Java.

 

Coding Cobol: dynamic filenames

So how do we prompt for a filename in Cobol?

identification division.
program-id. fileio.

environment division.
input-output section.
file-control.
select ifile assign to dynamic ws-fname.

data division.
file section.
fd ifile
   record contains 88 characters.
01 student-info.
   05 student-name occurs 4 times.
      10 stdnt-name pic x(15).
      10 stdnt-idno pic x(7).

working-storage section.
01 i pic 9.
77 ws-fname pic x(30).

procedure division.

   display "Filename containing book information? ".
   accept ws-fname.

   open input ifile.
      read ifile
   end-read.
   close ifile.

   move 1 to i.
   perform print-out until i is greater than 4.
   stop run.

print-out.
   display "Student name is " stdnt-name(i).
   add 1 to i.

Here’s what a potential data file has in it (Each record has 22 elements, 15 for the student name, and 7 for the student number):

Skywalker      6543287Ackbar         1189283Chewbacca      9882870Palpatine      0000001

One line of code in the environment division specifies the dynamic filename:

select ifile assign to dynamic ws-fname.

The output from this program is:

Student name is Skywalker
Student name is Ackbar
Student name is Chewbacca
Student name is Palpatine

 

C language: The good, the badly, and the ugly

C is a language used by many a programmer, and it is an interesting language. But here’s the thing, it was never designed to teach people how to program. It’s a great language for people who have a basic understanding of programming principles, but for a novice programmer, it just doesn’t make the grade. Let’s explore why.

The Good

Let’s start off with the good. C only uses 32 reserved keywords, which is extremely good from the perspective of learning the entirety of the language. On the other end of the scale, Java has 50, and C++ has the 32 keywords of C, with an additional 30 keywords, for a total of 62. So, C has brevity on its side, which is good. C also allows for access to low-level programming, some have likened it to “assembler with steroids”. This is a double edged sword, because it makes the language powerful, but it also makes it unwieldy for a novice programmer. It is also a very powerful language, and that is best illustrated by the fact that a number of compilers for other languages are written in C. However  Python has 33 keywords and is inherently easier to deal with.

C is also a pure language, unlike many other languages are multi-paradigm, or use concepts such as OO. Not that OO is bad, it’s just a real distraction to have to deal with concepts such as inheritance and polymorphism. However if you learn C, it is easier to transition to languages with a similar structure, such as C++, and even Java. Other good things? I have to strain to think of some… for the novice anyway.

The Bad

For the novice programmer, who knows very little about the intricacies of memory, C can be a horrible language to learn to program in. This manifests itself in the first time a programmer writes a scanf() statement. Consider this piece of code:

int nmbr;
scanf("%d", &nmbr);

To a veteran programmer, this is not an issue. To a novice, they are forced to understand that the ampersand character in the clause &nmbr is used to store the value input by the user from the keyword in the memory position associated with the integer variable nmbr. Quite a daunting task. Failure to add the & will result in a problem int he form of a Segmentation fault: 11. But the compiler *will* compile the code, giving the programmer the false sense of security by thinking that their code is okay. This could not be further from the truth. Now the novice programmer has to try and figure out what a Segmentation fault: 11 means.

This reliance on memory does not go away. It forces itself upon the programmer again in the guise of pointers in the context of function parameters, and dynamic memory if a large amount of space is need to store some piece of data. It can also inadvertently appear in the form of insufficient memory associated with the stack. You have to understand memory to program in C, it’s unavoidable. In addition, C allows programs with errors to run. Consider the following code:

int a = 0;
if (a = 1)
   printf("valid");

C allows assignment statements within an if conditional, so the fact that a is set to equal 1, means that the condition will always be true, regardless of what a‘s value is coming into the if statement. There are also deep issues with a failure to check array bounds, and overwriting memory.

The UGLy

Ugliness in C manifests itself in the compactness of the language. Firstly through the use of compact operators such as ++, ––, &&, ||, += etc. These operators likely made a lot of sense when C was first introduced, because they likely helped reduce machine instructions (many were first used in preceding language such as CPL, BCPL, B, etc). However to the novice programmer, the i=i+1 is inherently easier to write than i++, because it is understandable (once they get past i=i+1 as a programming statement, not a mathematical one).  The use of operators such as ++ serve to confuse the novice programmer, because in certain contexts such as:

int p = 4;
p++;
printf("%d", p);

The value of p is 5, whether or not p++ or ++p is used. However used in this context:

int p, x;
p = 4;
x = p++;

The value assigned to x is 4. Change p++ to ++p, and the value of x becomes 5. This is because p++ means “use the value of p, then increment p by 1, and ++p means “increment p by 1, and then use its value”. Subtle, yet bound to make an algorithm fail. The novice programmer doesn’t need this (and I would argue the rest of us don’t either). There are similar issues using && for and, etc.

There is also things like the way arrays are indexed. The most common way is something like a[x], where x is the index. However in C it is also possible to write x[a], which is super confusing for the novice. This is because a[x] means *(a+x) and x[a] means *(x+a).

Ugly? Yes.

Oh, and some control structures that have somewhat obtuse structures (e.g. switch), or lack consistency, such as the while and do-while loops. The while loop does not require enclosing parentheses, unless there is more than one statement. The do-while loop does require parentheses. Lack of consistency causes recall issues amongst novice programmers.

The BORING

I add this last category, because frankly, C is a boring language. There are things that can be done with C, but it takes a good amount of experience with the language. Starting with the bare bones language, there is nothing inspiring which can be done. No graphics, no processing images, no fun stuff. So it is inherently hard to motivate novice programmers, when the pinnacle of fun is outputting Factorials, or a “cool” sorting algorithm.

Coding Cobol: Basic re-engineering

The challenging part of re-engineering Cobol programs is to identify structures in need of being “updated”, or removed in the case of redundant features. This section deals with some of these issues, although it by no means covers everything.

Add end-if

All conditional statements which still use the next clause, or implicit terminators like a period should be transformed so that they are terminated by the explicit terminator end-if. This makes the code more consistent and removes redundant instructions (e.g. next). For example:

para-1.
   if x > 0 
      go para-2
   else
      next sentence.
   display ‘x’.
para-2.

is converted in the following manner:

para-1.
   if x > 0 
      go para-2
   end-if.
   display ‘x’.
para-2.

eliminating goto

Eliminate jump instructions to reduce the amount of unstructured spaghetti-code. For example:

para-1.
   if x > 0 
      go to para-2
   end-if.
   display ‘x’.
para-2.

would be re-engineered to look like:

para-1.
   if x > 0 
      continue
   else
      display ‘x’
   end-if.
para-2.

Note that the role of the continue statement is to prevent empty portions of the if statement. This can be taken a step further in the next section, which removes code containing continue statements that are not necessary.

Eliminate unnecessary continue

Eliminate continue instructions that are deemed not necessary. For example:

para-1.
   if x > 0 
      continue
   else
      display ‘x’
   end-if.
para-2.

could be modified by changing the conditional:

para-1.
   if not x > 0 
      display ‘x’
   end-if.
para-2.

Sometimes it is easier to add a continue statement during earlier processing, and remove the redundant ones later as a post-processing step.

Restructuring while

In dialects of Cobol 74 there was no while construct available, so it was often simulated using a go to. This is a loop in Cobol 74:

loop-one.
   if expr 
      ...
      go to loop-one
   end-if.

And this is the equivalent loop in Cobol 85:

loop-one.
   perform until not expr 
      ...
   end-perform.

 

 

 

 

 

Coding Cobol: A program with loops (and an array)

To illustrate some loops (and arrays) in Cobol, let’s look at a Bubblesort algorithm in Cobol (although Cobol has a rich built-in sorting functionality). This program prompts the user for the number of items to enter, inputs them, and sorts the list.

identification division.
program-id. bubblesort.

data division.
working-storage section.
01 n            pic 99.
01 array.
   02 unsortedA pic s9(3) occurs 100 times.
01 temp         pic s9(3) value 00.
01 i            pic 99.
01 j            pic 99.
01 jp1          pic 99.

procedure division.

   display "How many numbers to sort? ".
   accept n.
   move 1 to i.
   display "Enter ", n, " numbers: "
   perform until i > n
      display i
      accept unsortedA(i)
      add 1 to i
   end-perform.

   compute i = n - 1.
   perform until i < 1
      move 1 to j
      perform until j > i
         compute jp1 = j + 1
         if (unsortedA(j) > unsortedA(jp1))
            move unsortedA(j) to temp
            move unsortedA(jp1) to unsortedA(j)
            move temp to unsortedA(jp1)
         end-if
         add 1 to j giving j
      end-perform
      subtract 1 from i giving i
   end-perform.

   move 1 to i.
   perform until i > n
      display i ":=" unsortedA(i)
      add 1 to i
   end-perform.
   stop run.

The first thing you will notice is the array declaration:

01 array. 
   02 unsortedA pic s9(3) occurs 100 times.

“Arrays”, such as they are in Cobol cannot be created using a level 01 specification. In this declaration the array as a complete entity is created as a level 01, and it’s element is a level 02, to hold 100 numbers, 3 digits in length (try and store 9761 here and it will truncate it). The main part of the program just performs a Bubblesort, what we are interested in here is how the loops function. In Cobol you won’t find any fancy words like for, while or loop. Loops are pure weirdness (but then again from a pure usability viewpoint, the words perform until, are very descriptive, more so that for). Here is the loop for the user to input n integers:

move 1 to i. 
display "Enter ", n, " numbers: " 
perform until i > n 
   display i 
   accept unsortedA(i) 
   add 1 to i 
end-perform.

Now because of the structure, the loop control variable i has to be initialized before the loop begins, and incremented within the loop. In this case the loop uses “perform until“, which provides for an exit condition (rather than “do this while true“). The nested loop just uses two perform “loops”… nothing untoward here.

In fact there is only one real trick in this program. The comparison in a Bubblesort is normally written like this:

 if (unsortedA(j) > unsortedA(j+1))

But, Cobol won’t allow this. It wants to see a single index, and j+1 just doesn’t cut it. So that is why the code instead calculates this value prior to using it:

compute jp1 = j + 1 
if (unsortedA(j) > unsortedA(jp1))

Some languages just do things differently. Now there are other loops in Cobol:

  • perform thru – executes a series of paragraphs.
  • perform until – perform until the condition becomes true.
  • perform times – perform a paragraph a number of times.
  • perform varying – perform a paragraph till the condition in the until phrase becomes true.

 

Coding Cobol: An alternate program

In the previous post, we looked at a program with calculated the type of a triangle. Now let’s look at a similar program which takes a filename as input, removing the restriction of the hard-coded filename. There are also some other subtle changes in how the file is accessed. Here is what the program now looks like:

identification division.
program-id. triangle.

environment division.

input-output section.
file-control.
select input-file assign to dynamic ws-fname
   organization is line sequential.

data division.
file section.
fd input-file.
01 integer-triple.
   03 i           pic 9(5).
   03 j           pic 9(5).
   03 k           pic 9(5).

working-storage section.
77 ws-fname       pic x(30).
77 match          pic 9.
   88 scalene     value 0.
   88 isosceles   value 1.
   88 equilateral value 3.
77 a-message      pic x(100).
77 eof-switch     pic 9 value 1.

procedure division.
   display "Filename? ".
   accept ws-fname.

   open input input-file.

   perform computation until eof-switch = 0.
   close input-file.

   stop run.

computation.
   read input-file into integer-triple
      at end move zero to eof-switch
   end-read.
   if eof-switch is not = 0
      if ((i+j) > k) and ((j+k) > i) and ((k+i) > j) then
         perform triangle
         perform print
      else
         move "Not a triangle" to a-message
         perform print
      end-if
   end-if.

triangle.
   move zeros to match.
   if i is equal to j then
      add 1 to match
   end-if.
   if j is equal to k then
      add 1 to match
   end-if.
   if k is equal to i then
      add 1 to match
   end-if.

   if scalene then
      move "scalene triangle" to a-message
   else
      if isosceles then
         move "isosceles triangle" to a-message
      else
         if equilateral then
            move "equilateral triangle" to a-message
         end-if
      end-if
   end-if.

print.
   display "Sides: ", i, j, k
   display a-message.

The code which deals with the file allows the user to enter a filename through standard input. This requires a modification to the specification in the file control section.

file-control. 
select input-file assign to dynamic ws-fname 
   organization is line sequential.

Here a string, ws-fname, is association with the file handle, which is declared in the working-storage section.

77 ws-fname     pic x(30).

The “main” part of the program is modified, so that the filename can be input, and then the paragraph computation is executed until the end-of-file variable is set to zero. This effectively allows for multiple triangles to be evaluated (one set of data per line).

procedure division. 
   display "Filename? ". 
   accept ws-fname. 

   open input input-file. 
   perform computation until eof-switch = 0. 

   close input-file. 
   stop run.

Inside computation, the file is read, and eof-switch is set to zero when the EOF is encountered.

read input-file into integer-triple 
   at end move zero to eof-switch 
end-read.

The only other change (apart from a couple of new variables), is that after the triangle data is read, if not EOF, then the triangle data is processed.

 

Coding Cobol: What’s with those weird numbers?

In any Cobol program, especially when specifying a file record, or variables in the working-storage section, you will see the use of numbers called level numbers. For example:

01 integer-triple.
   03 i           pic 9(5).
   03 j           pic 9(5).
   03 k           pic 9(5).

77 ws-fname       pic x(30).
77 match          pic 9.
   88 scalene     value 0.
   88 isosceles   value 1.
   88 equilateral value 3.

The level numbers are unsigned 2-digit integers used in the data division of a program. Here’s wht they mean:

  • For independent data items, levels 01 or 77, which can be used interchangeably.
  • For group items (e.g. records), the hierarchical structure can be defined by using 01 for the highest level item, and levels 02 through 49 for subordinate entries.
  • The number 88 is reserved for condition name specification.
  • The number 66 ( renames clause), is used to apply a new name to an identifier or group of identifiers. It is not generally used in modern Cobol programs.

Here’s another example of a group item:

01 mailing-record.
   05 name.
      10 first-name     pic x(30).
      10 last-name      pic x(30).
   05 address-1         pic x(30).
   05 address-2         pic x(30).
   05 city              pic x(30).
   05 province          pic xx.
   05 postal-code       pic x(6).