Dear Mr. Ward:It's been a long time since I've written to a magazine about something I read, simply because I usually don't have the time. However, since I've been catching up on my reading while waiting for a big project to MAKE, I've had the opportunity to read several back issues of CUJ in succession, and found two things nearly simultaneously that I feel need responding to. Hence, I write today.
The first item I'd like to address is the review in the March 1990 issue of The HALO Graphics Library. Since I'm working with some graphics programming, and expect to do more in the future, I was quite interested in learning more about how this popular system works. In fact, I was quite glad when I got to the second paragraph which read "The BARGRAPH application in Listing 1 demonstrates the style and ease with which HALO can be integrated with C programs," since this is exactly the environment I'd be using, and I'd like to know how easy it might be. I immediately started flipping pages to find Listing 1 so that I could examine the code. Unfortunately, I found the end of the magazine before the referenced Listing, nor could I find it anywhere else in the issue. Could you send me a copy of the missing listing, or better yet, publish it in a future issue so that all of us interested in this application could review it? I'm sure there are other people out here who felt the same frustration.
My second item comes from reading the April 1990 "We Have Mail," in which a reader pointed out Leor Zolman's misunderstanding of the DOS SUBST command. By themselves, Mr. Zolman's initial errors could be forgiven as a UNIX user trying to live in a "foreign" environment with poor documentation. His response, however, certainly exacerbated the problem, adding confusion to the picture rather than more information. I don't know what DOS manual he's using, but I don't think it's one that came from Microsoft if it didn't mention using SUBST by itself to list the current drive assignments. As for the rest of his comments:
1. "After selecting a virtual drive defined via subst, there are two different notations for specifying the full pathname of any file on the virtual drive..." This is true. Is there any harm in doing so? "...but no way to access those portions of the file tree that reside 'above' the base of the virtual drive without reverting to 'real' drive notation." This is also true. The primary purposes of the SUBST command are either to provide a shorthand notation for accessing files deep within a nested hierarchy to avoid the 63-character pathname limit, or to allow software to be run which expects certain drives to contain their files, and to use a hard disk subdirectory to hold those files. It should not be expected to provide access to parent directories of the SUBSTituted path: What meaning is there in accessing the parent of the root directory of any other drive designation? Allowing parental access through SUBST would not be consistent with drive characteristics.
2. "After a virtual drive has been assigned with subst, any redefinition of that drive is prohibited by DOS." This is absolutely not true! I have a WH.BAT in each of my project areas which (among other things) sets up a set of virtual drives for the task I'm working on. My compiler then knows that my source files are always on drive W:, my include files on drive I:, and that the object files are to be placed on drive 0:. Drives I: and 0: are the same most of the time, but W: has to change constantly. It's easy:
SUBST W: /D SUBST W: .The first command removes the old W: SUBSTitution, the second assigns it to the current directory. Voila, I'm working in a new environment.3. "Finally, to be able to use subst at all, CONFIG. SYS must be changed and the system re-booted." It took me a while to figure this one out, since I changed my CONFIG.SYS so long ago and forgot about it. The command Mr. Zolman is talking about is LASTDRIVE. In order to reference drive letters beyond E: if you don't have a physical disk there, DOS has to be informed of your intent so that it can allocate its internal data structures (such as for keeping a record of the current directory on the SUBSTituted drive). On my system at home, which lives in its own world, I've got LASTDRIVE=Z in the CONFIG.SYS file so I can use any drive letter I want, for any purpose I want. At work, it's a little different, since NetWare only starts assigning drive letters after whatever LASTDRIVE is set to, so I have to have LASTDRIVE=I to match the way everyone else has their network drives mapped. Hence, my working directory is drive H:, rather than drive W:. To modify my environment to match the new system, I only had to modify the batch files which reference the current working directory (such as the one which runs my compiler). Now my programming environments work the same at home as at work, and I don't have to worry about remembering which command set to use where. Like I said, I set up CONFIG. SYS once and forgot about it. Did I worry about having to re-boot the computer when I did that? No, since I was also installing a RAM disk driver, a mouse driver, and a bunch of other things at the same time. There's no harm in potentially having extra drive letters around, unless you're so cramped for memory that a few hundred bytes will make the difference. In that case, you want to change operating systems anyway, so why worry about SUBST not misbehaving the way you think it should?
I can understand and appreciate Mr. Zolman's desire to make his different environments work in similar manners, and the desire to reduce the number of keystrokes to perform a given operation. WH.BAT, for example, used to be WORKHERE.BAT until I got tired of typing the longer name. I've also got an extensive set of two-letter-named batch files that I use for most of the common things I do, and reprogrammed the function keys to enter longer command strings. DOS has a lot of tools available to make it work more like you want it to if you're not satisfied with its user interface. Saying that you're not going to use one of the more powerful tools available because it doesn't work quite the way you think it should shows that you really don't want to be using DOS anyway, or that you haven't spent enough time reading the documentation. If you want to be a UNIX programmer, why knock DOS? It's not a multitasking, multiuser operating system. It does, however, have a lot of good software available simply because there is one programming interface for all of the computers it runs on, not over thirty dialects that have to be accounted for to get a reasonable market. Hopefully there will be a similar multitasking standard someday that takes full advantage of all of the powerful computing platforms now available. Until that happens, I'm probably going to stick to programming for DOS for my own projects because there is a big market for a relatively small investment.
I must say that I'm generally quite impressed with your magazine, and look forward to getting a new issue each month. However, my subscription has lapsed, so they've stopped coming at the moment. I've therefore enclosed my renewal order, so that I can once again look forward to CUJ on a regular basis.
Sincerely,
Fred Koschara
Box 617 - Kenmore Station
Boston, MA 02215I'm terribly sorry to hear about the problem with the Listing in the April issue. Of course, I'd love to fix that problem for you, but the manufacturer's warranty on that issue has long since expired. May I suggest that in the future you read your magazines sooner?
Seriously, when preparing the Halo story for print, we chose to delete the listing to save space. Unfortunately we didn't enforce that decision everywhere during copy editing. We're sending you a copy, and we'll also put the listing on the code disk for that issue.
Thanks for the SUBST information. I don't think Leor really meant to knock MS-DOS just to explore some system level techniques and show how to "gloss over" system differences. rlw
Dear Mr. Ward:
I would like to comment on a minor point of Dan Saks' second article on writing your own standard headers (March 1990, volume 8, number 3, page 95). If a programmer is concerned about "eliminating some irritating portability problems", he cannot afford to choose a declaration style based on the compiler currently to hand. Thus Mr. Saks' suggestion to write a declaration for malloc as either
typedef char *void star; void star malloc(),or
void *malloc();depending on what your compiler will support is not a good idea. If you choose the latter and then try to port code to a platform whose compiler does not support generic pointers, you will find yourself with an unpleasant (or at least time consuming) editing task to perform at the point of all uses of void *. Rather you should choose a declaration style that can handle the worst case and use it religiously. Such as:
typedef char *Gptr; /* Generic pointer */ typedef short Undfnd; /* Undefined type */ Gptr malloc(); Undfnd free();Then when you find yourself using a more advanced compiler you re-write only your keyword definitions to take advantage of the compiler's support, as in:
typedef void *Gptr; typedef void Undfnd;A more significant issue is the application of this general principal to all the base types of C. In my work, I never use C's type keywords directly since in pre-ANSI days they provided no guarantees. For example, it may or may not have been possible to store the signed literal 35000 in an int. long int and short int were not a complete solution since they could be equivalent. The easy solution was to use C's flexible type construction to define type keywords that guaranteed a minimum size. For a given compiler these might look like:
typedef char Intl; typedef int Int2; typedef long Int4;If I ported applications to a platform with a different size of 'plain integer', I needed only adjust the definitions of these keywords.Even if all non-ANSI compilers disappeared tomorrow, I would continue to use this technique for its documentation benefits because I feel it might give me the upper hand in a nasty porting situation.
Sincerely,
Corey F. Huber
Fraser Consulting Inc.
1 Liberty Street
Cazenovia, NY 13035Dan's Reply:
As I stated in my first article on standard headers (January 1990), my primary intent is to help people port ANSI C code (found in current literature) to traditional compilers. Newer code uses headers that are often unavailable on older compilers. My suggestion is "don't change the code, write your own headers."
The headers define symbols that provide a portable interface to the C environment, but the headers themselves need not be portable. It doesn't matter that the definition for malloc() may be slightly different on different compilers; it's hidden in <stdlib.h>. It only matters that <stdlib.h> is defined everywhere so that code using it is portable.
I wrote in my article that putting definitions such as void (and maybe void star) in <quirks.h> "simplifies writing the standard [headers] and eliminates some irritating portability problems." My primary concern is making it easier to write the headers. If, as a bonus, <quirks.h> eliminates some other portability problems, so much the better.
It seems to me that Mr. Huber is simply suggesting the use of names Undfnd and Gptr in place of my suggested void and void star. If you use my names, you can compile ANSI C code such as
void f(p) char *p;on older compilers without change. Using his suggestion, you have to change the void to Undfnd, or add a definition for void (which is my idea). I see no advantage to Undfnd. If you port ANSI C code with
void *g(n) size t n;you might have to change the void * to void star or Gptr. Here, the porting effort is identical; the choice between the void star and Gptr is purely personal preference. I agree that careful use of defined types for some built-in types makes it easier to write highly portable code, but I'm afraid I don't see that definitions like
typedef long int Int4;help very much. For example, if you are using an old C compiler in which both int and long int are 16-bits, then how do you define Int4 so that you can manipulate 35000 as a positive signed int whose value is distinct from the negative value with the same 16-bit representation? You could define
typedef struct {unsigned lo; int hi;} Int4;to create a 32-bit signed integer type, but then you can't apply operators like + and to it (you'll need macros or functions), and you have write literals like 35000 as structured constants, e.g,
Int4 L35000 = {35000, 0};If you follow Mr. Huber's advice and write in a style that anticipates the worst possible case, then you have to write all your code assuming 16-bit long ints. It's probably easier to write a pre-compiler to handle 32-bit long ints than to write all your C code in this style. Any code which treats 35000 as a signed integer must assume that long int is bigger than 16 bits (typically 32 bits). You might as well call that type long int.Dear Sirs:
I am impressed by your ability to print useful and readable material in each issue, and by your continued interest in numerical applications. Unfortunately, the examples in Sheppard's article "Evaluating Your Floating Point Library" don't constitute a very useful test suite, although I found some challenge in trying to understand the results.
About the only thing tested by the first three listings is the underflow behavior. Figure 2 is interesting in that it shows the effect of IEEE gradual underflow, where precision is lost gradually as the number is reduced from FLT_MIN to FLT_MIN*FLT_EPSILON, which is the point of total underflow.
Listing 3 would have showed undesirable behavior in the Microsoft 8080 library, if there had been a C compiler using the same poorly coded functions. A properly handled internal underflow will not have any effect. About the only likely problem would be if the function started out by multiplying the argument by 2/PI, when accuracy could be lost for arguments less than LDBL_MIN*PI/2. As the author points out, the function probably involves a series or a rational polynomial, but only the first term is large enough to affect results for small arguments. If the coder has cheated on the number of terms in the series, it wouldn't necessarily show up in this test.
Listing 4 is interesting in that inequality of the results should never occur for the test values which are squares, even if the arithmetic doesn't fully meet IEEE standards. In fact, these are the values for which the author found the worst discrepancies. I tried various software and hardware floating point options, but I could not produce errors in these values. With normal rounding, FLT_ROUNDS=1, none of the errors were greater than DBL_EPSILON*x. I had to set the FLT_ROUNDS=2 mode to get all of the non-zero errors to come out negative, so there must be something strange about the arithmetic tested by the author.
This reminds me of the old Honeywell 6000, where the accuracy of sqrt() could be improved greatly by changing the last step from
return (x/y+y)/2;to
(void)frexp(x/y,&exp); return (x/y+ldexp(.5,exp-DBL_MANT_DIG)+y)/2;which worked well because x/y was calculated in FLT_ROUNDS=0 double arithmetic, with the remaining operations performed in long double and final rounding in FLT_ROUNDS=1 mode. These extra steps could be performed by a single OR instruction when coding in assembly. If there were more than one upward rounding operation, results similar to Sheppard's would be produced.The Paranoia code contains a fairly complete test of the sqrt() function which will show whether tricks like the one above have worked. I believe that there exist test suites for the elementary functions, but they are not covered by the IEEE standard. I would be interested to know if there are good public domain test suites written in C or which could be translated to C.
Sincerely,
Tim Prince
39 Harbor Hill Dr.
Grosse Pte Farms NI 48237-3747I too would be interested in other floating point test suites, public domain or not. If you have some experience with some, or ideas for how to construct a good one, write. We'll share the most useful here. rlw