Some Fortran string tips

it can get a little confused with strings in Fortran. For example, consider the following piece of code:

program str
   implicit none
   character (len=20) :: s1, s2

   s1 = "Obi-Wan"
   s2 = 'Kenobi'

   print*, s1, " ", s2

end program str

The string s1 is created with the first seven elements taking up the characters “Obi-Wan“, and the remaining 13 elements filled with trailing spaces. Here either single or double quotes can be used to designate a string – although the best way is to choose one and stick with it, just for the sake of consistency. Functionally there is no difference.

Now if you want to derive the length of a string, you can use a function called len_trim(), which calculates the length of a string minus trailing blanks. So the length of s1, len_trim(s1), would be 7. If you just use plain old len(), then the value returned would be 20, the size of the string.

String concatenation can be done using the // operator. For example (assuming s3 is the same string as s1 and s2):

s3 = s1 // s2

The problem here is that the answer would just be “Obi-Wan”. Why? Because s3 is only 20 characters in length, and s1 alone is 20 characters, so there is no room for s2. The solution is to trim away the trailing blanks:

s3 = trim(s1) // trim(s2)

Then the solution is Obi-WanKenobi. Finally, let’s consider strings that are built character by character. For example the string s4, declared just like s1. Now a loop is used to read four characters from standard input, and assign them to the first four elements of the string.

 do i = 1,4
    read*, s4(i:i)
 end do
 print*, s4, len_trim(s4)

Now when this code is run, and the characters l, e, n, and s are input, the following output is produced:

lens M�l��          20

Obviously there is a problem here, and it relates to the fact that the trailing characters have not been filled with spaces, but rather garbage. But the solution is easy, just add, s4="" before the loop. Note that this is not an issue if the string is input whole.

More 2D strings in Fortran

Arrays of strings are easy to do. For example the following declaration produces an array of strings, where there are 5 strings, each of 10 characters in length:

character(len=12), dimension(4) :: a

This could also be written as:

character(len=12) :: a(4)

However the first declaration is the one that should be used, the second declaration is associated with old versions of Fortran, and should be avoided. Here is a program which deals with some strings, and prints out various things.

program strarr
   implicit none

   character(len=12), dimension(4) :: a
   a(1) = "Mandalorian"
   a(2) = "BobaFett"
   a(3) = "CaraDune"
   a(4) = "IG-11"

   print*, a
   print*, a(2)(5:8)
end program strarr

You will notice there are two types of indices: one for the index of the array, and another for the position of the character. These two indices are specified separately, with the index first, and the position of the character in the string second. So in the example a(2)(5:8) means array element 2, string indices 5 to 8. Note that because this is a string and not a character array, to access a single element you have to use a range in the for x:x. Here is the output from the program above.

 Mandalorian BobaFett    CaraDune    IG-11
 Fett

The strings in the array could also be allocated, so that the storage for each string in the array is variable length. Here is a program that uses an array of allocatable strings.

program arrvarstr
   implicit none

   type :: varl
      character(len=:), allocatable :: name
   end type varl

   type(varl), dimension(4) :: a(4)
   integer :: i

   allocate(character(len=11) :: a(1)%name)
   a(1)%name = "Mandalorian"
   allocate(character(len=8) :: a(2)%name)
   a(2)%name = "BobaFett"
   allocate(character(len=8) :: a(3)%name)
   a(3)%name = "CaraDune"
   allocate(character(len=5) :: a(4)%name)
   a(4)%name = "IG-11"

   do i = 1,4
      print*, a(i)%name
   end do
   print*, a(2)%name(5:8)

end program arrvarstr

Here is the output:

 Mandalorian
 BobaFett
 CaraDune
 IG-11
 Fett

Finally, there a variable length array of (static) strings. Here is an example of a variable length array of strings of size 20. The length of the array of strings is allocated to 40.

