Columns


Questions & Answers

Problems With float

Ken Pugh


Kenneth Pugh, a principal in Pugh-Killeen Associates, teaches C language courses for corporations. He is the author of C Language for Programmers and All On C, and was a member on the ANSI C committee. He also does custom C programming for communications, graphics, image databases, and hypertext. His address is 4201 University Dr., Suite 102, Durham, NC 27707. You may fax questions for Ken to (919) 493-4390. When you hear the answering message, press the * button on your telephone. Ken also receives email at kpugh@dukemvs.ac.duke.edu (Internet).

Q

I have been programming in C for more than three years. I thought I understood the language fairly well, until I encountered this nasty problem. Could you explain to me what is happening in the following program? I compiled everything with Microsoft C 5.1, without any options.

The program consists of a main program and two small subroutines: one that writes a floating point number at the starting position of a file and one that reads a floating point number at the same position in the same file. The writing routine receives its only parameter (the floating point number to write) by value. The reading routine has no arguments, and returns the floating point number read.

In the main program (Listing 1) , I open an (existing) file, write a floating point number at the starting position, and read it again with the reading routine. Everything OK. Then I call the writing routine to write the same floating point number, and the reading routine to read it out again. This does not seem to work. The value read does not equal the value written (see program test1.c).

In a second version, test2.c, (Listing 2) I could work around the problem, by changing the writing routine a little. I first copied the function argument to a local variable, which I then wrote to the file. This works correctly, although I do wonder why I get 123.449997 instead of 123.450000. (One would expect that the variable read contains exactly the same bitpattern as the variable written, so that it prints the same way, too.)

I thought something might be wrong with the way in which the writing routine receives its parameter, so I declared the function with an argument list, test3.c (not shown here). When compiling this program, I got a warning at line 86 (the header of the writing routine): "parameter 1 declaration different".

Then I split the program into two parts: test4.c (not shown here) and test4a.c (Listing 3) , one containing the main program and the function declarations, the other containing the two subroutines. This compiled without warnings, and the floating point number was read correctly. However, I could not print the value of the floating point number in the writing routine (although it was correctly written to the file).

In my last test I replaced all floats with doubles. All four versions worked well.

I look forward to reading your answers in CUJ (which, by the way, I recommend to any C programmer I meet).

Hugo Calleens
Deinze, Belgium

A

This is an example of float to double parameter conversion. In the K&R version of C, all floating point parameters were passed as doubles. Even if you declared the parameter as a float, it was treated as a double. This simplified the life of the compiler. Prior to prototypes, it had no idea of the type (float or double) the parameter was declared in the called function. (This also happened with char parameters that were treated as ints).

You could have declared:

int w(f)
float f;
as

int w(f)
double f;
and the same eight bytes would be allocated for f. When you wrote out the value of f, you wrote sizeof(float) bytes, which only wrote the first four bytes out. That was not the most-significant part of the eight-byte number. (If it had been, you would have just had a slight rounding problem, rather than a complete error). If you had specified sizeof(f), you would have written eight bytes.

ANSI C does not require that the values of function parameters declared floats (in both the function and the prototype) be passed as floats. Similarly, it does not require that calculations involving floats be done as floats.

One solution is to do just as you mentioned — use doubles everywhere. When I teach my courses, I usually don't mention the float type until the very end of the course. The only reason I use the float type is to save room if I have a large array of floating-point numbers. It is actually faster in many cases to use doubles rather than floats. Many math co-processors require eight-byte values. floats must be converted to doubles before they can be used in those processors. And the result must be reconverted back to a float.

If you add the function print_hex (Listing 4) , your problems will be more apparent. Try calling it in various places with:

print_hex(&f, sizeof(float));
print_hex(&f, sizeof(double));
print_hex(&f, sizeof(f));
Q

I began reading your column about two years ago when I started teaching myself C. After initial struggles, I decided that the best method would be to use K&R (second edition) as a course text, and solve all the exercises throughout the book. Although I have now finished the problems in Chapter 1, Exercise 1-18 has given some rather unusual warning messages upon compilation. The code (Listing 5) was compiled using Quick C v2.5 with the small memory model and the warning level set to 1.

All the warnings surround the function remove and appear to refer to the number of parameters. However, my inspection of the code reveals that all references to remove involve three parameters. The solution may by obvious, but I just don't see it. Can you help?

Joseph S. Alessi
Morristown, NJ

A

This is a subtle problem. QuickC has defined a function remove in its library and included the prototype for it in <stdio.h>. It has a prototype of:

int _CDECL remove(const char *)
Its purpose is to delete the file specified by filename. Your function conflicts with that function prototype.

This is not strictly ANSI compatible. The Standard states that "each header declares and defines only those identifiers listed in its associated section." The section in this case refers to the input and output functions in the Standard C library. It may contain identifiers for compiler internal functions (whose names begin with an underscore).

The fix? Rename your function or remove the prototype from <stdio.h>. Your choice. (KP)

Readers' Replies

Capturing Video Screen

In the February issue of The C Users Journal there was a question from a Matt Nowicki of Millersville, MD regarding how to capture a video screen. I have several answers for him.

1. Unless you are using this as a learning exercise, why write a screen-capture program? With the software which came with the DAK Soul Snatcher, there are two programs which will capture screens, the Scan functions of PC PAINTBRUSH and the TSR which comes with PC PAINTBRUSH called FRIEZE. Directions for using both come with the manuals. If you don't wish to use either one of these, there are several good VGA screen-capture utilities available on your local BBS or Compuserve.

2. I have a set of routines for capturing images from the Soul Snatcher available as shareware. These routines are predominantly C with some assembler for time-critical functions. There are three packages available.

