From ts@uwasa.fi Sat Oct 10 00:00:00 1998
Subject: FAQPAS6.TXT contents

                               Copyright (c) 1993-1998 by Timo Salmi
                                                 All rights reserved

FAQPAS6.TXT The sixth set of frequently (and not so frequently)
asked Turbo Pascal questions with Timo's answers. The items are in
no particular order.

You are free to quote brief passages from this file provided you
clearly indicate the source with a proper acknowledgment.

Comments and corrections are solicited. But if you wish to have
individual Turbo Pascal consultation, please post your questions to
a suitable Usenet newsgroup like news:comp.lang.pascal.borland. It
is much more efficient than asking me by email. I'd like to help,
but I am very pressed for time. I prefer to pick the questions I
answer from the Usenet news. Thus I can answer publicly at one go if
I happen to have an answer. Besides, newsgroups have a number of
readers who might know a better or an alternative answer. Don't be
discouraged, though, if you get a reply like this from me. I am
always glad to hear from fellow Turbo Pascal users.

....................................................................
Prof. Timo Salmi   Co-moderator of news:comp.archives.msdos.announce
Moderating at ftp:// & http://garbo.uwasa.fi/ archives 193.166.120.5
Department of Accounting and Business Finance  ; University of Vaasa
mailto:ts@uwasa.fi <http://www.uwasa.fi/~ts/>  ; FIN-65101,  Finland
Spam foiling in effect.  My email filter autoresponder will return a
required email password to users not yet in the privileges database.

--------------------------------------------------------------------
126) How do I detect if my program is running under Windows?
127) How do I set errorlevel in Turbo Pascal and test it in a batch?
128) How do I get started with mouse programming?
129) How can I find out if a date is a valid date?
130) How can I write an array to a text file in Turbo Pascal?
131) Where do I get ASCII and scan codes for the different keys?
132) How do I define and use records? How do I give initial values?
133) What real numbers types are there? How do I use them?
134) How do I find out the default status of the compiler switches?
135) Anyone know where I can download Pascal source codes?
136) Is there a bug in Turbo Pascal's set operations?
137) How can I output 1000000 as 1,000,000?
138) How can I write text to the printer from a TP program?
139) How do I get a random number from 1 to 49 instead of 0 to 49?
140) How do I get a list of numbers from 1 to 52 in a random order?
141) How can I disable/re-enable a drive from within a TP program?
142) How can I write a filter for ROT13 encryption in Turbo Pascal?
143) Why does writing the value of r := 104.50; give a false result?
144) What is the quickest way to find a word from an ordered array?
145) How to tell which TP version has been used to compile an .exe?
146) How can I make a directory read-only under TP, and vice versa?
147) Where can I find Turbo Pascal 7.0 compiler on the net?
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:06 1998
Subject: Detecting Windows DOS box

126. *****
 Q: How do I detect if my program is running under Windows?

 A: The following solution is for MS Windows 3.1 and 3.11.
  Uses Dos;
  function WinVersion : word;
  var regs : registers;
  begin
    FillChar (regs, SizeOf(regs), 0);
    regs.ah := $16;
    regs.al := $0A;
    Intr ($2F, regs);
    if (regs.ax = 0) then
      WinVersion := regs.bx
      else WinVersion := 0;
  end;
  {}
  procedure TEST;
  var ver : word;
  begin
    ver := WinVersion;
    If ver = 0 then
      writeln ('The program is not running under Windows')
      else begin
        writeln ('The program is running under Windows version ',
                  Hi(ver), '.', Lo(ver));
      end;
  end; (* test *)
  {}
  begin
    TEST;
  end.

As to whether the DOS program is running under Windows95 test for
Lo(DosVersion) = 7;
   Phil Brutsche comments: "Actually, that's a bad way to detect
Win95.  If, say, you were using PC-DOS 7.0 or (I think) Novell
DOS/OpenDOS 7 this test would return that you were running Win95,
even if you are running PC-DOS 7 on a 8088 w/512k RAM." Phil's
posted code with very minor corrections:
  var regs : registers;
  var major, minor : word;
  begin
    FillChar (regs, SizeOf(regs), 0);
    regs.ax := $1600;
    intr ($2F, regs);
    major := regs.al;
    minor := regs.ah;
    case major of
      $00 : writeln ('Standard Mode Windows 3.x or plain MS-DOS');
      $01, $FF : writeln ('Windows/386 2.x');
      else begin
        if (major = 4) and (minor = 0) then
          writeln ('Windows 95')
        else
          writeln ('Windows ', major, '.', minor);
      end;
    end;
  end.