program arrvarlen

   implicit none

   character(len=20), dimension(:), allocatable :: c
   allocate(c(40))

   c(1) = "do"
   c(2) = "or"
   c(3) = "do not"

   c = [c, "there is no try"]

   print*, c
   deallocate(c)

end program arrvarlen

Note there is also an interesting piece of code on Line 12. Here a new string is appended to the next position in the array (in this case element 4). Here is the output from the program:

 do                  or                  do not              there is no try

Calculating wind chill in Fortran

As temperatures fall and the wind howls, we begin hearing about the danger of “wind chill”. The “wind chill” factor W is reported by meteorologists during winter. W is an equivalent temperature that accounts for the increased chilling effects of wind on a human body. The wind chill factor combines the temperature and wind speed to tell you how cold the wind makes it “feel”. The coldest wind chill recorded in Canada was at Pelly Bay, Nunavut, on January 13, 1975, when 56 km/h winds (a wind chill factor of 3,357 watts/m²) made the temperature of -51°C feel more like -92°C.

The first wind chill formula and associated tables were based on research conducted by scientists Paul A. Siple and Charles F. Passel in Antarctica in the 1940s. The research found that the rate at which water freezes depends on three factors: how warm it was to begin with, the outside temperature and the wind speed. This Wind Chill Index was a 3-4 digit number with units of kilocalories per square metre per hour (kcal/m2/hr). For example 1400 was the threshold for frostbite. The original Siple-Passel equation for wind chill factor (°C) is:

C = (10.0√V - V + 10.5)(33-Ta)

where V is the wind velocity (m/s), and Ta was the air temperature (°C). One of the problems with this formula was that the units were not very user-friendly for the general public. In 2001 a new formula was derived , based on a model of how fast a human face loses heat and incorporates modern heat transfer theory, that is, the theory of how much heat is lost by the body to its surroundings during cold and windy days. (The face is the part of the body most often exposed to severe winter weather). It must be noted that although the wind chill factor is expressed on a temperature scale (the Celsius scale in Canada), it is not a temperature: it only expresses a human sensation. There are two formulas used by Environment Canada:

W = 13.12 + 0.6215Tair - 11.37V(10m)0.16 + 0.3965Tair V(10m)0.16
W = Tair + [(-1.59 + 0.1345Tair)/5]V(10m)

where W is the wind chill factor based on the Celsius temperature scale, Tair is the air temperature in degrees celsius, and V10m is the wind speed in km/h at 10 meters. The first equation is used when the temperature of the air is ≤ 0°C and the wind speed is ≥ 5km/h. Then second equation is used when the temperature of the air is ≤ 0°C and the wind speed is > 0km/h, but < 5km/h.

Here is the Fortran program to perform the calculation:

program wind_chill

    real :: airTemp, windS, windCF

    ! Obtain the user input
    write (*,*) 'Air temperature (Celsius): '
    read (*,*) airTemp
    write (*,*) 'Wind speed (km/hr): '
    read (*,*) windS

    if (airTemp <= 0.0) then
        if (windS >= 5.0) then
            windCF = 13.12 + 0.6215*airTemp - 11.37*windS**0.16 + &
                     0.3965*airTemp*windS**0.16;
        else if ((windS > 0.0) .and. (windS < 5.0)) then
            windCF = airTemp + ((-1.59+0.1345*airTemp)/5.0)*windS;
        else
            write (*,*) 'There is no wind!'
        end if
        write (*,100) windCF
        100 format ('The temperature feels like ', F7.2, ' degrees Celsius')
    else
        write (*,*) 'Unable to calculate - the air temperature is too high'
    end if

end program wind_chill

The program is inherently easy to write in Fortran. Here’s the program running:

Air temperature (Celsius):
-3
Wind speed (km/hr):
15
The temperature feels like   -8.12 degrees Celsius

One of the problems in the world is a lack of understanding about math