The first package includes just the executables for a replacement for VID, which will capture multiple screens, print dithered images directly to a Laserjet or compatible, and save files in .VID (32 shade of grey) or .PCX formats. It also includes a frame grabber that can grab as many frames of 32 or 64 shades of grey as your conventional and EMS memory can hold, and a viewer to play these video images back. (The grabber can grab images at about 2 frames/second on a 20MHz 386 AT clone).

The second package includes the source to the programs listed above as well as a Large Model Microsoft C 5.1 library, which contains the interface routines to the Soul Snatcher. The source for the interface library is not included.

The third package includes the source to the programs as well as the source to the library routines.

If Matt really wants to write a screen-save routine, a good source of information is Micheal Abrash's ON Graphics columns in The Programmer's Journal — in particular, his column in Volume 6.6 which appeared sometime in 1988. I believe he also discusses screen-capture routines in his book, which I think is also called On Graphics.

Ken Graham
Hanover Park, IL

The packages are available from Ken Graham, 4715 Zeppelin Drive, Hanover Park, IL 60103 for $10, $25, and $35 respectively. (KP)

Debugging Problems

In response to the letter form Mark Petrovic (September 1990, p. 111) and the reply from Janet Price (January 1991, p. 89):

I phoned Mark back in September because I have experienced exactly the problem he describes. From your response, and Ms. Price's, I'd say that neither of you have had the pleasure. Mark's description of the problem is both precise and definitive. Like the pointer problems which you mention in your reply, Mark's problem is so troubling because its effect and its apparent solution are so disparate. Everybody knows that adding a printf statement can't prevent your code from locking up.

Like Mark, however, I have defeated the lockup bug on occasion by adding a printf (or mprintf) here and there in my code. The purpose of adding such statements is to observe the values of the variables. It is frustrating when the values are as expected, and the lockup bug vanishes. It's even more frustrating when I remove the printf and the bug returns.

This is not a frequent occurrence. I've been aware of it perhaps four or five times in as many years. That just makes it all the more difficult to resolve. I'm writing now because it has happened to me again. This time I've been able to duplicate the problem, and to solve it.

While I was making changes in some new code, I got a malloc error in a routine I've used for six months. I hadn't made changes to the old code, so I felt the problem was in the new material. I added a printf which would report the size of the allocation, and recompiled. The malloc error disappeared. Further, I could toggle the error by adding or removing the printf in the new routine.

I examined my new code for calls to the old routine. There were four calls. Two of these took the return value, an int (my old routine frees its own allocation before returning), and made use of it. The other two ignored the return value. The toggling printf was located after one of the calls that ignored the return value. I added local variables to receive this return value, even though the code didn't need it. This solved the problem. I could remove the toggling printf.

Turbo C v1.5 provides a great deal of help by way of error messages and warnings during the compile and link. One of those warnings is: "VarName is assigned a value which is never used in function FuncName." Because of this warning I have often in the past removed the unused variable without a thought, while looking for more serious problems. I believe it is this subtle change in the code, intended to eliminate a warning, that causes the mysterious appearance of the problem which Mark describes.

I have a new rule for my Nature of C collection: if a function returns a value, take it. If I don't need the value, I assign it to a variable with an obviously pointed name, like TakeValue. Then I know not to remove this variable when Turbo C warns me about it.

I don't know if this is a bug in Turbo C. It could be, but I wouldn't make the accusation. I don't even know what compiler Mark uses, and I don't know anyone else who has experienced this problem. It could just be that I've failed to provide function declarations at some point, though I'm usually pretty careful about that.

Also, I don't know how many people would feel that I've found the real problem. Adding an unnecessary local variable may appear just as irrelevant as printing an unnecessary value, equally foreign to the real problem. But I believe the problem has to do with leaving return values on the stack. If that's it, then taking those values off the stack really is the solution to the problem.

Art Shipman
Westbrookville, NY

I wish that I'd "never have had the pleasure." I probably have it about once every two years. But when it strikes, I cringe. Experience is the best teacher — the more mistakes you make in C, the better a debugger you become.

The last time was about six months ago, so maybe I can rest easy for a while. It was the same sort of subtle bug. When the printf was put in, the program worked. When it was out, it didn't. The #ifdef DEBUG statements were being stuffed all over the place. The job needed to get done by the end of the day. You know the scenario.

As I mentioned in my original reply to Mark, the potential source of the error is either a pointer problem or a compiler bug. Although the latter is rare, it does crop up sometimes. However I tend to blame myself first.

There are at least three things that are affected by the insertion of the printf. The first is that the location of the remainder of the code is shifted down by some bytes. The second is that the code generation itself may be affected, as you suggest. The third is the position of static data. The string itself is stored in the initialized data section.

An obvious overrun (at least it was obvious in the end) of the index into a static array was the culprit. With the printf in there, a trivial static variable was altered. Without it, a less trivial one was being changed.

I'm reminded of one of my quirkiest debugging experiences. I had a function called xxx and an external variable called xxx (in another source file). These weren't their real names. My memory fails me on this one, but it was well before the days of 32-character names, and abbreviated names tended to look alike.

The linker did not report a double definition of this identifier when the files were linked. It linked the two names together. The external variable was placed at the same address as the beginning of the function. The first time I called the function, everything was okay. In another part of the program, I assigned something to the external variable, xxx = 5. Guess what happened the next time I called the function?

I agree with you on your statement of the Nature of C. Except for the return from printf and a few other functions, I make it a rule to always retrieve the return value from any function. If it gives something back, it is for a reason. (KP)