Hacker’s Guide to Visual FoxPro
An irreverent look at how Visual FoxPro really works. Tells you the inside scoop on every command, function, property, event and method of Visual FoxPro.
BITAND()
, BITCLEAR()
, BITLSHIFT()
, BITNOT()
, BITOR()
, BITRSHIFT()
, BITSET()
, BITTEST()
, BITXOR()
All these functions manipulate bit values as 4-byte signed integers. Visual FoxPro has the full complement of AND, OR, NOT and XOR operations. In addition, we have simple and accessible SET, CLEAR, TEST as well as Left and Right SHIFT commands.
For those of you who haven’t had to slice and dice bits since school, a little base number review is in order here. Base 10, the decimal system, is the most common way of counting. From right to left, the digits are the ones place, the tens place, and the hundreds place. These can also be expressed as 10 to the zeroth power, 10 to the first power, and 10 to the second power. Base 2, or binary math, works the same way. The first place is the ones place, 2 to the zeroth. The second place (from the right) is the twos place, two to the first power. The progression continues—third place is four, two squared; fourth place is eight, two to the third. Let’s try to make this into a table and see what happens:
Binary Place |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
Holds the value |
128 |
64 |
32 |
16 |
8 |
4 |
2 |
1 |
Decimal Value |
Represented in Binary |
|||||||
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
2 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
3 |
0 |
0 |
0 |
0 |
0 |
0 |
1 |
1 |
4 |
0 |
0 |
0 |
0 |
0 |
1 |
0 |
0 |
16 |
0 |
0 |
0 |
1 |
0 |
0 |
0 |
0 |
127 |
0 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
128 |
1 |
0 |
0 |
0 |
0 |
0 |
0 |
0 |
255 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
1 |
It’s hard to fit more than eight bits across the page in a table that makes sense—check out the sample Num2Bit.PRG, below, for a full 32-bit display.
So when we’re talking about the value 128, our computer is really thinking 10000000. And 127, not very different to us, is very different to the computer, which represents it internally as 01111111. So why would any human being in his right mind want to play with all these ones and zeroes? This was the question we asked our algebra teachers, and hardly ever received a satisfactory answer. Well, here, finally, we have an answer.
Space. The final frontier. Uh, no, not that kind of space—space of the hard disk kind of space. What if you had to store six yes/no answers in a table? You could create a character field of four spaces, storing “Yes,” “No,” “Ya,” “Nein,” and “Nyet,” but parsing and case-testing would probably be far too time-consuming. A logical field seems more efficient. But six logical fields take six bytes, perhaps not a major amount with multi-gigabyte hard drives in the hundreds of dollars, but millions of these bytes clog the lines of communication. Multiply these by a few dozen fields in the more complex tables and you have the formula for bogging down whatever system you choose in wasted I/O, sloshing padding characters around.
In order to save space, many functions use individual bits within a byte to store simple yes/no information. For example, DBF headers store information on whether or not the file uses CDXs, memo fields, and whether the file is a DBC or not—all in byte 28. Divining this information is far easier thanks to the BIT functions. Other places where bit-splitting comes in handy are in deciphering the buttons pressed and modifier keys in MouseDown, MouseMove and related events, or in hacking SCX datestamp fields. Obviously, Microsoft has had these functions available to them internally for quite some time—it is great that they exposed them for us to use as well.
A little terminology primer is needed here. If a bit contains a 1, it is said to be “set” or “on.” If a 0 instead occupies that place, the location is said to be “cleared” or “off.” The operations of setting these bits can therefore be referred to as “setting” and “clearing.”
All BIT functions operate on numbers in the range from 0 to ±(2^31 - 1), using 31 bits in a total of four bytes. The 32nd bit (bit 31—remember, we started counting at zero) is reserved for the negative flag. If bit 31 is set on, the value is considered negative, and the value of the following bits is different from what they mean for positive values. Negative 1 is expressed as 32 bits set to 1, negative two as 31 one-bits followed by a zero, negative three as 30 one-bits followed by zero-one, and negative four as 30 one-bits followed by two zeroes. We seem to remember this as “two’s complement math,” but we’ve been out of school way too long to think this way. This must make some sense to the computer, but little to us. Our advice: Use caution when manipulating bit 31 or shifting bits left, which could change the sign of the number. Combining BIT functions with addition or subtraction under these conditions can lead to inaccurate results.
nRetVal = BitAnd( nFirstArg, nSecondArg [, nThirdArg [ ... ] ] )
nRetVal = BitNot( nFirstArg )
nRetVal = BitOr( nFirstArg, nSecondArg [, nThirdArg [ ... ] ] )
nRetVal = BitXOr( nFirstArg, nSecondArg [, nThirdArg [ ... ] ] )
Parameter |
Value |
Meaning |
nFirstArg |
Numeric |
First numeric value to process, ranging from 0 to ±(2^32 - 1). |
nSecondArg |
Numeric |
Value to process with the first numeric value, restricted to the same range. |
nThirdArg |
Numeric |
Value to process with the first two numeric values, restricted to the same range. |
... |
Numeric |
Up to 23 more values to process with the others, restricted to the same range. |
These fundamental functions perform bit mathematics. They allow the changing of one or more bits at the same time. BITAND()
may be used with a “mask” argument to allow through only selected bits. BITAND()
returns a 1 in a bit position if and only if all arguments have a 1 in that position. For example, in the TimeStamp field illustrated in the Stamp2DT.PRG below, the hours field is stored in the five bits in positions 11, 12, 13, 14 and 15, and those individual bits can be isolated with a mask with only those bits set. BITNOT()
reverses all the bits of the supplied number from 1 to 0 and 0 to 1. BITOR()
returns a 1 in a bit position if at least one of the arguments has a 1 in that position. BITOR()
can be used to ensure that some bits are turned on by supplying a mask with those bits turned on. Note that starting in VFP 7, you can pass up to 26 arguments to BITAND()
and BITOR()
, while VFP 6 and earlier permit only two.
Prior to VFP 7, explaining BITXOR()
was only a little complicated. The function matched two values to the most severe test—that a bit was set in one value and not set in the other. With two arguments, BITXOR()
returns a bit set if only one of the two numbers has that bit set, and zero if either both numbers do not have the bit set or both numbers do have the bit set. In other words, BITXOR()
tests for a match in corresponding positions and sets a bit only if the two numbers do not match. In VFP 7, with the ability to pass up to 26 arguments, the test has changed. BITXOR()
returns a 1 for a given bit if one and only one of the arguments has that bit set.
nRetVal = BitClear( nValue, nBitPos )
nRetVal = BitSet( nValue, nBitPos )
lRetVal = BitTest( nValue, nBitPos )
Parameter |
Value |
Meaning |
nValue |
Numeric |
Value to process, ranging from 0 to ±(2^32 - 1). |
nBitPos |
Numeric |
Bit position to modify or test. Ranges from 0 to 31. |
nRetVal |
Numeric |
The result is the numeric value of taking the supplied nValue and SETting or CLEARing the specified bit. |
lRetVal |
.T. |
Position nBitPos is set in nValue. |
.F. |
Position nBitPos is clear in nValue. |
These three handy functions allow access to a single bit to either clear it (set it to 0), set it (to 1) or query its current value (return .T. if the bit is 1 and .F. it it’s 0). nBitPos must be in the range 0 to 31—numbers outside this range (or null) return error 11, “Invalid function argument value, type or count.”
* Num2Bit - return character representation of numeric bitmap
LPARAMETERS number
LOCAL I
LOCAL lcRetString
IF TYPE('number') <> "N" OR ABS(number) > 2^32
lcRetString = "Must be number -2^32 to +2^32"
ELSE
lcRetString = ""
FOR I = 31 TO 0 STEP -1
lcRetString = lcRetString + IIF(BITTEST(number,I),"1","0")
NEXT
ENDIF
RETURN lcRetString
nRetVal = BitRShift( nValue, nAmt2Shift )
nRetVal = BitLShift( nValue, nAmt2Shift )
Parameter |
Value |
Meaning |
nValue |
Numeric |
Value to shift, ranging from 0 to ±(2^32 - 1). |
nAmt2Shift |
Numeric |
Number of bits to shift, positive or negative. |
nRetVal |
Numeric |
The numeric value from taking the supplied number and shifting the bitmap the specified amount to the right or left. |
The SHIFT functions return the result of moving all the bits a specified number of places to the right or left. For example, shifting the binary value 0110 one to the right results in 0011, to the left 1100. What happens to the values that fall off the right or left end of the value? They’re gone—dumped into the bit-bucket, never to return. Zeroes are always used to fill in the newly created “holes” on the other end. These functions allow you to grab a specified range of bits and process them as a separate value. In combination with BITAND()
and the other functions above, a value can be masked and those selected bits moved to the right or left to isolate a set of bits and calculate their value.
The following function hacks SCX/VCX timestamps and returns the correct datetime value for the record.
********************************************************************
* Program....: STAMP2T6.PRG
* Version....: 1.0
* Author.....: Ted Roche
* Date.......: May 31, 1998
* Notice.....: Copyright © 1998 Ted Roche, All Rights Reserved.
* Compiler...: Visual FoxPro 06.00.8093.00 for Windows
* Abstract...: VERSION SIX AND LATER ONLY!!!
* ...........: Simpler version of Stamp2DT written for HackFox3 and
* ...........: also published in FoxPro Advisor magazine
* Changes....:
********************************************************************
LPARAMETERS tnStamp
#DEFINE SecondsMask 15 && 00001111
#DEFINE MinutesMask 63 && 00111111
#DEFINE HoursMask 31 && 00011111
#DEFINE DaysMask 31 && 00011111
#DEFINE MonthsMask 15 && 00001111
#DEFINE YearsMask 63 && 00111111
#DEFINE SecondsOffset 1 && Note this is a LEFT shift, not RIGHT
#DEFINE MinutesOffset 5
#DEFINE HoursOffset 11
#DEFINE DaysOffset 16
#DEFINE MonthsOffset 21
#DEFINE YearsOffset 25
#DEFINE fMonth BITAND(BITRSHIFT(tnStamp,MONTHSOFFSET ),MONTHSMASK)
#DEFINE fDay BITAND(BITRSHIFT(tnStamp,DAYSOFFSET ),DAYSMASK)
#DEFINE fYear 1980+BITAND(BITRSHIFT(tnStamp,YEARSOFFSET ),YEARSMASK)
#DEFINE fHour BITAND(BITRSHIFT(tnStamp,HOURSOFFSET ),HOURSMASK)
#DEFINE fMinute BITAND(BITRSHIFT(tnStamp,MINUTESOFFSET),MINUTESMASK)
#DEFINE fSecond BITAND(BITLSHIFT(tnStamp,SECONDSOFFSET),SECONDSMASK)
IF TYPE("VERSION(5)") = "U"
MESSAGEBOX("This routine only works with Visual FoxPro 6.x or later.")
RETURN .F.
ENDIF
LOCAL ltReturn
ltReturn = IIF(tnStamp = 0, {//::}, ;
DATETIME(fYear, fMonth, fDay, fHour, fMinute, fSecond))
RETURN ltReturn
This is a really handy function to check the last date that form elements were modified. Open up an SCX file and issue the command:
BROWSE FIELDS platform, uniqueid, timestamp, realtime=stamp2t6(timestamp),;
classname=LEFT(class,20), namebaseclass=LEFT(baseclass,20)
This example requires some explanation. The TIMESTAMP field in SCX’s and VCX’s is composed of bitmaps of the year, month, day, hour, minute and second. In order to compress all this information into the smallest amount of space, the designers examined each of the values above, and calculated the amount of space each would take, in bits. The seconds field was trimmed on the right (using a BITRSHIFT()
) so the precision of this field is within two seconds, probably close enough. Then these values are mapped into a continuous range of 30 bits:
Value |
Range |
Number of Bits Required |
Starting Position |
Years since 1980 |
0 – 64 |
6 |
25 |
Months |
1 – 12 |
4 |
21 |
Day |
1 – 31 |
5 |
16 |
Hours |
0 – 23 |
5 |
11 |
Minutes |
0 – 59 |
6 |
5 |
Seconds |
even 0 – 58 |
5 |
0 |
Parsing out these values is a matter of masking out all other values, and then shifting the value of interest to the right. For example, if we want to determine the hours setting of the value 510494257, first we BITRSHIFT()
this value 11 places to the right, and then mask all but the rightmost five bits to get our final value of 16—this screen element was last modified at 4 p.m.
Note that this function was updated for the Hacker’s Guide VFP 6.0 edition—with the new parameters to pass to DATETIME()
, we no longer have to fool around with various SET(“DATE”) cases. Another win for unambiguous dates!
By the way, this method of storing datetimes is archaic—the datetime data type will store the same amount of information in 20 percent less space, while providing a far better clue to the hapless programmer trying to decode the TIMESTAMP field. We hope we'll see the change to using datetime fields in a future VFP version. |