Everyday we are bombarded with information. Some of it is visual, some verbal, and some mathematical in nature. But due to a real lack of understanding about math, some people born after Gen X, likely don’t quite understand things like statistics, and hence tend to ignore them. Don’t believe me? Then take a look at some stats. The Conference Board of Canada says that 55% of Canadian adults have inadequate numeracy skills, and that overall Canada earns a grade of “C”. Countries that achieve an “A” include Japan and Finland, a “B”, Germany, Sweden, etc. I mean we get the same grade for problem solving skills and literacy. Not a really great outlook.

It’s likely because of this that people don’t understand the math they see on tv or the internet. It’s actually probably one of the reasons inflation has been such a shock to people. I mean the current inflation rate is 6.9%, which historically is not as high as they seen between 1977 and 1983 when it was 12.9%. But I digress, let’s look at some simple math.

Let’s talk about coffee. Let’s say you like to drink a Cortado, and it costs $4.00. If a student buys just one coffee a day it would cost them $5 after tax and tip. If they only have one coffee each weekday, that’s $20 a week, or $1,040 a year. Not chump change. If they average one coffee a day, that’s $1,825. Add a baked treat in and it might add another $4, so $9 per day. By itself it seems like a small amount, but added up, it’s a lot of money. A basic lack of understanding about quantities means that it is hard to extrapolate out, $5 doesn’t seem like much until it is viewed as a yearly cost. It is no different with other items. Take milk for example – if you buy two 2-litre cartons of milk a week, at $5 each, that means you spend $520 a year just on milk. It is hard to budget if you don’t really understand math.

If you can’t understand basic milk-math, then things like mortgages become even more challenging. Let’s look at the world of increasing mortgages. If someone took out a 25-year mortgage for $500,000 in 2020, with a fixed 3-year term, they would have paid 3.94%. Doing the calculations on this isn’t even that challenging, because there are an abundance of calculators – but you have to be able to decipher the calculations. A 3-year term means that the 3.94% holds for the term period, after which it might go up or down. If payments are made monthly, there are 36 payments during this period.

Mortgage payment per month: $2,613.86
Principal payment: $37,577.11
Interest payment: $56,521.97

So after the three years, the principal will have reduced to $462,422.89. If the interest rate were to stay the same, over the course of the 25 years the principal would have been paid back, and $284,158.99 would have been paid in interest. So the entire loan would have cost $784,158.99. None of this is challenging math, but if you don’t understand yearly coffee costs, this will be challenging. However as prime interest rates go up, so do bank rates. That same 3-year rate is now 6.04%, which means people are going to pay more every month. But for some reason it’s a shock to people. Monthly costs for the next 3-year term are now somewhere in the vicinity of $3,149.24 (on the remaining principal). To add to that, if the interest rate goes up, more of the monthly payment goes towards the interest, and less to the principal. It’s basic math, but so many people seem to struggle with it.

The classic example is on those TV shows where fledgling entrepreneurs go to get an investment. A lack of understanding about numbers, and a certain amount of ego makes some people believe their company is worth way more than it actually is. Classic examples are people who ask for $300,000 for 5% of their business, valuing the company at a cool $6 million. Then they get asked what their sales were in the past year and they say $50,000. So they are asking for an investment in a company that has sold little yet is deemed to be worth a lot. Now if we calculate the price/sales multiple, we get $6M/$50k, or 120 times last years sales. Very few companies are going to sell $50k of products one year and $3M the next. Of course some people want investments based on future potential, which in most cases isn’t at all realistic. But the point here is that people always think they can ask for a lot of money, and that’s partly because they really just don’t understand the math.

Many Canadian students have mediocre math skills, so it’s no wonder they don’t understand the most basic calculations. Why are these students passing through the elementary system with such poor skills? Part of the reason is the use of discovery or experiential learning, whereby students are encourage to explore various ways to solve a problem. The reality, is that these approaches just don’t work for the particular problems they are experiencing, because their working memories get overwhelmed, and as a result they don’t have the basic tools to solve more complex problems in an easy way. I’m talking in part about times tables. They should be rote-learned, because they provide the basic knowledge to easily solve more complex problems.