See ftp://garbo.uwasa.fi/pc/programming/inter59c.zip INTERRUP.J for
more.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:07 1998
Subject: Errorlevel setting and use

127. *****
 Q: How do I set errorlevel in Turbo Pascal and test it in a batch?

 A: Below is a simple example of a COMPARE.PAS program that compares
two integers and returns an errorlevel 0, 1 or 2 depending on the
result of the comparison. Errorlevels 3 and 4 are reserved for
actual errors.
  var num1, num2 : longint;
      k1, k2 : integer;
  begin
    if ParamCount < 2 then halt(4);
    Val (ParamStr(1), num1, k1);
    Val (ParamStr(2), num2, k2);
    if (k1 <> 0) or (k2 <> 0) then halt(3);
    if num1 = num2 then halt(0);
    if num1 > num2 then halt(1);
    if num2 > num1 then halt(2);
  end.
Below is a batch file that reports, based on the errorlevel returned
by COMPARE.EXE, the result of a comparison of two integers. The
program and the batch file example should give you the idea of the
rudiments of programming for MS-DOS errorlevels.
  @echo off
  compare 11 24
  rem The errorlevels (must be in this order!)
  if errorlevel==4 goto _level4
  if errorlevel==3 goto _level3
  if errorlevel==2 goto _level2
  if errorlevel==1 goto _level1
  if errorlevel==0 goto _level0
  ::
  :_level0
  echo Equal integers
  goto _end
  ::
  :_level1
  echo The first integer is bigger
  goto _end
  ::
  :_level2
  echo The second integer is bigger
  goto _end
  ::
  :_level3
  echo Error in integer
  goto _end
  ::
  :_level4
  echo Missing parameter
  ::
  :_end
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:08 1998
Subject: Playing cat and mouse

128. *****
 Q: How do I get started with mouse programming?

 A: I wrote the elementary test program below. Take a moment to
study it. The example should get you started. It is as far as I went
myself. The mode is is for the ordinary 25x80 text mode.

  program MousTestProgram;

  uses Crt, Dos;

  procedure ResetMouse;
  var regs : registers;
  begin
    FillChar (regs, SizeOf(regs), 0);
    regs.ax := $0000;
    Intr ($33, regs);
    if regs.ax <> $FFFF then begin
      writeln ('hardware/driver not installed');
      halt;
    end;
  end; (* ResetMouse *)

  procedure ShowMouseCursor;
  var regs : registers;
  begin
    FillChar (regs, SizeOf(regs), 0);
    regs.ax := $0001;
    Intr ($33, regs);
  end; (* ShowMouseCursor *)

  procedure HideMouseCursor;
  var regs : registers;
  begin
    FillChar (regs, SizeOf(regs), 0);
    regs.ax := $0002;
    Intr ($33, regs);
  end; (* HideMouseCursor *)

  procedure GetMouseCursor (var row, column, button : word);
  var regs : registers;
  begin
    FillChar (regs, SizeOf(regs), 0);
    regs.ax := $0003;
    Intr ($33, regs);
    row := regs.dx div 8;
    column := regs.cx div 8;
    button := regs.bx;
  end; (* GetMouseCursor *)

  procedure PutMouseCursor (row, column : word);
  var regs : registers;
  begin
    FillChar (regs, SizeOf(regs), 0);
    regs.ax := $0004;
    regs.dx := 8 * row;
    regs.cx := 8 * column;
    Intr ($33, regs);
  end; (* PutMouseCursor *)

  procedure TEST;
  var row, col, but : word;
  begin
    repeat
      GotoXY(1,1);
      GetMouseCursor (row, col, but);
      write (' row=', row:4, ', col=', col:4, ', but=', but:2);
    until KeyPressed;
  end;

  begin
    ResetMouse;
    ShowMouseCursor;
    PutMouseCursor (12, 39);
    TEST;
    HideMouseCursor;
  end.  (* MousTestProgram *)

I tested out these routines is a trivial "GRIDGAME.EXE A simple
reaction game w/mouse" which is available without source code (but
crucial parts are here) as ftp://garbo.uwasa.fi/pc/ts/tsgmef10.zip
or whichever is the current version number.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:09 1998
Subject: Date validation

129. *****
 Q: How can I find out if a date is a valid date?

 A: Ask her :-). Ok, ok, seriously. The task boils down to finding
