ArtsAutosBooksBusinessEducationEntertainmentFamilyFashionFoodGamesGenderHealthHolidaysHomeHubPagesPersonal FinancePetsPoliticsReligionSportsTechnologyTravel

QBasic Functions For Floating Point Arithmetic

Updated on July 15, 2012

QBasic supports four numeric data types. These are:

  • INTEGER (signed integer stored in 2 consecutive 8-bit bytes)
  • LONG (signed integer stored in 4 consecutive 8-bit bytes)
  • SINGLE (single precision floating point number stored in 4 consecutive 8-bit bytes)
  • DOUBLE (double precision floating point number stored in 8 consecutive 8-bit bytes)

QBasic also has the so called "user defined" types which starts with keyword "TYPE" and ends with "END TYPE" (Note: space required between two words). QBasic had carried over from older Basic's (BASICA/GW-BASIC) the FIELD statement using which a programmer defines a record area exactly like a TYPE statement except that FIELD statement can only be used in association with a file.

An Example

This code snippet uses TYPE to describe a record in a random file from which last record is read and "ClosingPrice" is extracted:

TYPE PriceRecStruc
  DateNumber AS SINGLE
  OP AS SINGLE
  HP AS SINGLE
  LP AS SINGLE
  CP AS SINGLE
  Volume AS SINGLE
END TYPE
DIM PriceRec AS PriceRecStruc 
DIM ClosingPrice AS SINGLE
FileName = "C:\DAT\" + TickerName$ + ".DAT"
FileNr = FREEFILE
RecLen = LEN(PriceRec)
OPEN FileName FOR RANDOM AS FileNr LEN = RecLen
' Calculate Last Record Number
NORecs = LOF(FileNr) / RecLen
GET #FileNr, NORecs, PriceRec
ClosingPrice = PriceRec.CP
CLOSE #FileNr


To achieve same result using FIELD statement we may code this snippet as follows:

DIM ClosingPrice AS SINGLE
FileName = "C:\DAT\" + TickerName$ + ".DAT"
FileNr = FREEFILE
RecLen = 24 ' This calculated by hand and hard coded
OPEN FileName FOR RANDOM AS FileNr LEN = RecLen
FIELD FileNr, 4 AS DNS$, _
              4 AS OPS$, _
              4 AS HPS$, _
              4 AS LPS$, _
              4 AS CPS$, _
              4 AS VolS$
'
' Calculate Last Record Number
NORecs = LOF(FileNr) / RecLen
GET #FileNr, NORecs ' Please note: No variable name to receive
                    ' data, data will be received in above FIELD
                    ' variables
ClosingPrice = CVS(CPS$) ' 4 bytes in CPS$ are transferred to
                         ' ClosingPrice. Both CPS$ and
                         ' ClosingPrice occupy 4 bytes. But only
                         ' ClosingPrice will be treated as single
                         ' precision floating point number
CLOSE #FileNr


CVS and MKS$ Functions

In above code snippet the way to get the 4 bytes from file to be interpreted as single precision number is to use the function CVS. And reverse operation is required to populate FIELD variables. That operation is performed by MKS$. Neither CVS nor MKS$ do any real work. They just transfer 4 bytes from what QBasic thinks as a STRING into what QBasic thinks as a SINGLE.

In some of new BASIC programming systems FIELD statement is not supported but some of these new systems still have CVS and MKS$ functions. Full list of functions needed for input-output from a random file is QBasic is given below:


QBasic Functions for use with FIELD statement

MKI$
CVI
2 byte integer
MKL$
CVL
4 byte integer
MKS$
CVS
single precision floating point number
MKD$
CVD
double precision floating point number
QBasic Functions for input/output from random file using FIELD statement

For Older versions of Basic

Older versions of Microsoft Basic used their proprietory binary format called Microsoft Binary Format (MBF). Those formats were different for floating point values. So if you were processing older random files which had MBF floating point values you need additional two functions as given in following table:


Functions for Older Versions of Floating Point Values

 
 
 
MKSMBF$
CVSMBF
older version SINGLE
MKDMBF$
CVDMBF
older version DOUBLE
QBasic Functions for use with files with older versions of floating point values

Example: A Program Which Causes Overflow Error

Example: A Program Which Causes Overflow Error

REM FLOAT1.BAS program
REM This program causes Overflow error
CLS
FOR i = 0 TO 1000
  x = 2 ^ i
  PRINT "x"; x
NEXT i

Two floating point numbers are treated differently by the PRINT statement