Sure rote-learning is tedious, but what happens now is 4×6 becomes 4+4+4+4+4+4, which is inherently inefficient. Worse is when students use their fingers to work out a basic math problem. Traditional math teaching worked – ask the generations of people who could do math in their heads. Imprinting the 12 times tables in long-term memory is important – using other techniques likely means the problem has to solved from scratch every time. The other problem of course is that some teachers teaching math cannot actually do math themselves.

Computing power is too often wasted

I learned to program in the mid-80’s, when computer power was still somewhat scarce. The CS department I was in had a Pyramid 90x, a 32-bit processor running at 8 MHz with a max of 4×1MB memory boards. That machine could support up to 128 users. It beat the pants off the universities DEC System-20, running TOPS-20 operating system – now that was a ghastly system to use. On the Pyramid, like most machines, you had to code things efficiently or they just wouldn’t run in the proper amount of time, or at all if they used up too many resources. As machines got faster, and memory got cheaper we didn’t really evolve to use these thing efficiently, software development just evolved to use up the available resources. It’s kind of like buying a new iPhone… you always debate what size memory to get, because if you know about computing, you know the system and apps will just gobble up the resources.

Part of this stems from an inability to create lean software, well perhaps unless you work for NASA where there isn’t always the luxury of having unlimited resources to send into space. In 50 years time, computers will be faster, and we will suffer from the same gluttonous use of resources. It’s a bit like that in other aspects of people lives… cars, houses etc. They always have to be bigger, using up as many resources as possible. It’s no different with programming languages. Languages like Fortran, and C are probably considered by many to be quite lean, when juxtaposed against the likes of Python and Julia. Some languages contain everything but the kitchen sink, and it is these things that make them somewhat unwieldy. Some have argued over the years that C could use a string type, but why bother when in reality strings are just a subset of arrays.

Wasted computing power ultimately has an impact on the environment. The carbon footprint of technology is something which it seems few people even bother to think about. It turns out that modern computing accounts for 2-4% of global greenhouse gas emissions, more than the aviation sector. In 2020, data centers accounted for 1% of global electricity demand, and it’s no wonder. Data centres generally still use HDD to store data, and as equipment gets hot, it needs to be cooled. Cooling is less of an issue in northern Canada or Norway, but a big issue in warm climes. Just as well we’ll soon have guilt-free fusion right?

One has to question how much of an impact poor, inefficiently designed code has had on things like the environment? I think some of the issues stem from not really teaching students about efficient programming practices anymore. People love to talk about how cows impact climate change, but never once bother to consider how their tech-junkie habits contribute to a crappier environment.

Recursion: Permutations by interchange

One method of generating permutations is by employing recursion with an interchange method. The program below is loosely based on Heap’s algorithm [1] which generates all possible permutations of n objects using recursion. The program is written in Fortran.

recursive subroutine genPermutation(str,i,n)
   character(len=4), intent(inout) :: str
   integer, intent(in) :: i, n
   integer :: j

   if (i == n) then
      print*,  str
   else
      do j = i,n
         call swap(str,i,j)
         call genPermutation(str,i+1,n)
         call swap(str,i,j)
      end do
   end if
end subroutine genPermutation

The input parameters to the subroutine genPermutation() are:

  • str – the input string used to generate the permutations.
  • i – the index of the element to use (when the subroutine is first called, this i=1).
  • n – the length of the string.

How does this work? Let’s run through an example, using the input string “tea“.