which is the last valid day of the relevant month because the rest
is trivial (i.e. the month must be between 1 and 12, and the first
day of a month always is at least 1). The following function gives
the last day of a month. The rest is up to you.
  function LASTDDFN (month, year : integer) : integer;
  var i, nextMonth, yy : integer;
  begin
    nextMonth := month + 1;
    yy := year;
    if nextMonth > 12 then begin
      yy := yy + 1;
      nextMonth := nextMonth - 12;
    end;
    for i := 28 to 31 do begin
      if Weekday (i, month, year) =
         ((WeekDay (1, nextMonth, yy) + 6) mod 7)
         then lastddfn := i;
    end;
  end;  (* lastddfn *)
The Weekday function in the above was already given in an earlier
FAQ item "I want code that gives the weekday of the given date".
  John Stockton's edited comment: "Even assuming WEEKDAY is
available, that seems a cumbersome approach; and the Q is
ill-defined unless the Calendar (&c) is defined; consider
1700/02/29, valid in England and the Colonies, and probably in
Scotland too (a bit of doubt remains), but not in Rome; or
1752/09/12 (inverse validity) or 0004/02/29 or 1712/02/30."
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:10 1998
Subject: Writing array to text file

130. *****
 Q: How can I write an array to a text file in Turbo Pascal?

 A: You can't directly, as such. You'll either have to declare an
array type of file, which is not a text file (METHOD1 below) or
write the elements of the array one by one to a text file (METHOD2
below). Study these small examples. They include some useful
beginner's tricks including declaring an array as a constant and
format of giving the initial values, checking being able to open a
file, and two formats of array declaration.
  procedure METHOD1;
  type matrix23 = array [1..2] of array [1..3] of real;
  var f : file of matrix23;
  const a : matrix23 = ((11, 12, 13), (21, 22, 23));
  begin
    Assign (f, 'test1.dat');
    {$I-}Rewrite(f);{$I+}
    if IOResult <> 0 then begin
      writeln ('Error opening file for output');
      halt(1); {returns errorlevel 1}
    end;
    write (f, a);
    Close(f);
  end;  (* method1 *)
  {}
  procedure METHOD2;
  var f : text;
      i, j : integer;
  const a : array [1..2, 1..3] of real
            = ((11, 12, 13), (21, 22, 23));
  begin
    Assign (f, 'test2.dat');
    {$I-}Rewrite(f);{$I+}
    if IOResult <> 0 then begin
      writeln ('Error opening file for output');
      halt(1);
    end;
    for i := 1 to 2 do
      for j := 1 to 3 do
        write (f, a[i, j], ' ');
    Close(f);
  end;  (* method2 *)
  {}
  begin
    METHOD1;
    METHOD2;
  end.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:11 1998
Subject: Ascii and scancodes

131. *****
 Q: Where do I get ASCII and scan codes for the different keys?

 A: I have written a SCANCODE.EXE program that you can use to get
the codes. It is contained in
  Oct 18 1997 ftp://garbo.uwasa.fi/pc/ts/tsbase13.zip
  Programmer's tools base: base, basecalc, scancode
or whichever is the latest version number.
  Usage:  SCANCODE  [ 1 | 2 | 3 | 4 ]
     get ascii codes -+   :   :   :
       get keyscan codes -+   :   :
            get shift status -+   :
       get extended shift status -+

An example of the usage. I pressed F1 to get its code
  c:\tuki\scancode 1
  Extended ascii keycode:  0  59  ($3B)
Compare this result with the very first item in the first part of
this FAQ. It shows how pressing the F1 key can be detected in a
Turbo Pascal program.

Another example. I pressed 0 in the numerical keypad to get is ASCII
code and its key scancode
  c:\tuki\scancode 1
  Ascii keycode:   48  ($30)  for 0
  c:\tuki\scancode 2
  Scancode:   82  ($52)  0
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:12 1998
Subject: Just for the record

132. *****
 Q: How do I define and use records? How do I give initial values?

 A: The examples below give some very basics of using records. Let's
