MORE INFORMATION
Answers to Common Questions About Microsoft QuickBasic
Version 4.50
This article is intended for individuals who are using QuickBasic
version 4.50 for the IBM PC and compatibles, and provides answers to
common questions, along with supplementary information on QuickBasic
version 4.50.
The following is an outline of topics covered by this article:
- MEMORY MANAGEMENT AND MODULAR PROGRAMMING
- COMPATIBILITIES
- Mixed-language programming version-compatibility list
- Video problems
- HINTS AND SUGGESTIONS
- How to use "COM1:" and "COM2:" communications
- Example of key trapping
- Floating point affects execution speed
- Limitations of floating-point mathematics
- Common floating-point errors
- Reference books for floating-point mathematics
- Reference books for QuickBasic
Memory Management and Modular Programming
Modular Programming
Question:
What is modular programming? How do I modularize a program?
Response:
QuickBasic has several features to help you create programs that are
organized in a logical and structured manner. This style of
programming is often referred to as "modular" programming. There are
many advantages to modular programming; the most important advantage
is that the logic of a modular program is clearer.
A modular program is structured in such a way that the various
functional blocks of source code are isolated in separate procedures.
This makes it easy to reuse particular procedures in other programming
projects and greatly simplifies debugging. In a modular program,
problems can be quickly isolated to a given procedure. The procedure
can then be tested and debugged separately, without you having to
consider the remainder of the program.
QuickBasic Modular Programming Vocabulary
There are several important terms used by QuickBasic programmers when
talking about program structure. It is important to understand these
terms in order to fully utilize the QuickBasic programming
environment.
- Module: A section of program source code that is stored in a
separate file and can be separately compiled. Every program has at
least one module (the main module) and may consist of many modules
(support modules). The modules are separately compiled and linked
together to produce a finished executable program.
- Procedure: A general term referring to either SUBs or FUNCTIONs in
QuickBasic. Conceptually, a procedure is a functional block of code
that performs a specific action and that is logically distinct from
the rest of the program.
- SUB: Short for SUBProgram. A block of QuickBasic code is delimited
by SUB....END SUB. Do not confuse this with a subroutine.
Subroutines are blocks of code that are terminated with a RETURN
statement and were used in older forms of Basic.
- FUNCTION: A block of code that is delimited by FUNCTION....END
FUNCTION. Similar to SUBs except that FUNCTIONs are used when a
procedure is needed that returns a single value. Do not confuse
FUNCTIONs with the older DEF FN functions.
- Module level code: The code that is found at the module level,
which is outside of any procedure.
- Procedure level code: The code that is found inside a SUB or
FUNCTION procedure.
QuickBasic Modular Programming Features
By pressing the F2 key or choosing View Subs from the View menu inside
QuickBasic, you can see an outline view of the program that is
currently loaded. The resulting window will show each module name, and
each of the procedures in that module will be indented under it. Each
editing window in QuickBasic shows the code of one procedure at a
time. Use the View Subs display window to select the procedure that
you want to edit. Also, from this same display, you can delete and
move procedures at will. Whenever you enter the reserved word SUB or
FUNCTION, followed by a procedure name, QuickBasic creates a new
editing window for you so that you can add the code for that
procedure.
The fastest way to program in QuickBasic is to design your program so
that the main module-level code calls procedures. Just determine each
action that the program must perform and then write a procedure to
accomplish that task. Then create the module-level code to tie all of
the procedures together.
Execution starts at the beginning of the main module-level code. If
your program requires more than one module, your support modules will
contain additional procedures; however, note that since the only way
to transfer control across modules is to call a procedure, module-
level code in support modules is unreachable. This means that support
modules contain only declarative statements and procedures. You will
never have any executable statements in the module-level code of a
support module. The flow of control, for the overall program, is
governed by the module-level code of your main module.
Memory Management, the Big Picture
Question:
How do I get more string space?
Response:
There are several things to keep in mind when programming in
QuickBasic. Because Basic manages memory for you, you need to
understand the limitations of the Basic memory-management system. The
most important points for the programmer are the following:
- Your program receives one 64K code segment for each module that it
contains. If the code generated by the compiler for a given module
approaches 64K, you must add a new module to your program to
continue adding code. The easiest way to determine if you need to
add a new module is to look at the .MAP file produced by the
linker, LINK.EXE. You will notice at the top of the .MAP file
listing that there is a segment with the same name as your module
with an _CODE appended, listed under the BC_CODE class. This is the
code segment for that module and must be smaller than 64K.
- Your entire executable program, no matter how many modules it
contains, has only one near data segment. All ordinary variables,
all variable-length STRINGs, and your stack are located in this
segment. The FRE() function can be used to determine if you are
close to filling up this segment. The "Out of STRING Space" error
indicates that this segment is full.
Usually, the best approach to minimizing your usage of the near
data segment is to move as many items as possible out into the far
heap. However, the Basic memory management engine allows only
certain types of data items to be placed in far memory. These items
are DYNAMIC arrays of numeric data or fixed-length STRINGs.
Variable-length STRINGs never go in the far heap.
The easiest way to move these items out of the near data segment is
to make all arrays DYNAMIC unless there is a specific reason to
keep them STATIC. Usually, the program structure shown below is the
simplest organization:
Pseudo Code
-----------
DIM all STATIC arrays first
COMMON statements next
REM $DYNAMIC
DIM all DYNAMIC arrays here,
including the ones placed in COMMON
Also, if practical, use only arrays of fixed-length STRINGs.
Remember, arrays of variable-length STRINGs cannot go into the far
heap. These techniques will minimize the portion of the near data
segment that your program's data requires, leaving more room for
conventional variables and variable-length STRINGs. If any of your
DYNAMIC arrays must be larger than 64K, you will need to start
QuickBasic or compile with the /Ah option.
Note: Microsoft Basic Professional Development System (PDS) 7.00
offers support for far variable-length strings and Expanded Memory
Specification (EMS 4.0), while giving your programs more code and
data space so that you can develop dramatically larger programs for
MS-DOS or OS/2.
COMPATIBILITIES
Question:
Which versions of Microsoft FORTRAN, Macro Assembler, Pascal, C
Compiler, and QuickC are required with QuickBasic 4.50 for mixed-
language programming?
Response:
QuickBasic version 4.50 creates .OBJ modules that can be linked with
.OBJ modules from the following languages:
Microsoft Pascal Version 4.00
Microsoft FORTRAN versions 4.10 and 5.00
Microsoft C version 5.10 and QuickC versions 1.01 and 2.00
Microsoft Macro Assembler (MASM) versions 5.00 and later
Programs should be assembled/compiled using the medium, large, or huge
memory model to be compatible with compiled Basic, which is
effectively in the medium memory model.
Video Problems
QuickBasic version 4.50 is more selective of the video hardware on
which it will operate than QuickBasic versions 4.00 and 4.00b.
QuickBasic requires a video card that is 100-percent compatible with
an IBM CGA, EGA, VGA, or Hercules Monochrome card.
If QB.EXE version 4.50 does not operate with your video system, try
invoking QuickBasic with each of the video-specific options, such as:
Option Description
----- -----------
/b (black-and-white) option
/nohi (no high-intensity) option
/g (update screen as fast as possible) option
/h (high-resolution) option
Example:
qb /b
Also, try setting the video mode from MS-DOS using the MODE command
before initiating QuickBasic (for example, MODE CO80 or MODE BW80).
Example:
MODE CO80
qb
MODE BW80
qb
If a ghost image appears after running QuickBasic on your video
system, use the MS-DOS MODE command to clear the screen if CLS doesn't
clear it.
The following is a list of known compatibility problems:
- According to Microsoft's testing, the following cards loaded
QB.EXE, but had numerous problems with screen swapping:
Tecmar VGA
Quadram VGA
Vega Video Seven FastWrite VGA
Vega VGA (a customer suggested QB /H for better Vega VGA
behavior)
- According to Microsoft's testing, the following cards will not load
QB.EXE:
COMPAQ Laptop (BIOS problem -- no correction)
COMPAQ SLT/286 (okay with AC power, fails with battery unless
you disable the power-conservation utility PWRCON.EXE or
PWRCON.COM.)
Genoa SuperVGA HiRes
ATI VIP VGA
Sigma EGA
HINTS AND SUGGESTIONS
Question:
How should I set up my COMmunications line in QuickBasic 4.50?
Response:
If you have problems using COM1 or COM2, try the following OPEN
statement, which makes Basic as tolerant as possible of hardware-
related problems:
OPEN "COM1:300,N,8,1,BIN,CD0,CS0,DS0,OP0,RS,TB2048,RB2048" AS #1
(This OPEN is FOR RANDOM access.) The following is an explanation of
each recommended parameter used in this OPEN statement:
- The higher the baud rate, the greater the chances for problems;
thus, 300 baud is unlikely to give you problems. 2400 baud is the
highest speed possible over most telephone lines, due to their
limited high-frequency capability. 19,200 baud, which requires a
direct wire connection, is most likely to cause problems. (Possible
baud rates for QuickBasic are 75, 110, 150, 300, 600, 1200, 1800,
2400, 4800, 9600, and 19,200.)
- Parity usually does not help you significantly; because of this,
you should try No parity (N).
For those devices that require parity, you should use the PE option
(Parity Enable, required to turn on parity checking) in the OPEN
COM statement. When the PE option turns on parity checking, a
"Device I/O error" occurs if the two communicating programs have
two different parities. (Parity can be even, odd, none, space, or
mark.) For example, a "Device I/O error" occurs when two programs
try to talk to each other across a serial line using the following
two different OPEN COM statements:
OPEN "COM1:1200,O,7,2,PE" FOR RANDOM AS #1
and
OPEN "COM2:1200,E,7,2,PE" FOR RANDOM AS #2
If the PE option is removed from the OPEN COM statements above, no
error message displays.
- 8 data bits require No parity (N) because of the size limit for
Basic's communications data frame (10 bits).
- The BIN (binary mode) is the default. Note: The ASC option does NOT
support XON/XOFF protocol, and the XON and XOFF characters are
passed without special handling.
- Ignoring hardware handshaking often corrects many problems. Thus,
if your application does not require handshaking, you should try
turning off the following hardware line-checking:
CD0 = Turns off time-out for Data Carrier Detect (DCD) line.
CS0 = Turns off time-out for Clear To Send (CTS) line.
DS0 = Turns off time-out for Data Set Ready (DSR) line.
OP0 = Turns off time-out for a successful OPEN.
- RS suppresses detection of Request To Send (RTS).
- For buffer-related problems, try increasing the transmit and
receive buffer sizes above the 512-byte default:
TB2048 = Increases the transmit buffer size to 2048 bytes.
RB2048 = Increases the receive buffer size to 2048 bytes.
A larger receive buffer can help you work around Basic delays
caused by statements such as PAINT, which use the processor
intensively.
The following are additional important hints for troubleshooting
communications problems:
- You should use the INPUT$(x) function in conjunction with the
LOC(n) function to receive all input from the communications device
[where x is the number of characters returned by LOC(n), which is
the number of characters in the input queue waiting to be read; n
is the file number that you OPENed for "COM1:" or "COM2:"]. Avoid
using the INPUT#n statement to input from the communications port
because INPUT#n waits for a carriage return (ASCII 13) character.
Avoid using the GET#n statement for communications because GET#n
waits for the buffer to fill (and buffer overrun could then occur).
- For an example of data communications, please refer to the
TERMINAL.BAS sample program that comes on the release disk for
QuickBasic version 4.50. Many communications problems may actually
be due to inappropriate source code design and flow of control.
- Many communications problems can be shown only on certain hardware
configurations and are difficult to resolve or duplicate on other
computers. We recommend experimenting with a direct connection
(with a short null-modem cable) instead of with a phone/modem link
between sender and receiver to isolate problems on a given
configuration.
- The wiring schemes for cables vary widely. Check the pin wiring on
your cable. For direct cable connections, a long or high-resistance
cable is more likely to cause problems than a short, low-resistance
cable.
- If both "COM1:" and "COM2:" are open, "COM2:" will be serviced
first. At high baud rates, "COM1:" can lose characters when
competing for processor time with "COM2:".
- Using the ON COM GOSUB statement instead of polling the LOC(n)
function to detect communications input can sometimes help you work
around timing or buffering problems caused by delays in Basic.
Delays in Basic can be caused by string-space garbage collection,
PAINT statements, or other operations that heavily use the
processor.
Many commercial communications programs use sophisticated techniques
not found in Microsoft Basic, and may give better performance.
If you need better communications performance than you are getting
from Basic, you may want to try Microsoft C. (You can call Microsoft C
versions 5.x routines from QuickBasic version 4.50.)
Question:
Why doesn't QuickBasic support COM3 and COM4 serial ports?
Response:
Support for these two additional communications ports requires a
larger code size for QuickBasic in both the compiler and the run-time
module. Therefore, the decision was made NOT to support COM3 and COM4.
The QuickBasic compiler supports the use of serial communications
ports COM1 and COM2 through the use of the OPEN "COM" statement.
To access COM3 and COM4, it is possible for compiled Basic to call
third-party library routines, which are listed in catalogs such as the
"Provantage Buyer's Guide" [in the United States: (800)
336-1166, in Canada: (800) 225-1166, Customer Service: (216) 494-8899].
For example, you may want to contact the Software Interphase Company
to determine if its QuickComm product supports COM3 and COM4, which
are called from Microsoft compiled Basic.
Question:
Can you give an example of key trapping?
Response:
Pressing any key in combination with CTRL, SHIFT, ALT, CAPS LOCK, or
NUM LOCK changes the keyboard scan code. To trap combinations of keys,
the KEY statement requires adding together the values of the keyboard
flags as shown in the code example below.
The following is a code example:
CONST alt = &H8
CONST noflag = &H0
CONST leftshift = &H1
CONST rightshift = &H2
CONST ctrl = &H4
CONST numlock = &H20
CONST capslock = &H40
CONST extendedkeyboard = &H80
CONST left = &H4B
CONST right = &H4D
CONST up = &H48
CONST down = &H50
CONST C = &H2E
CONST scrolllock = &H46
KEY 15, CHR$(extendedkeyboard + numlock) + CHR$(left)
KEY 16, CHR$(extendedkeyboard + numlock) + CHR$(right)
KEY 17, CHR$(extendedkeyboard + numlock) + CHR$(up)
KEY 18, CHR$(extendedkeyboard + numlock) + CHR$(down)
KEY 19, CHR$(ctrl + capslock) + CHR$(C)
KEY 20, CHR$(extendedkeyboard + ctrl + numlock) + CHR$(scrolllock)
ON KEY(15) GOSUB Keyleft
ON KEY(16) GOSUB Keyright
ON KEY(17) GOSUB Keyup
ON KEY(18) GOSUB Keydown
ON KEY(19) GOSUB Keybreak
ON KEY(20) GOSUB Keybreak
KEY(15) ON
KEY(16) ON
KEY(17) ON
KEY(18) ON
KEY(19) ON
KEY(20) ON
WHILE UCASE$(INKEY$) <> UCASE$("q")
WEND
END
Keyleft:
PRINT "left"
RETURN
Keyright:
PRINT "right"
RETURN
Keyup:
PRINT "up"
RETURN
Keydown:
PRINT "down"
RETURN
Keybreak:
PRINT "break"
RETURN
Execution Speed
Question:
Why are the newer versions of QuickBasic slower than the previous
ones?
Response:
The execution speed differences among different versions of QuickBasic
are due to different internal floating-point math representations.
QuickBasic versions 1.00 and 2.00 used Microsoft Binary Format (MBF)
representation. QuickBasic 3.00 was actually shipped with two
different versions: QB.EXE used MBF representation, and QB87.EXE, for
machines with a math coprocessor, used IEEE format floating-point
representation. All Microsoft Basic products (for MS-DOS or OS/2)
since QuickBasic version 4.00 have used IEEE format exclusively.
There are several reasons why Microsoft chose to support the IEEE
floating-point standard rather than continuing to use MBF. The main
reason is that IEEE is the industry standard and is required for the
Intel 80x87 math coprocessors. Also, all other Microsoft language
products (except COBOL) use IEEE. Therefore, supporting IEEE in
QuickBasic, as well, makes interlanguage programming using any
combination of these products much easier.
IEEE support, however, requires more overhead and did cause execution
speed to slow, although execution speed on machines equipped with a
math coprocessor is slightly faster than older (MBF) versions of
Basic.
Since INTEGER math is much faster than floating-point math, the best
way to improve the speed of your program is to use INTEGER variables
whenever possible. One easy way to accomplish this is to use the
following code fragment in the beginning of your programs:
DEFINT A-Z
The default, in Basic, is to create new variables as SINGLE-precision
floating point. Using the DEFINT A-Z forces all new variables that are
not declared as some other type (usually this is done with a type
specifier such as x! for SINGLE precision) to be INTEGERs. On most
programs this makes a dramatic improvement. The variables that have
the greatest effect upon execution speed are those that are frequently
accessed, such as loop and array indexes. Make these types of items
INTEGERs whenever possible.
Limitations of Floating-Point Mathematics
Question:
What are some mathematical rounding issues?
Response:
Floating-point mathematics is a complex topic that confuses many
programmers. The tutorial below should help you recognize programming
situations where floating-point errors are likely to occur and show
you how to avoid them. It should also allow you to recognize cases
that are caused by inherent floating-point math limitations as opposed
to compiler errors.
Decimal and Binary Number Systems
Normally, we count things in base 10. The base is completely
arbitrary. (The only reason that people have traditionally used base
10 is that they have 10 fingers, which make handy counting tools.)
The number 532.25 in decimal (base 10) means the following:
(5 * 10^2) + (3 * 10^1) + (2 * 10^0) + (2 * 10^-1) + (5 * 10^-2)
500 + 30 + 2 + 2/10 + 5/100
_________
= 532.25
In the binary number system (base 2), each column represents a power
of 2, instead of 10. For example, the number 101.01 means the
following:
(1 * 2^2) + (0 * 2^1) + (1 * 2^0) + (0 * 2^-1) + (1 * 2^-2)
4 + 0 + 1 + 0 + 1/4
_________
= 5.25 Decimal
How Integers Are Represented in PCs
Since there is no fractional part to an integer, its machine
representation is much simpler than it is for floating-point values.
Normal integers on PCs are 2 bytes (16 bits) long with the most
significant bit indicating the sign. Long integers are 4 bytes long.
Positive values are straightforward binary numbers. For example:
1 Decimal = 1 Binary
2 Decimal = 10 Binary
22 Decimal = 10110 Binary etc.
However, negative integers are represented using the "two's
complement" scheme. To get the two's complement representation for a
negative number, you need to take the binary representation for the
number's absolute value and then flip all the bits and add 1. For
example:
4 Decimal = 0000 0000 0000 0100
1111 1111 1111 1011 flip the bits
-4 = 1111 1111 1111 1100 add 1
Note that -1 Decimal = 1111 1111 1111 1111 in binary, which explains
why Basic treats -1 as logical true (all bits = 1). This is a
consequence of not having separate operators for bitwise and logical
comparisons. Often in Basic, it is convenient to use the code fragment
below when your program will be making many logical comparisons. This
greatly aids readability.
CONST TRUE = -1
CONST FALSE = NOT TRUE
Note that adding any combination of two's complement numbers together
using ordinary binary arithmetic produces the correct result.
Floating-Point Complications
Every decimal integer can be exactly represented by a binary integer;
however, this is not true for fractional numbers. In fact, every
number that is irrational in base 10 also is irrational in any system
with a base smaller than 10.
For binary, in particular, only fractional numbers that can be
represented in the form p/q, where q is an integer power of 2, can be
expressed exactly with a finite number of bits. Even common decimal
fractions, such as decimal 0.0001, cannot be represented exactly in
binary (0.0001 is a repeating binary fraction with a period of 104
bits).
This explains why a simple example, such as the following, will print
1.000054 as output:
SUM = 0
FOR I% = 1 TO 10000
SUM = SUM + 0.0001
NEXT I%
PRINT SUM ' theoretically = 1.0
The small error in representing 0.0001 in binary propagates to the
sum.
For the same reason, you should always be very cautious when making
comparisons on real numbers. The following example illustrates a
common programming error:
item1# = 69.82#
item2# = 69.20# + 0.62#
IF item1# = item2# then print "Equality!"
This will NOT print "Equality." Why? Because 69.82 cannot be
represented exactly in binary, which causes the value that results
from the assignment to be SLIGHTLY different (in binary) than the
value that is generated from the expression. In practice, you should
always code such comparisons in such a way as to allow for some
tolerance. For example:
IF (item1# < 69.83#) AND (item1# > 69.81#) then print "Equal"
This WILL print "Equal."
Other Common Floating-Point Errors
The following are common floating-point errors:
- Round-off error
This error results when all of the bits in a binary number cannot
be used in a calculation.
Example: Adding 0.0001 to 0.9900 (SINGLE precision)
Decimal 0.0001 will be represented as follows:
(1.)10100011011011100010111 * 2^(-14+Bias) (13 leading zeros in
binary!)
0.9900 will be represented as follows:
(1.)11111010111000010100011 * 2^(-1+Bias)
Now to actually add these numbers, the decimal (binary) points must
be aligned. For this they must be unnormalized. Here is the
resulting addition:
.000000000000011010001101 * 2^0 (Only 11 of 23 bits retained)
+.111111010111000010100011 * 2^0
________________________________
.111111010111011100110000 * 2^0
This is called a round-off error because some computers round when
shifting for addition. Others simply truncate. Round-off errors are
important to consider whenever you are adding or multiplying two
very different values.
- Subtracting two almost equal values
.1235
-.1234
_____
.0001
This will be normalized. Note that although the original numbers
each had four significant digits, the result has only one
significant digit.
- Overflow and underflow
This occurs when the result is too large or too small to be
represented by the data type.
- Quantizing error
This occurs with those numbers that cannot be represented in exact
form by the floating-point standard.
- Division by a very small number
This can trigger a "divide by zero" error or can produce bad
results, as in the following example:
A = 112000000
B = 100000
C = 0.0009
X = A - B / C
In QuickBasic, X now has the value 888887, instead of the correct
answer, 900000.
- Output error
This type of error occurs when the output functions alter the
values they are working with.