genPermutation("tea", 1, 3)
j = 1
   swap("tea",1,1) -> "tea"
   genPermutation("tea", 2, 3)
      j = 2
         swap("tea",2,2) -> "tea"
         genPermutation("tea", 3, 3) => print("tea")
         swap("tea",2,2) -> "tea"
      j = 3
         swap("tea",2,3) -> "tae"
         genPermutation("tae", 3, 3) => print("tae")
         swap("tae",2,2) -> "tea"  
   swap("tea",1,1) -> "tea"  
j = 2
   swap("tea",1,2) -> "eta"
   genPermutation("eta", 2, 3)
      j == 2
         swap("eta",2,2) -> "eta"
         genPermutation("eta", 3, 3) => print("eta")
         swap("tea",2,2) -> "eta"
      j = 3
         swap("eta",2,3) -> "eat"
         genPermutation("eat", 3, 3) => print("eat")
         swap("eat",2,2) -> "eta"  
   swap("eta",1,2) -> "tea"       
j = 3
   swap("tea",1,2) -> "aet"
   genPermutation("aet", 2, 3)
      j = 2
         swap("aet",2,2) -> "aet"
         genPermutation("aet", 3, 3) => print("aet")
         swap("aet",2,2) -> "ate"
      j = 3
         swap("ate",2,3) -> "ate"
         genPermutation("ate", 3, 3) => print("ate")
         swap("ate",2,2) -> "aet"  
   swap("aet",1,3) -> "tea"    

  1. Heap, B.R., “Permutations by interchanges”, The Computer Journal, 6(3), pp.293-298 (1963)

As AI gets smarter, humans get more stupid

Every week there is some new AI thing introduced. The latest is ChatGPT, which people are worried is going to be used by students to write essays. Oh, let’s face it, it probably is – but those essays will be easy to spot because they will be too well written, if the software is any good that is. Why? Because the reality is that most people in highschool or post-secondary suck at writing. I’m not saying that to be mean, it’s a reality. They have few if any life experiences, and very little experience at writing, so of course their work will not exactly win a Pulitzer Prize. Yes, there will be people with exceptional writing, but those people love to write, and would never use an AI to cheat at writing. People who take the humanities seriously won’t use AI either.

But people who use these shortcuts are just cheating – and they are mostly cheating themselves. Cheating themselves from actually learning something instead of letting yet another piece of technology do something for them. There are too many people looking for shortcuts because they can’t handle the work put in front of them. It’s no different in computer science – people who think they can use shortcuts to get things done, but eventually realize they have little or no ability to solve problems and the best they can do programming-wise is HTML (and that’s not even programming).

The saviour might be other software that can detect the use of AI. I mean it’s not super hard. Writing generated by AI will conform to a particular style. There won’t be any form of individualism in the junk these things pump out. After four years in a history program, a person will develop a writing style, something unique to them. That’s the point of writing – to develop the ability to resonate your ideas – to inform, persuade, explain or entertain. AI can’t write based on the intrinsic experience of humans. AI works by formulating tasks as problems based on prediction, and then uses statistical techniques and a profusion of data to make predictions. Good for small bits of writing, not really that great for long-form coherent, interesting text.

The thing with AI is that it is not sentient. It has no clue about the context of the essays it is producing. AI does it’s job by using billions of pieces of data, most of it human generated data. So it writes essays based on a plethora of digital data from many sources… but it’s undoing is the fact that not all the world’s information is digital. There is a lot of information, in many differing languages, from many time periods, that has not been digitized. It also can’t include personal experiences because it doesn’t have them – it’s only an algorithm.

Algorithms still can’t craft a narrative the way a person can, and maybe they never will, which in my book will be a good thing for humans. Because if we can’t document our own experiences, can’t express ourselves in words, we loose one of the characteristics of being human. If you rely on a machine to do your writing, then you are loosing a means of communication. While technology is said to promise society so much, we must be mindful of the cost to humanity. technology was meant to help serve people, yet we increasingly find ourselves subservient to it, and many people don’t even realize it. People have become lazy, their attention fading quickly, their lives reduced to a 6-inch screen and a streaming service. Is it even possible for many to think outside the pale, glowing box?