consider complex numbers and their multiplication. This is a typical
situation where records are convenient.

  (* Define a complex number type as a record with the real and the
     imaginary part *)
  type complexRecordType = record
                             re, im : real;
                           end;

  (* A procedure for multiplying two complex numbers *)
  procedure COMPMULT (c1, c2 : complexRecordType;
                      var c3 : complexRecordType);
  begin
    c3.re := c1.re * c2.re - c1.im * c2.im;
    c3.im := c1.im * c2.re + c1.re * c2.im;
  end;  (* compmult *)

  (* One, ordinary way of doing it *)
  procedure TEST1;
  var a, b, c : complexRecordType;  {Define three complex variables}
  begin
    a.re := 0.5; a.im := 1.0;       {Give the values, method 1}
    with b do begin                 {Give the values, method 2}
      re := -1.0; im := 2.0;
    end;
    COMPMULT (a, b, c);
    writeln (c.re:0:5, ' i*(', c.im:0:5, ')');
  end;  (* test1 *)

  (* Another way of doing it giving initial values as a constant *)
  procedure TEST2;
  {... define an array of records ...}
  type complexRecordTypeArray = array [1..2] of complexRecordType;
  {... this is how to give initial values to a record ...}
  const a : complexRecordTypeArray
          = ((re:0.5; im:1.0), (re:-1.0; im:2.0));
  var c : complexRecordType;
  begin
    COMPMULT (a[1], a[2], c);
    writeln (c.re:0:5, ' i*(', c.im:0:5, ')');
  end;  (* test2 *)

  (* The main program *)
  begin
    TEST1;
    TEST2;
  end.

A comment from the regular contributor Dr. John Stockton: Note that
a TP/BP function cannot return a structured type such as a record.
(My addition: i.e. one always needs to use a procedure for that.)
There's a bit on Complex at                {$N+}

        http://www.merlyn.demon.co.uk/pas-math.htm#Complex
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:13 1998
Subject: Is this for real?

133. *****
 Q: What real numbers types are there? How do I use them?

 A: I'll only give the very basics, since the information is
contained in your friendly manuals and this FAQ is not intended to
replace them! If you have Turbo Pascal 7.0 please see in the
Language Guide the item on Real Types on pages 28-29 and Chapter 14
on Using the 80x87. However, the five real types are
  TYPE       LOW        HIGH      DIGITS   BYTES
  real     | 2.9e-39    1.7e38   | 11-12  |   6
  single   | 1.5e-45    3.4e38   |  7-8   |   4
  double   | 5.0e-324   1.7e308  | 15-16  |   8
  extended | 3.4e-4932  1.1e4932 | 19-20  |  10
  comp     | 0.0, 1.0   9.2e18   | 19-20  |   8
A much better presentation of the table can be found at Dr. John
Stockton's http://www.merlyn.demon.co.uk/pas-math.htm#FloatTypes
You'll always have an access to the real type. Using the others will
require appropriate setting the {$N} and the {$E} compiler
directives. To get more information about them, read the current
file in the Turbo Pascal IDE (i.e. the editor), move the cursor on
the directives above, and press Ctrl-F1 to get the TP help. For the
first select from the list of three the item "$N: Numeric
Coprocessor Switch".
   If you have a program where you have user the type real and wish
to increase the accuracy, you do not need to change every occurrence
of "real". A much quicker method is shown by the small example below
  {$N+} { The Numeric Coprocessor Switch needed for access to four
          additional real types: Single, Double, Extended, and Comp. }
  {$E-} {The compiler links with smaller floating-point library. It
         requires that the math coprocessor is available at run time. }
  type real = double;  { ... this is the trick! ...}
  var a, b : real;
  begin
    a := 4.0; b := 7.0;
    writeln (a/b);
  end.
John, quite correctly, comments on 'type real = double': "I don't
like redefining a standard, though not reserved, identifier - too
confusing - and prone to error if using Units, since one must ensure
that it applies in every Unit as well." I continue: with this in
mind, use e.g.
  {$N+,E+} { Use coprocessor emulation for a change, better
             generality, slower code. }
  var a, b : extended;
  begin
    a := 4.0; b := 7.0;
    writeln (a/b);
  end.
Also see http://www.merlyn.demon.co.uk/pas-math.htm#Float

Comments from Osmo Ronkanen: 1) "If you use $N+ and especially if
you use also $E+, you should not use the real type." 2) "One way is
to do:
  {$ifopt n+}
  type real=double;
  {$endif}
That way it compiles on both $N+ and $N- with no compromises."
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:14 1998
Subject: Default switches

134. *****
 Q: How do I find out the default status of the compiler switches?

 A: The compiler directives can be pasted by pressing Ctrl-O O. This