Sometimes it prints it as an integer sometimes in scientific notation

REM FLOAT2.BAS program
REM Two floating point numbers are treated
REM differently by the PRINT statement
REM
REM First prints it as an integer and second
REM print in "scientific notation"
CLS
x = 2 ^ 23
PRINT x
x = 2 ^ 24
PRINT x


Output of above program looks like:

 8388608 
 1.677722E+07 


Please note that second PRINT has used scientific notation.

Why Two Floating Point Numbers are Treated Differently by PRINT Statement?

We investigate why two floating point numbers are treated differently by QBASIC PRINT statement.

First the program:

REM FLOAT2.BAS program
REM This program investigates why two
REM floating point numbers are treated
REM differently by the PRINT statement
REM
CLS
x = 2 ^ 23
PRINT "We investigate for x="; x: PRINT
DIM aString AS STRING
aString = MKS$(x)
REM
REM Now aString has the 4 bytes which
REM can be treated as a string. These are
REM same 4 bytes which make up the
REM internal representation of x
REM
PRINT "Now we look at individual bytes that make up the internal representation of x"
PRINT
FOR i = 1 TO 4
  PRINT "i-th byte's ASCII value, for i="; i, ASC(MID$(aString, i, 1))
NEXT i
REM
PRINT
PRINT "======================================================================"
PRINT
x = 2 ^ 24
PRINT "We investigate for x="; x: PRINT
aString = MKS$(x)
REM
REM Now aString has the 4 bytes which can treated as a string
REM These are same 4 bytes which make up the internal representation of x
REM
PRINT "Now we look at individual bytes that make up the internal representation of x"
PRINT
FOR i = 1 TO 4
  PRINT "i-th byte's ASCII value, for i="; i, ASC(MID$(aString, i, 1))
NEXT i

Now the output generated by above program:

We investigate for x= 8388608 
Now we look at individual bytes that make up the internal representation of x
i-th byte's ASCII value, for i= 1          0 
i-th byte's ASCII value, for i= 2          0 
i-th byte's ASCII value, for i= 3          0 
i-th byte's ASCII value, for i= 4          75 
======================================================================
We investigate for x= 1.677722E+07 
Now we look at individual bytes that make up the internal representation of x
i-th byte's ASCII value, for i= 1          0 
i-th byte's ASCII value, for i= 2          0 
i-th byte's ASCII value, for i= 3          128 
i-th byte's ASCII value, for i= 4          75


What is happening here? To find out about internal representation of floating point numbers we dig into the help bundled with QBasic interpreter. We position keyboard cursor at the MKS$ function and press function key F1.

Two Digressions On New Computers

Function Keys on New Computers

Many new computers come with default mapping to hardware specific actions mapped onto these function keys. To obtain old behaviour of these keys these computers provide a special key called FN key. Most of these computers allow to restore old behaviours via software or via a BIOS setting. If you are irritated by such computers search manufacturer's documentation on a search page. For example, for my HP laptop I managed to find help on Google search pages which led me to an HP documentation page titled: Disable The Fn + Function Key Combination on Certain Models - HP Customer Care (United States - English).

How to run QBasic on Windows 7?

Also my HP laptop has Windows 7 and to run QBasic I had to download a software called DosBox. I have not managed to configure this software to make it work exactly like QBasic on my older pre-XP systems. But if you have no option but to use a Window 7 system it is not bad software.

How to Find Help in QBasic

For 99% of your needs you can just position keyboard cursor on a valid QBasic keyword and press function key F1 and QBasic shows the relevant help.

Before we digressed ...

Before we digressed, I was seeking help on MKS$ function. So I positioned keyboard cursor at MKS$ in my program and pressed function key F1. It does not give much information on MKS$ but at bottom of help screen there is reference to two more help screens. One is for FIELD statement and the other is for: "MKSMBF$, MKDMBF$, CVSMBF, CVDMBF". And the second cross reference says following:

MKSMBF$ and MKDMBF$ convert IEEE-format numbers to 
Microsoft-Binary-format numeric strings that can 
be stored in FIELD statement string variables.
CVSMBF and CVDMBF convert those strings back to 
IEEE-format numbers.
.
MKSMBF$(single-precision-expression!)
MKDMBF$(double-precision-expression#)
CVSMBF (4-byte-numeric-string)
CVDMBF (8-byte-numeric-string)
.
    Function    Returns
    ========    ==================================
    MKSMBF$     A 4-byte string containing a 
                Microsoft-Binary-format number
.
    MKDMBF$     An 8-byte string containing a 
                Microsoft-Binary-format number
.
    CVSMBF      A single-precision number in 
                IEEE format
.
    CVDMBF      A double-precision number in 
                IEEE format
.
    * These functions are useful for maintaining 
      data files created with older versions of 
      Basic.


This gives us the clue that older Microsoft-Binary-format were before Microsoft QBasic switched to new IEEE format.

After some fumbling I found wikipedia article on IEEE floating point format. It seems that there are more than one IEEE standards for floating point number representation. There is one identified as IEEE 754-2008 which is an upgrade of a standard published in 1985 identified as IEEE 754-1985. Since QBasic existed before 2008 we should dig deeper into IEEE 754-1985. Which is exactly what we are going to do.

Brief Introduction to IEEE754-1985 standard

Before we dig deeper into IEEE 754-1985 standard we will rewrite our program FLOAT3.BAS to spit out the result in bits format rather than in ASCII values that I have done so far. Also I shall print the result into a file because in DosBox I still do not know how we can copy text for pasting later in a Windows application. This I will do a lot in following and I just wanted to make my life easier, since I would be pasting into a text area in a browser. Feel free to modify the code for your own use for any non-commercial use.

First we show the program FLOAT4.BAS which has these changes:

DECLARE FUNCTION Binary$ (y AS SINGLE)
DECLARE FUNCTION halfByte$ (a AS STRING)
REM FLOAT4.BAS program
REM This program investigates why two
REM floating point numbers are treated
REM differently by the PRINT statement
REM
CLS
OPEN "FLOAT4.TXT" FOR OUTPUT AS #1
x = .15625
PRINT #1, "We investigate for x="; x: PRINT #1,
DIM aString AS STRING
aString = MKS$(x)
REM
REM Now aString has the 4 bytes which
REM can be treated as a string. These are
REM same 4 bytes which make up the
REM internal representation of x
REM
PRINT #1, "Now we look at individual bytes that make"
PRINT #1, "up the internal representation of x"
PRINT #1,
Output$ = ""
FOR i = 1 TO 4
  y = ASC(MID$(aString, i, 1))
  Output$ = Binary$(y) + " " + Output$
NEXT i
PRINT #1, Output$
CLOSE #1
END
FUNCTION Binary$ (y AS SINGLE)
IF y > 255 OR y < 0 THEN
  PRINT "Severe Error: FLOAT4.BAS:Binary$:#1"
  PRINT "Program will be terminated"
  END
END IF
DIM a AS STRING
IF y < 15! THEN
  a = HEX$(y)
  Binary$ = "0000 " + halfByte$(a)
ELSE
  a = HEX$(y)
  Binary$ = halfByte$(LEFT$(a, 1)) + halfByte$(RIGHT$(a, 1))
END IF
END FUNCTION
FUNCTION halfByte$ (a AS STRING)
  SELECT CASE a
  CASE "0"
    halfByte$ = "0000 "
  CASE "1"
    halfByte$ = "0001 "
  CASE "2"
    halfByte$ = "0010 "
  CASE "3"
    halfByte$ = "0011 "
  CASE "4"
    halfByte$ = "0100 "
  CASE "5"
    halfByte$ = "0101 "
  CASE "6"
    halfByte$ = "0110 "
  CASE "7"
    halfByte$ = "0111 "
  CASE "8"
    halfByte$ = "1000 "
  CASE "9"
    halfByte$ = "1001 "
  CASE "A"
    halfByte$ = "1010 "
  CASE "B"
    halfByte$ = "1011 "
  CASE "C"
    halfByte$ = "1100 "
  CASE "D"
    halfByte$ = "1101 "
  CASE "E"
    halfByte$ = "1110 "
  CASE "F"
    halfByte$ = "1111 "
  CASE ELSE
    PRINT "Severe Error:FLOAT4.BAS:halfByte$:#1"
    PRINT "Program will be terminated"
    END
  END SELECT
END FUNCTION


We test run the program for value in x as 0.15625 because that is the value used in wikipedia article which explains IEEE standard for 32 bit single precision floating point numbers. Click here to go to the relevant section on wikipedia page.

We investigate for x= .15625 
Now we look at individual bytes that make
up the internal representation of x
0011 1110  0010 0000  0000 0000  0000 0000  


We are happy to see bit pattern as identical to what is shown on wikipedia page. Please notice we had to show bytes in reverse order because that is how QBasic stores them. We will revisit topic of byte order later in another article. For now bear in mind that first bit on left to right scan is bit number 32nd. It is usual to call this as 31st bit because we count from zero. And this is standard way to talk about bits. That is to say, we start with zeroeth bit from right and go on until leftmost bit, the 31st bit. We will use notation like this: [b31 ... b0]. For example, for x = .15625, our FLOAT4.BAS output shows the following 32 bits in above output:

[b31 ... b0] = 0011 1110 0010 0000 0000 0000 0000 0000

Now we run the program FLOAT4.BAS for values 223 and 224 to see if these in any way warrant for QBasic to treat them differently.

Output for 223

We investigate for x= 8388608 
Now we look at individual bytes that make
up the internal representation of x
0100 1011  0000 0000  0000 0000  0000 0000  


The bit b31 is "0" here and it is the sign bit which is followed by 8 bits [b30 ... b23]: "100 1011 0" which has value of 27 + 24 + 22 + 21 = 128 + 16 + 4 + 2 = 150. This value is the exponent with a bias of 127. By "bias of 127" is meant that we will calculate the value of number represented by [b30 ... b23] and subtract 127 to get the power of 2. So value floating point number is obtained by calculating the fractional part (let us call it: F). And then calculating value of power of 2 as: value of [b30 ... b23] minus 127. We will call value of bits [b30 ... b23] as "e" as is done in the wikipedia article. So "bias of 127" means that power of 2 is calculated as (e - 127). And value of floating point number is: (-1)sign * 2(e-127) * F.

How to calculate fractional part F?

Remaining bits are: [b22 ... b0] which are used to represent what is called fractional part. It stands for a binary fraction: 1.b22 b21 b20 ... b2 b1 b0. Note that the bit "1" before "binary" point is actually not there in storage! It is an assumed bit. The "binary point" is in binary number system what is "decimal point" in decimal number system. By assuming that "1" is there before binary point we are saving a storage area of 1 bit. That is, by using 23 remaining bits we are actually storing a fractional number of 24 bits size if we were to also store the assumed 1 before binary point. For all non-zero fractional numbers we can do that. Because if it is a number smaller that 1 then we will multiply it by "2" repeatedly until the number becomes > 1. If a number > 2, then we can divide it by 2 until it becomes less than 2 and then it will be greater than 1 too. Of course, we will have to adjust exponent or power of 2 accordingly. For fraction which is less than 1 we will subtract 1 from power of 2 every time we multiply by 2. And when converting a fraction > 2 we will add 1 to power of 2 every time we divide by 2. This process of bringing our number in the range greater than equal to 1 and less than 2 is known as normalization.

So for x = 223 the "F" turns out to be: 1.000 0000 0000 0000 0000 0000, which is exactly 1. And exponent (or power of 2) is: 150 - 127 = 23.

And it is easy to see why 224 will be treated differently. It is the first power of 2 which will require one more bit than there is room for in the fractional part of representation. 224 is in IEEE format will be:

224 = 224 * 1.000 0000 0000 0000 0000 0000 0

except the last "0" is not there because there is no room for that "0" bit. This will give us when "bias 127" is applied an exponent of (24 + 127) = 151. 151 in binary number system is:

100 1011 1

Let us write out the entire 32 bits [b31 ... b0]:

0100 1011 1000 0000 0000 0000 0000 0000

We verify this by running FLOAT4.BAS for x = 224:

Output for 224

We investigate for x= 1.677722E+07 
Now we look at individual bytes that make
up the internal representation of x
0100 1011  1000 0000  0000 0000  0000 0000  


Exactly as we thought! Now it only remains to find out how fractional number which is exactly zero is stored in IEEE format. We run the program FLOAT4.BAS for x = 0 and get following result:

We investigate for x= 0 

Now we look at individual bytes that make
up the internal representation of x

0000 0000  0000 0000  0000 0000  0000 0000  


This is consistent with IEEE format. It is written in wikipedia article here that:

The true significand includes 23 fraction bits to the right of the binary point and an implicit leading bit (to the left of the binary point) with value 1 unless the exponent is stored with all zeros.

The word "significand" is new word introduced to term the normalized fraction with a "1" bit as assumed bit before binary point. And when exponent is all zeroes the value of floating point number is 0.0.

The IEEE standard has many other things which I have deliberately left out from this brief introduction to it. Wikipedia is a good starting point to start studying it.

Comments

    0 of 8192 characters used
    Post Comment

    No comments yet.

    Click to Rate This Article