Translating an Algol-60 program to Fortran

This post deals with the simple task of translating an Algol-60 program to Fortran, and some simple tricks which can be used. The program in question calculates e to many digits.

There are many algorithms for calculating numbers like π and e. Normally e is calculated using the infinite series. These series often offer fast convergence, and are easy to calculate, however the problem is that computers are generally unable to accommodate more than 10-20 significant digits. For generating an accurate value for e, one has to turn to an alternative algorithm. One such algorithm is provided by Sale [1] in Algol-60 (or thereabouts). As there are no Algol-60 compilers out there, it is then a task of converting the program to another language. Here we have chosen Fortran, for reasons which will become clear as we progress through the discussion.

The first thing someone will say is that they don’t know Algol-60. While I’m sure that’s true of most people, if you have a basic clue about control structures in programs, and can read a program, deciphering what goes on here should not be that hard. The first thing to do is come reverse engineering. You have to understand what the algorithm embedded in the Algol-60 code does.

The first thing you might notice about this code is the fact that it allocates dynamic-type arrays within the code. This was not that unusual for Algol-type languages. Inputs to the procedure are n and d: where n is the number of decimal places to be calculated, and d is the array used to store them in. The use of the keyword loop does not in fact indicate a loop, but rather a label for that particular line. The if statement on the next line, if true, uses a goto to “loop” back to loop. In the Fortran this has been replaced by a real do while loop.

m := 4;
test := (n + 1) × 2.30258509;
loop: m := m + 1;
if m × (ln(m)-1.0)+0.5 × ln(6.2831852) × m)
   ≤ test then go to loop;
m = 4
test = (n+1) * 2.30258509
do while (m * (log(m*1.0)-1.0) + 0.5 *     
               log(6.2831852 * m) <= test)
   m = m + 1
end do

You will notice that variables are created when they are needed in the original program. As Fortran does not work in the same manner, these variables will be moved to the declaration block at the top of the subroutine. Here the value of m calculated in the previous code is used to allocate storage for the array coef. Fortran will allow allocate statements anywhere in a program. The loop to set the elements of coef to 1 is replaced with a simple statement in Fortran. The two do loops in Fortran fairly well mimic the for loops in Algol-60 program.

begin integer i,j,carry,temp
  integer array coed[2:m];
  for l:=2 step 1 until m do coef[j]:=1;
  d[0] := 2;
  sweep: for i:=1 step 1 until n do begin
    carry := 0;
    for j:=m step -1 until 2 do begin
      temp := coef[j] × 10 + carry;
      carry := temp ÷ j;
      coef[j] := temp - carry × j
    end
    d[i] := carry
  end
end
allocate(coef(2:m))
coef(2:m) = 1
d(0) = 2;
do i = 1, n
   carry = 0
   do j = m,2,-1
      temp = coef(j) * 10 + carry;
      carry = temp / j
      coef(j) = temp - carry * j
   end do
   d(i) = carry
end do

Now we can wrap these code segments inside a subroutine:

procedure ecalculation(n,d);
value n;
integer n;
integer array d;
begin integer m;
real test;

comment Add code segments 1 and 2

end of ecalculation;

subroutine ecalculation(n,d)
integer, intent(in) :: n
integer, allocatable, dimension(:), intent(out):: d
integer :: m, i, j, carry, temp
integer, allocatable, dimension(:) :: coef
real :: test

allocate(d(0:n))

! Add code segments 1 and 2

end subroutine ecalculation

And finally add this subroutine into a Fortran main program wrapper:

program ecalc
   implicit none

   integer :: i,num
   integer, allocatable, dimension(:) :: eArr

   print*, "Number of digits?"
   read (*,*) num

   call ecalculation(num,eArr)
   print*, "e (actual) = 2.7182818284590452353602874"
   write(*,fmt="(a15)", advance="no") "e (calc)   = 2."
   do i = 1,num
      write(*,fmt="(i1)", advance="no") eArr(i)
   end do
   write(*,*)