is one of the less known, useful features of the Turbo Pascal's
editor. (You'll find all the editor key combinations in Turbo Pascal
version 7.0 Programmer's Reference, Appendix A, Editor reference.)
When you press Ctrl-O O you'll get something like
  {$A+,B-,D-,E-,F-,G-,I-,L-,N-,O-,P-,Q-,R-,S+,T-,V+,X+}
  {$M 16384,0,655360}
If you wish to know what each means, move the cursor on the relevant
directive and press Ctrl-F1. Unfortunately, you'll have to lead each
directive is $ if you wish to use Turbo Pascal's online help to get
the meanings this way.
  A comment from Horst Kraemer: "Note that 'default' may mean
different things to different people at different times. This is not
really an abolute "default" status, but it is the actual setting
which reflects the permanent settings of the TURBO.TP currently
used."
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:15 1998
Subject: Need much source code

135. *****
 Q: Anyone know where I can download Pascal source codes?

 A: There is a lot of Turbo Pascal material, much of it with source
code, at http://garbo.uwasa.fi/pc/ in the following directories
 /pc/turboobj/  Turbo Pascal object based programs
 /pc/turbopa1/  Outdated, non-functional or incomplete Turbo Pascal material
 /pc/turbopa45/ Turbo Pascal versions 4.0, 5.0 and 5.5 specific material
 /pc/turbopa6/  Turbo Pascal version 6.0 specific programming material
 /pc/turbopa7/  Turbo Pascal version 7.0 or 6+ specific programming material
 /pc/turbopas/  Generic Turbo Pascal programming language material
 /pc/turbovis/  Turbo Pascal TVision oriented programs
 /pc/turbspec/  Special requirement TP programs, protected mode, BP, etc
The corresponding WWW clickables are
 http://garbo.uwasa.fi/pc/turboobj.html
 http://garbo.uwasa.fi/pc/turbopa1.html
 http://garbo.uwasa.fi/pc/turbopa45.html
 http://garbo.uwasa.fi/pc/turbopa6.html
 http://garbo.uwasa.fi/pc/turbopa7.html
 http://garbo.uwasa.fi/pc/turbopas.html
 http://garbo.uwasa.fi/pc/turbovis.html
 http://garbo.uwasa.fi/pc/turbspec.html
Of course the current file ftp://garbo.uwasa.fi/pc/link/tsfaqp.zip
has much source snippets.
   Furthermore, see http://www.uwasa.fi/~ts/http/http2.html for some
links to various Turbo Pascal resources available on the net.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:16 1998
Subject: Set operation bug?

136. *****
 Q: Is there a bug in Turbo Pascal's set operations?
I have the following code
  var num : integer;
  begin
    repeat
      write ('Enter a number between 1 and 99: ');
      readln (num);
    until num in [1..99];
  end.
but when I enter 788 the loop exits instead of repeating.

 A: There are very few bugs in Turbo Pascal (like the notorious
Crt.delay problem on a fast PC in TP 7.0) and this is not one of
them. Rather, as Dr. John Stockton puts it "It is a deficiency,
exposed by a programmer error." The above example is based on a
subtle mixing of apples and oranges. The problem is in defining "var
num : integer;" while Turbo Pascal sets are internally made up by
ordinal values between 0 and 255. If the code is tested prudently
with the range check {$R+} on, a run-time error results (as it
should) since the input value is outside the legitimate range. This
is what happens
     Enter a number between 1 and 99: 788
     Runtime error 201 at 0000:0088.
This goes to show how important it is to first test one's code with
all the checks on.
   In case the code weren't based on an assignment in using sets (we
don't know that in wrting this item), the easy solution would be to
allow all integers and terminate the loop with "until (num >=1) and
(num <= 99);"

 A2: Speaking of sets, the most well-know example of using sets is
the following kind
  const upabc = ['A', 'B', 'C'];
        loabc = ['a', 'b', 'c'];
  begin
    if 'b' in upabc + loabc then writeln ('In set')
      else writeln ('Not in set');
  end.
Note that here the size of the potential set (the set of ASCII
characters) does not exceed 256.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:17 1998
Subject: Currency type output

137. *****
 Q: How can I output 1000000 as 1,000,000?

 A: Here is one option as a function. It also includes decimals for
this currency-type output requirement.
  function CRNCYFN (x : real; dd : byte; sep : char) : string;
  var s, sint, sfrac : string;
      i, p : byte;
  begin
    Str (abs(Int(x)):0:0, sint);
    p := Length(sint);
    s := '';
    for i := p downto 1 do begin
      s := sint[i] + s;
      if ((p-i) mod 3 = 2) and (i <> 1) then s := sep + s;
    end;
    if x < 0 then s := '-' + s;
    Str (Frac(x):0:dd, sfrac);
    crncyfn := s+Copy(sfrac, 2, dd+1);
  end;  (* crncyfn *)
  {}
  begin
    writeln (CRNCYFN (-100000, 2, ','));
  end.

Dr. John Stockton wrote: "Neither adding a character to the front of
a string, nor Insert, is especially quick; but the following
complete program tests an alternative :" (John's alternative is for
integer types. I have converted John's original code into a function
and plugged in the handling of negative values.)
  function CRNJRFN (x : longint) : string;
  var p : byte; S : string ;
  begin
    Str (abs(x), S);
    p := Succ(Length(S)) ;
    while p>4 do begin
      Dec(p, 3) ;
      Insert(',', S, p);
    end ;
    if x < 0 then S := '-' + S;
    crnjrfn := S;
  end;  (* crnjrfn *)
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:18 1998
Subject: Printing from TP

