[ Go to Stephen's Entry Page ]
[ TI99/4a Articles index |    TI Book front page |    TI Resources Page |   PC99 Programs ||    TI*MES 34-36 ||    TI*MES 38 ]
contact - please use subject="pc99 page" to avoid spam trap!

Jump to:
Basic: Using PRINT USING
How to store data in TI disk files
Mostly on Arrays and something on sorts- by Jim Peterson.

This web page contains the text of articles for owners of the TI-99/4a from Issue 37 TI*MES (Summer 1992). It is of use to users of the TI-99/4a emulators.

Items from TI*MES Issue 37 Summer 1992

Graphic program

A SMALL GRAPHIC PROGRAM.
For Ex Bas + The Missing Link but any language which can set pixels on will do:
image of mesh plot
100 FOR T=0 T0 2000 STEP 0.0
06
110 X=SIN(0.99*T)-0.7*COS(3.
01*T)
120 Y=COS(1.01*T)+0.1*SIN(15
.03*T)
130 ! or try other fractiona
l multipliers
140 ! this formula gives X f
rom -1.7 to + 1.7
145 ! and Y from -1.1 to +1.
1 so
150 X=X*54+96 ::Y=Y*100+120
160 CALL LINK("PIXEL",X,Y)
170 NEXT T
180 END

It takes a while but the result is quite attractive. You can take even longer by concentrating on a part of the image (using different scaling) to see the finer details.


USING USING

by Mark Schafer. From the June 1990 issue of Bytemonger (Bluegrass 99 Computer Society).
This is a new and different animal for me, as I normally do not write tutorials. It came up at a recent meeting that some people are having trouble with PRINT USING. Since I consider it to be no problem, I volunteered to write an article about it. As for the title, remember that USING can also be used in a DISPLAY USING statement. There's a lot of ground to cover, so let's get started with ....

Beginner's Stuff
USING represents a method by which you can get data to print in a specific format instead of the default format. The syntaxes are:
PRINT USING format:print-list
DISPLAY [option-1ist:] USING format[:print-list]
where format is a line number or a string expression. A line number would the line where an IMAGE statement is found which would contain the string expression which is the iormat. A string expression can be as simple as a string literal enclosed in quotes or a complicated expression made up variables, function calls, whatever. Now 1et's compare the two methods of printing:
100 PRINT -56.7;109.2850
110 PRINT USING "####.# ###.####":-56.7,109.285

RUN this program and it looks like this:
-56.7  109.285
 -56.7 109.2850
USING allows you to better control the spacing as well as put trailing zeroes to the right of the decimal point.

Let's look at the format string. It is made up of fields with optional text.

Fields are like fill-in-the-blanks. They mark the place where an unknown value will be printed. Text is printed the same way every time. Fields are made up of pound (hash) signs (#) with possibly a decimal point, a minus sign, or maybe some circumflexes (^). Circumflexes follow in a later section.
Pound or hash signs take the place of a digit or sign. So in line 110 above, -56.7 printed with the field "####.#".

Go to top of page


-56.7 has only three characters to the left of the decimal point, so the initial character is left blank; the second one is where the minus sign went. Numbers are always aligned with the decimal point. If there isn't one, it's assumed to be at the end.

If there's a minus sign in the field, it always goes at the beginning. It indicates that you want the minus sign to appear immediately to the left of the number if it is negative. However, this is the same thing another pound sign would do, so you never really need to use a minus sign (except for a more advanced purpose to be discussed later.) In other words, "-##.##" does the same thing as "###.##".

The decimal point indicates where you want it to be. The number of pound signs you put to the left of it dictates how many digits you want to the left; the number you put on the right indicates how many decimal places you want. The decimal is always printed even if there are no pound signs after it.

If there are fewer digits in the value to the left of the decimal point than there are in the field, spaces are used to fill it out. If there are too many, the computer will refuse to print your value and fill the field with asterisks (*). This means your value is too high and/or there aren't enough places in your field. This includes the minus sign, if any.

If there are fewer digits in the value to the right of the decimal point than there are in the field, zeroes are used to fill in the remainder. If there are too many, the computer will estimate the number to the number of places given. So .## can be used to estimate to the nearest 100th, .# to the nearest tenth, and if there are no decimal places, it will estimate to the nearest unit.

Below is a table of how various values will be printed with various fields. The left side represents the value; across the top are the fields.
 
        #  ##  ###  #.#  ##.##  ###.###
2       2   2    2  2.0   2.00    2.000
-13     *  **  -13  ***  *****  -13.000
9.671   *  10   10  9.7   9.67    9.671
125.678 *  **  126  ***  *****  125.678
-.385   *  -0   -0  -.4   -.39    -.385
-3.05   *  -3   -3  ***  -3.05   -3.050

Generally, XB handles format strings like this:

It keeps printing text until it comes to a field (characterized by a pound sign). Then it looks for the next value to be printed. If there is one, it prints it in the format specified; if there isn't one, it terminates there and doesn't print anything else. However, you must specify at least one value and there must be at least one field in the format.

Except, curiously, DISPLAY USING doesn't need any values. Why would anyone use DISPLAY USING without any values?

If USING reaches the end of the string, and there's more values to be printed, it goes to the next line and starts over at the beginning of the string. So it's ok if the number of fields doesn't match the number of values.

Going back to syntax, if you want to use the format "I HAVE $###.##", you have three ways of doing it:
120 IMAGE I HAVE $###.##
130 PRINT USING 120:M
140 A$="I HAVE $###.##"
150 PRINT USING A$:M
160 PRINT USING "I HAVE $###.##":M

Notice that if use the first method using IMAGE, you don't need quotes. The only time you need quotes with an IMAGE statement would be when you have leading or trailing spaces. If you're going to use it repeatedly in a program, IMAGE is the most efficient method. If you're only using it once, go with the third method. Practically the only time you need the second method would be when the format string is constructed so you may not know exactly what it looks like.

The IMAGE statement must be on a line by itself. The computer ignores it when it comes to it in a program the same a it does the DATA statement, so you can put it anywhere.

Circumflexes are used to denote scientific notation which leads us to the next section....

You may want the number to come out in scientific notation (E format) even if the number normally wouldn't. To do this you put four or five circumflexes at the end of the field. Four means you want two digits in the exponent; five means you want three. If you put less than four, they will be treated as text; if you put more than five, the first five will be used, and the rest will be treated as text.

There's a little something different about E format. It always reserves the first character for the sign, so you'll need at least two #'s in the mantissa (or precede the field with a sign). Using the same numbers as above, the table can be amended thusly:

        ##^^^^  ####^^^^  #.####^^^^^
2        2E+00  200E-02    .2000E+001
-13     -1E+01 -130E-01   -.1300E+002
9.671    1E+01  967E-02    .9671E+001
125.678  1E+02  126E+00    .1257E+003
-.385   -4E-01 -385E-03   -.3850E+000
-3.05   -3E+00 -305E-02   -.3050E+001

This format also estimates whenever it isn't given enough places to express the exact value or tacks on zeroes when given too many. You'll probably not need this format unless you're dealing with exceedingly low or high numbers.

Time to switch to another concept. USING can also be used to format text values. No bells or whistles here though. Any field that can be used to format a number can also be used to format text. Text is just left-justified, and all characters within a field are treated the same. I can give you a table but I assure you it's quite boring:
          ### ###.### -###^^^^
BOB       BOB BOB     BOB
JOHN      *** JOHN    JOHN
25% OFF!  *** ******* 25% OFF!
In general, it makes more sense to use only #'s when constructing a text field. You may want to do something like column 2 if you're printing a table of numbers, and you want to use the same format string for the column headers as in the table.

Of course you can mix numeric and text values for the same format as in something like:
170 IMAGE ##### HAS $###.##.
180 PRINT USING 170:"MARY",123.4

This will print:
MARY  HAS $123.40 Note the computer recognizes the period at the end as a period and not as a decimal point since the field already has one. Even if it didn't, it wouldn't make any difference since the decimal point would be printed at the end.

Now suppose you want part of a line formatted instead of a whole line. Don't worry; you can always put a semicolon at the end of a PRINT or PRINT USING statement, so you can stay on the same line for the next PRINT. You can also use this technique if you need to print a # as text instead of a field character. However, you cannot put a comma at the end of a PRINT USING statement or the computer will think you left out a value.

Another problem you may have is suppose you want to concatenate two fields, say ## with ###. If you put them together, you get #####, and the computer will see that as one field. Well, you could try using "##-##". The computer will then recognize that as two fields, ## and -##. But if your second field can have three digits, this won’t work. If this is the case, you will need to split up the format string into two PRINT USING statements. Perhaps like this:
190 PRINT USING "##":A;
200 PRINT USING "###":B

You cannot confuse the computer with pound signs, minus signs, circumflexes, decimal points, etc. lt will always know where one field ends and another begins. Except in the case like above where two fields collide. I'll give you another way to handle that in ....

Go to top of page


Advanced Stuff
There isn't very much in the way of advanced stuff because this is such a small subset of a much bigger entity. I always consider undocumented features to be advanced stuff. That’s right, boys and girls, USING has an undocumented feature! By "etc." in the above paragraph, I meant the plus sign! It can also be used like the minus sign in a field. what it will do is float the sign in front of the number be it positive or negative. Let me illustrate:
        +#  +###  +###.##^^^^
2       +2    +2  +200.00E-02
-13     **   -13  -130.00E-01
9.671   **   +10  +967.10E-02
125.678 **  +126  +125.68E+00
-.385   -0    -0  -385.00E-03
-3.05   -3    -3  -305.00E-02

If it's negative, you get a minus sign; if it's positive or zero, you get a plus sign. There isn't one word about this in the XB manual. This can be used to write format strings for equations, like this:
200 IMAGE ###x^2+##x+##
210 PRINT USING 200:2,7,10
220 PRINT USING 200:-15,-11,1
230 PRINT USING 200:+9,33,-14
This will print:
  2x^2 +7x+10
-15x^2-11x +1
  9x^2+33x-14

Suppose you don't want it to print those leading spaces when there aren't enough digits to fill the field. This is where you would use a variable for the format string. You would construct it so that each field would only have as many #'s as it needed to print the number (since they're all integers, that can easily be accomplished with LEN(STR$(X))) and concatenate the necessary text and use the variable as the format string. For instance, I have a program that uses a define function like this:
240 DEF MF$(X)=" $"&RPT$("#"
,LEN(STR$(INT(X))))&".##"

That function generates a format string for money so that there will no spaces between the dollar sign and the amount.

This places trailing zeroes after the decimal point. However, user-defined function calls take a long time in Extended BASIC, so if you need speed or if you only need it once, put a formula like this directly where you need it.

There's another manual correction that must be made (although it may have been made already in some addendum I don't know about).
The syntax for PRINT USING with files is wrong. I won't reprint the manual entry here because I don't like glorifying the incorrect. The correct syntax is as follows:
PRINT [#file-number,[REC record-number,]USING format:print-list

That translates specifically as something like:
250 PRINT #1,USING 200:A,B,C
260 PRINT #2,REC 15,USING 20
0:A*4,B*4,C*4

The file being referenced is most likely the printer, but it could be a disk file. If it is, it must be a DISPLAY format file; you will get a FILE ERROR if is INTERNAL format. Obviously line 260 isn't referring to the printer.

PRINT USING is very good for printing to a printer because the printer has so many more columns to print in. So there is a good chance that you may have a program in which a particular PRINT USING is only being used to print to the printer and never to the screen.

If this is the case, there is another solution to the concatenated field problem mentioned in the last section. You can print some unprintable character between the two fields. The computer will then recognize them as two fields but they will be together on the printout! Something like this would be typical:
270 A$="##"&CHR$(0)&"###"
280 PRINT #1,USING A$:A,B

CHR$(0) doesn't do anything on most printers, so the two fields will appear consecutive. Note that you cou1dn't do that in an IMAGE statement since you can only use characters and not expressions. The expression cou1d've also been constructed within line 280 itself.

This should about exhaust the subject of USING except to remind you that you can also use DISPLAY USING if you need to format values somewhere else on the screen (by using the AT option) or if want to BEEP or clear the screen before doing it (ERASE ALL).



Go to top of page

FILE PROTOCOLS Another article by Mark, from the July 1990 issue of Bytemonger- this describes the various choices available when deciding how to save data to TI disk:

This article is way too late. About 10 years too late. Texas Instruments should have read this article before they made TI-Writer! They are using the wrong file format, and you probably are too.

Let's look at how files are stored on disk. No, I am not going to get technical and tell you how you can use sector editors to look at and/or change things in a file. This will be a general approach. First, you should be aware that disks are divided into sectors. Catalog programs usually tell you how many of those you have free on a disk. One whole sector is used for each file to tell the computer everything it needs to know about it before it tries to read or write to it (the header sector). But I am not going to focus on that, either. I am looking at the part of the disk on which the content of the file is stored.

Now how many file formats are there? Only counting the ones Extended Basic can manipulate on its own, there are four:
INTERNAL/VARIABLE,
INTERNAL/FIXED,
DISPLAY/VARIABLE, and
DISPLAY/FIXED.

With any of these file types the computer never splits a record between two sectors. If there is not enough room on the current sector to store the next record, it will start it on the next sector. This is key to my discussion here.

Starting with variable-length files, the maximum number of characters you can get in one record is 254 (253 in internal in Basic). There are 256 characters in each sector. As a quick technical excursion, the computer uses the other two characters as a length byte and an end-of-record marker (why it needs both is beyond me). Anyway, you can optionally specify the maximum record length when you open a file (in any language; that is the beauty about this article: it applies to all programming languages.) TI-Writer files, for instance, are in DIS/VAR 80 format, so they are limited to 80 characters per record.

Get ready because here comes one of my key points! Why limit the record length to 80 characters when you can have 254? Well, you might say it takes up less disk space. Wrong! Each record in a variable-length file takes up only as many bytes as it needs to. In other words, if you changed every DIS/VAR 80 file to DIS/VAR 254, nothing would change. It would still take up the same amount of space and take the same amount of time to load. Do not do that because TI-Writer will not be able to read them.

So, you might say, why reserve space for each record you will never use? After all, TI-Writer only puts 80 or fewer characters in each record; even if the format were DIS/VAR 254, the extra characters would probably never be used. The point is there is no reason not to. If they had done it this way, DIS/VAR 254 would be the standard that DIS/VAR 80 has become. Then all programs that used DIS/VAR files would be able to read each other's files, although, they may not be able to make sense out of them, but at least the option would be available.

So if you use DIS/VAR or INT/VAR, you should only use 254. The only reason not to now is to maintain compatibility with other programs. And that is unfortunate because that is a good reason. That is what I mean about being too late. Oh, well, if compatibility is not important for a particular program you are working on, then go with 254. This will give you the ability to add more characters to the record should it prove necessary later on without having to change the record length.

Yes, I am also going to attack fixed-length record files. Now the maximum is 255 characters (254 in internal in Basic). The length byte and the end-of-record byte are not needed since all records are the same length. And since that is the case, the number of records in each sector is fixed. So in most cases, there will be unused bytes in every sector. We need to try to reduce the number of unused bytes without taking up more sectors.

Obviously, FIXED 255 does not work because you would only get 1 record per sector no matter how small. I will just give this one to you and tell you the reason afterwards. Take 256 and divide it by the number of bytes you plan to have in each record, and drop the decimal. Then take this number and divide it into 256, and drop the decimal again. This is what you should fix the length at. For those of you who are not mathematically inclined, let me give you a list of numbers: 1 through 19, 21, 23, 25, 28, 32, 36, 42, 51, 64, 85, 128, and 256. You cannot use 256 since the computer cannot represent that number in a single byte, so you have to use 255 instead. If the length you want to fix is not on the list, move up to the next higher number that is. So if you want to use INT/FIX 43, 43 is not on the list, so you would use INT/FIX 51 instead.

Let me explain. You see with 43 bytes per record, you would get 5 records per sector on the disk. That would amount to wasting 41 bytes per sector (trust me). If you use 51 instead, you still get 5 records per sector but only waste 1 byte per sector. Same argument as above against "… but I am reserving more space than I need." You will have the ability to add more characters later on without taking up more space or changing your file format. This will not work out every time. You may have to change the file format some time, but there is no reason to use 43 when 51 costs you no more disk space. The same argument applies to every number not on the list.

This is very important for the numbers between 128 and 255. If you use anything in that range, you will only get 1 record per sector. As an alternative to using 255, you might look for a way to reduce the number of characters in each record especially if you are only a little over 128. If you can get it down to 128, you will not believe the space you will save! Suddenly, you will get two records per sector taking only about half the space!

This rule also applies to variable-length record files. As a case in point, I had a program which used a variable-length record file with 64 records. I put so much space in each record, that the file took up the full 65 sectors (one more for the header sector). So I split it into two files, so I could get more records per sector. On one file I get two records per sector; I get three on the other, so the two files combined now take up only 48 sectors! If I had read this article before, they would both be INT/VAR 254 files. So remember even variable files can benefit from the number list given above. If you can lower the maximum number of characters per record in files with consistently long records, you can save serious disk space. But you would still use VARIABLE 254.

In fixed-length records, the most efficient numbers are the powers of 2 (128, 64, 32, etc.) because they use every byte of a sector. This is why archived files are in INT/FIX 128 format. It wastes no space in the fewest reads.

Whereas all the above applies to any language, in Assembly Language you have the option of using PROGRAM files which use every byte up to the last sector, and are therefore the most efficient (not to mention quicker load time). So in Assembly Language, you might want to think about this format for program-specific files. This is the way fractals are stored in Fractal Explorer.



Go to top of page

Arrays and Sorts by Jim Peterson.

The concept of arrays, and especially of multi-dimensional arrays, is very difficult for many people to grasp. The following is the best explanation that I know of.

A variable name is a box in which you store something. When you write A$="X" you are telling the computer to "go to the box labeled A$ and put the character "X" in it." Or, more accurately, "go to the box labeled A$, throw away anything you find in it, and put "X" in it."

A simple array such as A$(3) is a row, labeled A$, of at least 3 boxes, labeled (1), (2), (3), and maybe more. When you tell the computer that A$(3)="X" you are again telling it to go to the row of boxes labeled A$, find the box labeled (3), and put "X" in it.

A 2-dimensional array such as A$(3,3) is a row, labeled A$, of at least 3 filing cabinets, labeled (1, and (2, and (3, and each having at least 3 drawers labeled 1) and 2) and 3). So, you can use A$(3,3)="X" to tell the computer to find the row of filing cabinets labeled A$, go to the one labeled (3, and open the drawer labeled 3) and put "X" in it.

And in a 3-dimensional array, A$(3,3,3)="X" tells the computer to find the A$ row of cabinets, find the one labeled (3 and find the drawer labeled ,3, and find the folder in that drawer labeled 3) and put …

Finally, you can write A$(2,2,2,2,2,2,2)="X" to tell the computer to find row A$; cabinet (2; drawer ,2; folder ,2; paper 2, in the folder; line 2, on the paper; word 2, on the line; and letter 2) of the word!

Yes, TI Extended Basic can handle 7-dimensional arrays, but it is not very practical. Try running this —
100 DIM A(3,3,3,3,3,3,3)
— and you will get MEMORY FULL IN LINE 100.
Arrays with several dimensions are very wasteful of memory. I don't think I have ever seen a program that used more than a 4-dimensional array, and very rarely more than 3 dimensions.

Now then — A$(J)="X" means "go to the box labeled "J", find the number in it, then go to the row of boxes labeled A$ and find the box in that row which is labeled with that number …"

And even something as horrible-looking as A$(Y(J),Z(A,B))="X" just tells the computer to:
1. Go to box J and find the number in it;
2. Go to row of boxes Y and find the number in box number J of that row;
3. Go to box A and find the number in it;
4. Go to box B and find the number in it;
5. Go to the row of filing cabinets labeled Z, find the one labeled with number A, open the drawer labeled with number B and find the number in it;
6. Go to the row of filing cabinets labeled A$, find the one labeled with the number you found in Y(J), open the drawer labeled with the number you found in Z(A,B) and;
7. Put the "X" in it!

Simple, isn't it?

Remember that, in a multi-dimensional array, only the last dimension holds the value; the others are just pointers to its location. A$(2,3)=A$(3,3) throws out whatever is in the 3rd drawer of the 2nd cabinet of the A$ row, and replaces it with whatever is in the 3rd drawer of the 3rd cabinet of that row, but the contents of the 3rd drawer of the 3rd cabinet are unchanged.

Also remember that box X or box X(1) or cabinet drawer X(1,1) or whatever, contain a 0 until you put something else in; box X$ or X$(1) or drawer X$(1,1) contain nothing at all until you put a string value into them. When you put something in the box, you throw away whatever was previously in the box. And to empty a box without putting anything in, you put a 0 in a numeric box or "" into a string box. Enough, on that subject. Now, when you have all your data crammed into an array, the next thing you will probably need to do is to sort it into alphabetic or numeric sequence.

Sorting is one of the hardest jobs that you can give to a computer, and one of the things that a computer is the slowest at doing. Your TI can figure your bank balance in a split second, but might take half an hour to sort your mailing list.

Here's why. You can sort a bridge hand of 13 cards into sequence in 13 moves or less, by simply pulling out each card and slipping it back into its proper place. But, suppose those 13 cards were in 13 boxes, and you had to sort them without removing them from the boxes, except that you could hold one card in your hand? Even if you could figure out the best way, it would take you far more than 13 moves.

That is the problem that the computer has. You have just learned that the computer stores all those values in labeled boxes, or file drawers, and therefore must sort them by shuffling them from one box to another, emptying a box to shuffle into by holding one value in a temporary box while its value is compared with the others to find its proper place.

Of course, you could just set up a new row of empty boxes, and then search through the old boxes for the lowest value and move that to the first box in your new row, etc. — but that would double the amount of memory that the job would require. This would be no problem for a small array, but the computer can sort small arrays fast enough by the one-row method — it is the largest arrays that are too slow by the one-row method and would need too much memory by the two-row method.

Many ingenious routines have been written to accomplish these one-row sorts. I have written a program called "Sort Watcher" which enables you to actually watch various sorts taking place on the screen. It will also tell you the number of swaps and comparisons that were made.


Go to top of page

This program demonstrates that the time required for a sort increases greatly as the size of the array increases. Sorting an array of 20 does not take just twice as long as sorting an array of 10 — it may take 4 times as long. For this reason, some of the faster and more complex sorting routines divide an array into smaller segments to be individually sorted and then merged.

After an array has been sorted, my program will also let you change any value in any part of the array, and then let you watch the array being resorted. From this, you will learn that a sorting routine which is very fast for a completely random array may be very slow for an array which is already almost in sequence!

In fact, to add just one additional value to a sorted array, the fastest method is the simple "shoehorn" — just set up an empty box at the end of the row, and move each value down by one box until you come to the proper place for the new value.

A sorting routine can be either numeric or alphabetic depending on whether the variable names used are numeric or string. A numeric sort will be in strict numeric sequence and an alphabetic sort will be in ASCII sequence. That means that if all your strings are composed of upper case alphabetic characters, or all are lower case alphabetic characters, you will get an alphabetic sort — but if they are mixed, all of the upper case strings will come before any of the lower case strings, because the upper case ASCIIs are 65-90 and the lower case are 97-122. And if you have lower case words with capitalized initial letters …!

For the same reason, if you perform an alphabet sort of strings containing numeric digits, you will not get a numeric sequence — 10000 will come before 2 because 1 has a lower ASCII code than 2. It would be extremely difficult to devise a sorting routine which could sort numeric digits numerically within strings.

However, if all the numbers are the same length, such as zip codes, the ASCII sort will be numeric. Sorting a multi-dimension array becomes a very complex task. If you swap values around without also swapping all the related values, you will end up with complete garbage. Swapping all the related values takes time, and a dimensioned temporary variable name is also required.

Another way around this is to combine the data from an array into simple strings, or set it up originally as simple strings, and then perform a simple sort based on a specified segment of the string. For instance, you could use TI-Writer with tab settings to create a mailing list having first name at tab 1, second name at tab 15, address at tab 25, city at tab 45, state at tab 55 and zip code at tab 65. Then you could sort into last-name alphabetic sequence by sorting on SEG$(M$(J),10,255), or into zip code sequence by sorting on VAL(SEG$(M$(J),70,5)).

When using TI-Writer to set up such a file, be very sure to save it by PF with the C option, not by SF, and don't leave any blank lines at the end or elsewhere.

Alternatively, elements of data can be crammed into a string separated by control codes, and sorted by position of the code:
FOR J=1 TO 5 :: READ A$ ::
M$=M$&CHR$(J)&A$ :: NEXT J
and then sort on element X by:
SEG$(M$(J),POS(M$(J),CHR$(X)
,1),255)



Go to top of page

[ TI Book front page    |    TI Resources Page    |   PC99 Review    |   PC99 Programs ]