contains

   ! Add subroutine ecalculation

end program ecalc

Now we can compile the program and run a test:

 Number of digits?
25
e (actual) = 2.7182818284590452353602874
e (calc)   = 2.7182818284590452353602874

Refs:

  1. Sale, A.H.J., “The calculation of e to many significant digits”, The Computer Journal, 11(2), pp.229-230 (1968)

What is reverse engineering?

Sometimes when you have a program or piece of code that needs reengineering, you first have to perform a task known as reverse engineering (RE). It is basically used to identify the components of a program, and their interrelationships. Reverse engineering generally involves trying to extract design elements, i.e. what a program does, both from the program itself, and any associated documentation. Reverse engineering in itself does not involve changing the program in any manner – it is a process of examination.

To perform reverse engineering, you have to be able to comprehend a program. Even if you don’t fully understand the language, you should have the ability to decipher what it does, because the fundamental control structures in any language do not change that much. One area of RE is re-documentation. Re-documentation is the process of gaining enough knowledge of the program to be able to create a written representation of the program. This is helpful because in many cases, documentation is either lacking, or non-existent. This is the simplest form of RE, with the sole goal to recover documentation about a program.

What are we trying to accomplish with reverse engineering?

  • Deal with complexity – Old programs can be complex, and undocumented. By reverse engineering the program, we gain a better understanding of what it does, and how it does it.
  • Recover lost information – The evolution of programs over the years often results in lost information, or poorly documented changes. Reverse engineering allows knowledge about the programs design to be recovered.
  • Determine side effects – Potentially the program could contain a lot of side-effects from the initial design of the system. For example, a program written in Fortran IV could be riddled with unstructured code. This might impede the reengineering of the system.
  • Facilitate reuse – It is possible that components of a legacy system can be reused as-is, for example through the process of encapsulation.

The problem with π (iv) – Fortran to Ada

The Fortran program can also be translated to Ada. Below is the Ada program. It contains some code to output the resulting value of pi to the text file piCalc1000ADA.txt. In this code a is a “dynamic” array of integers, which is allocated using a declare block. In reality a is just of type pia, declared at a later point in the program.

with ada.Text_IO; use Ada.Text_IO;
with ada.Integer_Text_IO; use Ada.Integer_Text_IO;
with ada.strings.unbounded; use ada.strings.unbounded;
with ada.strings.unbounded.Text_IO; use ada.strings.unbounded.Text_IO;

procedure piSpigotDYN is

    n, len : integer;
    q, x, nines, predigit : integer;
    type pia is array(integer range <>) of integer;
    infp : file_type;

begin
    create(infp,out_file,"piCalc1000ADA.txt");
    n := 1000;
    len := 10 * n / 3;

    declare
       a : pia(1..len);
    begin

    a := (1..len => 2);
    nines := 0;
    predigit := 0;

    for j in 1..n loop
       q := 0;
       for i in reverse 1..len loop
          x := 10 * a(i) + q * i;
          a(i) := x mod (2*i-1);
          q := x / (2*i-1);
       end loop;
       a(1) := q mod 10;
       q := q / 10;
       if q = 9 then
          nines := nines + 1;
       elsif q = 10 then
          put(infp,predigit+1,width=>1);
          for k in 1..nines loop
             put(infp,0,width=>1);
          end loop;
          predigit := 0;
          nines := 0;
       else
          put(infp,predigit,width=>1);
          predigit := q;
          if nines /= 0 then
             for k in 1..nines loop
                put(infp,9,width=>1);
                nines := 0;
             end loop;
          end if;
       end if;
    end loop;
    put(infp,predigit,width=>1);

    close(infp);
    end;
end piSpigotDYN;