138. *****
 Q: How can I write text to the printer from a TP program?

 A: Many aspects of this question, some of them more advanced, are
already covered in this Turbo Pascal FAQ. See items #2, #15, #19,
and #106. But to answer the elementary printing question see the
code below
  uses Printer;
  begin
    writeln (lst, 'Hello world');
  end.
Including the the Printer unit assign a printer text file lst. No
other opening or closing the device is required. A solution that
does not use the printer unit is given below
  const UsePrinter : boolean = true;
  procedure WRITEIT (s : string);
  var f : text;
  begin
    if UsePrinter then
      begin
        assign (f, 'prn');
        rewrite (f);
        writeln (f, s);
        close (f);
      end
    else
      writeln (s);
  end;
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:19 1998
Subject: Random numbers

139. *****
 Q: How do I get a random number from 1 to 49 instead of 0 to 49?

 A: The first issue here is whether you wish to consider integers or
reals. Let's start with integers and the most elementary case of
producing 0's and 1's. They are produced by Random(2). Thus integers
from 0 to 49 would be produced by Random(50) and integers from 1 to
49 by 1+Random(49). More generally we have
  (* Generate a random integer in the range [low, high] *)
  function IRNDFN (low, high : integer) : integer;
  begin
    irndfn := low + Random (high - low + 1);
  end; (* irndfn *)
The above makes no validity checks of the range. However the
expression "high-low+1" must be compatible with the word type
variables, since that is what is required if an argument is given to
Random.
   In the case of reals the corresponding random function for a
range is
  (* Generate a random real in the range [low, high] *)
  function RNDFN (low, high : real) : real;
  begin
    rndfn := low + (high - low) * Random;
  end; (* rndfn *)
A simple test demonstrating the validity of these functions is e.g.
  procedure TEST;
  var i : integer;
  begin
    for i := 1 to 10 do begin
      writeln (IRNDFN(2,3));
    end;
  end; (* test *)
  {}
  procedure TEST2;
  var i : longint;
      r, rmin, rmax : real;
  begin
    rmin :=  1.0E+38;
    rmax := -1.0E+38;
    for i := 1 to 100000 do begin
      r := RNDFN(2,3);
      if r > rmax then rmax := r;
      if r < rmin then rmin := r;
    end;
    writeln (rmin:0:8, '  ', rmax:0:8);
  end; (* test2 *)
  {}
  begin
    Randomize;
    TEST;
    TEST2;
  end.
Comments from Dr. John Stockton:
"With Run-time Checking on, "irndfn(-22222, 22222)" fails Range and
Overflow checks.
      irndfn := low + Random (longint(high) - longint(low) + 1);
seems safer; and it would be another example of the need to be
careful with the size of arithmetic variables.
   Alternatively,
      function IRNDFN (low, high : word) : word ;
which I expect to accept any pair of word inputs with low<=high."
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:20 1998
Subject: Shuffling a deck of cards

140. *****
 Q: How do I get a list of numbers from 1 to 52 in a random order?

 A: What you are asking for can be called the card shuffling
problem. This is because one customary solution is first to make an
ordered array of the numbers from 1 to 52 and then shuffle the
array.
  Type IArrayType = array [1..52] of integer;
  {}
  procedure SHUFFLE (var IArray : IArrayType);
  var i, j, tmp : integer;
  begin
    for i := 1 to 52 do
      begin
        j := 1 + Random(52);
        tmp := IArray[i];
        IArray[i] := IArray[j];
        IArray[j] := tmp;
      end;
  end;  (* shuffle *)
  {}
  procedure TEST;
  var IArray : IArrayType;
      i : integer;
  begin
    for i := 1 to 52 do IArray[i] := i;
    SHUFFLE (IArray);
    for i := 1 to 52 do begin
      write (IArray[i]:3);
      if i mod 26 = 0 then writeln;
    end;
  end;  (* test *)
  {}
  begin
    Randomize;
    TEST;
  end.

Dr. John Stockton comments: "No, I think not. This does not give a
uniform distribution, and there is an elegant argument (given by
Gerald Brandt) to show that it cannot; moreover I have demonstrated
this to myself experimentally (for a smaller value than 52). One
should first choose one of 52, then one of the remaining 51, then of
50, 49, ... 2 -
        for J := Max downto 2 do Swap(A[J], A[Succ(Random(J))]) ;
This has indeed been a FAQ, and the details that I've collected can
be found at Web http://www.merlyn.demon.co.uk/pascal.htm#Rand "
Converting John's into my own notation we get
  procedure JRSHFFLE (var IArray : IArrayType);
    procedure SWAPITEM (var ia, ib : integer);
    var tmp : integer;
    begin
      tmp := ia; ia := ib; ib := tmp;
    end;
  var j : integer;
  begin
    for j := 52 downto 2 do SWAPITEM(IArray[j], IArray[1+Random(j)]) ;
  end;  (* jrshffle *)

Horst Kraemer posted another useful shuffling algorithm ("No
initialization of 'a' needed").
  a[1]:=1;
  for i:=2 to 52 do begin
    j:=random(i)+1; { j := one of 1..i }
    a[i]:=a[j]; a[j]:=i
  end;
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:21 1998
Subject: Disable a drive

141. *****
 Q: How can I disable/re-enable a drive from within a TP program?

 A: This FAQ collection already contains ample examples of interrupt
programming. Since this is a straight-forward application, I'll just
give you the pointer to the relevant interrupts. They are interrupt
21 Hex, function 5F Hex, subfunctions 08 and 07. For details, see
ftp://garbo.uwasa.fi/pc/programming/inter59b.zip INTERRUP.G.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:22 1998
Subject: ROT13

142. *****
 Q: How can I write a filter for ROT13 encryption in Turbo Pascal?

 A: ROT13 is a very simple encryption where the alphabet ASCII are
set forward by 13 steps. Many newsreader programs for Usenet news
include the ROT13 conversion. A Turbo Pascal filter program is given
below. Its usage is ROT13 < MYTEXT.TXT
  var ch : char;
  begin
    while not eof(input) do
      begin
        read (ch);
        case ch of
          'a'..'m', 'A'..'M' : write (chr(ord(ch)+13));
          'n'..'z', 'N'..'Z' : write (chr(ord(ch)-13));
          else write (ch);
        end; {case}
      end; {while}
  end.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:24 1998
Subject: Round-up error

143. *****
 Q: Why does writing the value of r := 104.50; give a false result?

 A: Consider the following code
  {$N+}  { The Numeric Coprocessor Switch }
  var r : real;
  begin
    r := 104.50;
    writeln (Round(r));
  end.
Unexpectedly it gives 104, not 105. I did not know this, but Osmo
Ronkanen and Pedt Scragg pointed out that "With $N+ with reals,
Round works to nearest even number". With r := 104.5001; and
104.4999; the rounding will give the results you would expect. The
same goes if you have {$N-}, i.e. the numeric coprocessor off.
   Well, then, how always to get what you rather would expect, also
under {$N+}? This already is covered in item #107 in this TP FAQ.
"RoundRealFn (r, 0);" will work.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:25 1998
Subject: Finding a word

144. *****
 Q: What is the quickest way to find a word from an ordered array?

 A: I don't know if this is absolutely the quickest way, but
literature considers binary search to be an efficient method of
finding a word from an array that has been presorted alphabetically
(in accordance to ASCII conventions). The relevant definitions and
the algorithm are given below. Note that in a real-life application
you would probably wish to use a bigger array. This can be done
using the definitions explained in item #14 on larger than 64Kb
arrays.
  const nmax    = 3072;
        kmax    = 20;
  type StringKType = string[kmax];
       WordsArrayType = array [1..nmax] of StringKType;
  var WordsArray : WordsArrayType;
  {}
  procedure BinarySearch (low, high : longint;
                          FindWord  : StringKType;
                          var j     : longint;
                          var found : boolean);
  begin
    found := false;
    j := (low + high) div 2;
    while ((not found) and (high >= low)) do
      begin
        if WordsArray[j] = FindWord then
          found := true
        else
          if WordsArray[j] > FindWord then
            high := j - 1
          else
            low := j + 1;
        j := (low + high) div 2;
      end;
  end;  (* BinarySearch *)
The initial value for low is 1 and the initial value for high the
number of words in the array.
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:26 1998
Subject: Executable version

145. *****
 Q: How to tell which TP version has been used to compile an .exe?

 A: Turbo Pascal versions 6 and 7 put a copyright string into the
executable. For the earlier versions I do not know how to find out.
The batch file below does the compiler version testing of an
executable file. To use this batch, you'll need Borland's grep.com
from Turbo Pascal distribution disks. In version 7.0 it is on disk
#3 in utils.zip. The reason is that the MS-DOS find.exe does not
read beyond any ASCII #26 characters in the executable, but grep.com
does. The batch below also requires an MS-DOS version new enough to
have a find.exe that returns errorlevels.

  @echo off
  if "%1"=="" goto _usage

  grep "Portions Copyright (c) 1983,92 Borland" %1 > tmp$$$.$$$
  grep "Portions Copyright (c) 1983,90 Borland" %1 >> tmp$$$.$$$

  find "Portions Copyright (c) 1983,92 Borland" tmp$$$.$$$ >nul
  if errorlevel==2 goto _serror
  if errorlevel==1 goto _not71
  echo %1 compiled with Turbo Pascal version 7.0 or 7.01
  goto _out

  :_not71
  find "Portions Copyright (c) 1983,90 Borland" tmp$$$.$$$ >nul
  if errorlevel==2 goto _serror
  if errorlevel==1 goto _not60
  echo %1 compiled with Turbo Pascal version 6.0
  goto _out

  :_not60
  echo %1 not compiled with Turbo Pascal version 7.01, nor 6.0
  goto _out

  :_serror
  echo Error in search
  goto :_out

  :_usage
  echo Usage: %0 NameOfTheExecutableFile

  :_out
  if exist tmp$$$.$$$ del tmp$$$.$$$
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:27 1998
Subject: Making directory read-only

146. *****
 Q: How can I make a directory read-only under TP, and vice versa?

 A: File attributes can be set with SetFAttr but this does not seem
to work for directories. Thus the only solution I know is executing
the attrib MS-DOS command:

  {$M 2048, 0, 0}
  Uses Dos;

  procedure RDIRON (dirname : string);
  begin
    SwapVectors;
    Exec (GetEnv('comspec'), '/c attrib +r ' + dirname);
    SwapVectors;
    if DosError <> 0 then
      writeln ('Dos error number ', DosError)
    else
      writeln ('Mission accomplished, exit code ', DosExitCode);
  end; (* rdiron *)

  procedure RDIROFF (dirname : string);
  begin
    SwapVectors;
    Exec (GetEnv('comspec'), '/c attrib -r ' + dirname);
    SwapVectors;
    if DosError <> 0 then
      writeln ('Dos error number ', DosError)
    else
      writeln ('Mission accomplished, exit code ', DosExitCode);
  end; (* rdiroff *)
--------------------------------------------------------------------

From ts@uwasa.fi Sat Oct 10 00:02:28 1998
Subject: Finding TP 7.0

147. *****
 Q: Where can I find Turbo Pascal 7.0 compiler on the net?

 A: This item owes heavily above all to Pedt Scragg and also to
Klaus Hartnegg + John Stockton. TP/BP is not free or shareware so
you have to buy it outright. Here are some resources for legally
obtaining the Turbo Pascal compiler, a compatible compiler or a
Pascal compiler in general. Please be aware, as always with WWW
links, that in a stored FAQ they might change. But these links at
least give you a hint what to look for.
 1) Eric Engelmann's EMS Professional Software's "Old Tool Exchange
    - Borland" http://www.wdn.com/ems/oldtools/borpas.htm
 2) Inprise "Where to buy" at http://www.inprise.com/wheretobuy/
    Inprise is the former Borland so you can also try e.g.
    http://www.borland.co.uk/
 3) Free Pascal (aka FPK Pascal) by Florian Klaempfl. A 32 bit
    pascal compiler made public by Klaus Hartnegg at his web page
    http://www.brain.uni-freiburg.de/~klaus/fpc/
 4) GNU Pascal - the free 32bit compiler available from
    http://agnes.dida.physik.uni-essen.de/~gnu-pascal/
 5) TMT Pascal - free 32bit evaluation/personal use compiler only,
    from http://www.tmt.com/
--------------------------------------------------------------------

