v5.0 / 20 of 23 / 01 sep 99 / gvg
* This chapter collects a number of interesting topics that weren't easily assembled elsewhere.
* Users often call us with what they perceive as numeric errors in VEE; for
example, one set a a REAL constant to the value 8.9972, and when he ran it
through a To Printer object, the result was 8.997199999999999. He was
certain this was a bug, that VEE had a roundoff error.
I replied that this was expected behavior precisely because there was no
roundoff error. What he was seeing was the full precision of the number as
VEE understood it. It wasn't exactly 8.9972 because VEE uses binary
floating-point numbers to represent real numbers, and these are an
approximation to an actual real for two reasons:
The reason it was being printed in this full-precision / no roundoff format
is because the default format in most transaction objects is:
In a sense, however, there was a bug, since the standard format actually
output the number to about a digit more than its maximum reliable precision,
which meant that VEE would very often display numbers in the unsettling
format shown above. In current versions of VEE, the standard format was
modified to output the number to about a digit less than its standard
precision, greatly reducing reports of trouble.
Even if you have an older version of VEE, if you want to print out a specific
precision -- say, 6 digits -- you can set the transaction to:
The same user also said that when he increased the precision of the Real
Constant, he could not input 8.9972 without it being extended to 8.997199 ...
In this case, VEE was simply doing what it was told to do. Not a problem,
since the precision could be set to whatever the user liked.
Similarly, a European field person posed an interesting and parallel problem
to me concerning errors with modulo-divide ("remainder" operation) and
integer-divide operations on VEE and other platforms. He performed the
following computations:
The fact that the error propagates across platforms is also understandable
(though the IBASIC versus VEE PC discrepancy is odd): the error occurs down
to the math library or floating-point processor level.
Such floating-point problems are universal and occur in all languages that
use binary floating-point math, you get similar results in C. It is, however,
much more visible in VEE than in C, because in VEE we get to see it happen,
while in C we have to print things out to see them -- and so we often get
complaints of this nature, and it proves difficult to explain that VEE is
operating correctly.
* When we have customers call us to complain they are getting an
"out-of-memory" error on VEE for HP-UX, when they actually have plenty of
memory. We always tell them to modify the "maxdsiz" (maximum data segment
size) in Kernel Configuration -> Configurable Parameters to tell the HP-UX to
give VEE more room to move.
This often leads to a protest that the program isn't using that much data, so
something must be wrong. Actually, since HP-UX doesn't execute the VEE
program directly (it's executed by VEE). The entire VEE program is seen as
"data", not just the variables and arrays.
* A VEE user wanted to know if there were some way to convert an arbitrary
string of items:
* You can set a TZ (timezone) environment variable in your PC. A user in
Japan set the timezone variable:
This is consistent with HP-UX usage. The problem is that different PC
applications don't interpret the TZ variable in the same way.
* A user wanted to know how to build a function to test for NIL inputs on
VEE. This function would return "0" if the input was NIL and "1" if it
wasn't.
I thought this was a simple question and came up with the following program:
Anyway, I passed this back to the user feeling pleased with myself and
then he replied that the UserFunction didn't work in a different context:
This is also why "totsize()" returns "1" for a NIL input: the numeric
functions are basically specialized cases of the Formula box, and so inherit
its behavior.
* We had complaints that on certain platforms, the VEE Timer object could not
handle a time span greater than 4500 seconds, it would roll over to 0.
According to the lab people, the maximum timer span is limited by the use of
a long integer to store the span AND the rate of the fundamental hardware
timing used. This means that the time span may be relatively short and it
can vary from platform to platform.
So for long-period timing the appropriate technique is as follows:
* We got a very interesting question from one of our field people concerning
the operation of the VEE If/Then/Else object. He had configured the
conditional clause to a triadic operator:
Now just to make things a little more devious:
I had a nice session in the lab seeing which engineers could figure this one
out (Sue, who designed the If/Then/Else object, didn't have a problem). Why
the field person wanted to do this (likely he was just tinkering) we don't
know, but it certainly was an amusing puzzle to figure out!
* While this particular trick is used in examples all through this document,
it's worthwhile giving it a section of its own so you can find it: to seed
the VEE random-number generator with a value that allows the random values to
vary greatly between runs, use a Formula object with the following contents:
* A user called me concerning the VEE Function Generator (FGen for short)
object, wondering how it could be used to generate amplitude- or
frequency-modulated waveforms. "I can do this with a real function
generator," he said. "Why can't I do this with the virtual instrument?"
I didn't have a ready answer, but the more I thought about it the more
interesting the question became.
* A real function generator in general produces a continuous analog output
waveform in real time. The FGen, in contrast, produces a so-called
"waveform" object that contains numeric information defining the time
duration of the waveform and data points defining the amplitude of that
waveform during that time span.
In short, the virtual instrument is make-believe. It can generate data that
can simulate that acquired by a scope or other type of digitizer from a real
function generator, or provide data (with a little processing) to, say, an
arbitrary-function generator that could then actually generate the real data.
The FGen has the appearance:
When this object is fired, it generates a waveform data object, which
consists (given the settings above) of an array of 256 real numbers plus the
20-millisecond timespan. You can display this data set or put it in a file
or whatever.
Suppose you want to amplitude-modulate an FGen with output from a
lower-frequency one. This is very easy to do, all you have to do is multiply
the two waveforms:
Well, OK, then, how about frequency modulation? That might seem easy to do;
just bring out a function pin to set frequency on one FGen and then feed it
the output of a modulating function generator:
So what about breaking the modulating waveform into its scalar elements and
then simply generating a set of waveform data objects that are finally added
up? Let's try this as a brute-force approach:
To add this item, you need to add more computations to the program: get the
number of points in the original waveform, determine the time interval
between each data point, and feed that into the final FGen to provide the
timespan for each output object.
This will generate the proper time-intervals, with one last problem: you end
up with discontinuities between each of the waveforms since they all start at
the same place. I can't think of a way around that.
In short, the results of trying to do frequency modulation through two ganged
function generators are hardly worth the bother. It would probably provide
better results and work much more effectively to compute the waveform with
basic math functions.
* A user wanted to know how to type non-ASCII characters into a VEE Text
Constant. This can be done easily enough on a Windows platform: you can
type the decimal ascii code (on the numeric keypad only) while holding
down the ALT key, and voila, the corresponding character is entered.
For example, Alt-132 gives you a lowercase "a" with an umlaut.
Unfortunately, this trick doesn't work on an HP-UX system.
* There is a special feature in VEE for Windows which is interesting but
undocumented. To get to this feature, all you have to do is bring up the
"Help -> About" panel and click on it with your secondary mouse button three
times, rather slowly and pausing slightly between mouse clicks. Try varying
the click rate until you get it right.
[20.1] FLOATING-POINT ISSUES
[20.2] RUNNING OUT OF MEMORY ON VEE-UX
[20.3] CONVERTING STRINGS TO ENUMS
[20.4] VEE & TIMEZONE VARIABLE
[20.5] NIL INPUTS ON VEE
[20.6] TIMER OBJECT SPAN IN VEE
[20.7] AN IF-THEN-ELSE PUZZLE
[20.8] SEEDING THE RANDOM-NUMBER GENERATOR
[20.9] USING THE FUNCTION GENERATOR VIRTUAL INSTRUMENT
[20.10] ENTERING NON-ASCII CHARACTERS UNDER VEE
[20.11] A VEEWIN HIDDEN FEATURE
[20.1] FLOATING-POINT ISSUES
WRITE TEXT A EOL
-- which implies "standard format": a format that will accept any input,
text or numeric, and pass it out as best it can.
WRITE TEXT A REAL STD:6 EOL
-- or whatever precision you like. The same goes for To File objects or
AlphaNumeric objects. If you don't like the way the number is stored or
displayed, you can readily change it.
________________________________________________________
VEE-PC IBASIC VE-/UX WINCALC CORRECT
________________________________________________________
10 MOD 0.1 0.1 0 0.1 0.1 0
10 MOD 0.2 0.2 0 0.2 0.2 0
10 MOD 0.25 0 0 0 0 0
9.6 MOD 3.2 3.2 3.2 3.2 3.2 0
9.6 DIV 3.2 2 2 2 2 3
________________________________________________________
The results seemed bogus but after looking at it for a while I concluded,
once more, that it was due to the fact that binary fractions are usually only
approximations of decimal ones. 0.1 is not exactly 0.1 and the division
algorithm is not 100% precise either, so it doesn't quite divide evenly
into 10 and leaves a remainder almost exactly (but not quite) 0.1. Do a MOD
of 100 by 10 and it works fine. It's only the fractional representation that
gives you a hard time.
[20.2] RUNNING OUT OF MEMORY ON VEE-UX
[20.3] CONVERTING STRINGS TO ENUMS
"X1","X2","X3","X4","X5"
-- into an Enum list. As it turns out, there is a way: convert it to a
string array and feed it into the Enum Values pin of an Enum object. For
example:
+--------------------------+ +--------------------------+
| Text | | From String |
+--------------------------+ +--------------------------+
| "X1","X2","X3","X4","X5" +-->| READ TEXT x QSTR ARRAY:* +-+
+--------------------------+ +-------------+------------+ |
| |
+-----------|--------------+
| |
| +-------+-------+
| | Radio Buttons |
| +---------------+
| | <*> X1 |
| | < > X2 |
+-->| < > X3 |
Enum Values | < > X4 |
| < > X5 |
+---------------+
Note that the From String is set to QUOTED STRING format, and that array
length is given as "*" (TO END). The Radio Button object does not have
to be set to any particular number of Enum entries. It will adjust
automatically.
[20.4] VEE & TIMEZONE VARIABLE
SET TZ=JST
-- and then found that VEE's "now()" function returned the time moved forward
9 hours. He was forced to use the following workaround:
now() - 9*3600
As it turned out, this wasn't a bug. VEE assumed that the system clock was
set to GMT and if the TZ variable was set, generated the proper offset from
GMT. This was 9 hours for Nippon. Since the PC's clock was set to local
time, not GMT, the TZ put it 9 hours off.
[20.5] NIL INPUTS ON VEE
+---------------+
| Call Function |
+-------+ +---------------+ +------+
| OK +--->| NilTest +-->| 0 |
+-------+ +---------------+ +------+
-- where OK button generates a NIL container, and the UserFunction NilTest
gives:
+----------------------------------------+
| NilTest |
+---+--------------------------------+---+
| | | |
| | +------------+ | |
| A +-->| Get Values +--+ totsize | |
| | +------------+ | pin | |
| | | | |
| | +---------------+ | |
| | | | |
| | | +-------------------+ | |
| | +-->| ( A==0 : 0 ? 1 ) +--->| X |
| | +-------------------+ | |
| | | |
+---+--------------------------------+---+
Note that you have to use array "Get Values" and not the "totsize()" function,
since "Get Values" returns "0" for a NIL container, while "totsize()" returns
"1", a distinction which has implications.
+--------------+
| Formula |
+-------+ +--------------+ +------+
| OK +--->| NilTest(A) +-->| 1 |
+-------+ +--------------+ +------+
I finally figured out what he was getting at and then wondered: what does a
Formula box do if it gets a NIL input? I revised my program to test:
+---------+ +---------------+
| Formula | | Call Function |
+-------+ +---------+ +---------------+ +------+
| OK +--->| A +--->| NilTest +-->| 1 |
+-------+ +---------+ +---------------+ +------+
As it turned out, the Formula box (which in this case simply wired the input
to the output) converted the NIL to "0". I checked with the lab and this
is specified behavior for the Formula box. There are pros and cons to this
usage, but as changing it would cause problems for people with existing
programs, there are no plans to change it.
[20.6] TIMER OBJECT SPAN IN VEE
+-------+ +-------------------+
| now() +-->| Set Variable: t0 |
+---+---+ +-------------------+
|
+-----+-----+
| block to |
| be timed |
+-----+-----+
|
+------+------+
| now() - t0 +-->
+-------------+
This has poorer resolution but if you are counting hours, then a second's
variation is not much of a problem.
[20.7] AN IF-THEN-ELSE PUZZLE
+-------------------------------+
| If/Then/Else |
+---+--------------------+------+
| | | then +-->
-->| a | [ a=="x" ? 5 : 2 ] | |
| | | else +-->
+---+--------------------+------+
What does this do? If you examine the documentation for the If/Then/Else
object, it says that if the conditional clause returns a nonzero value, it
will return that value out the "then" pin. If it returns a zero value, it
pops a zero out the "else" pin. This means that whether the triadic operator
succeeds (generating a "5") or fails (generating a "2"), the appropriate
value ("5" or "2") goes out the "then" pin.
+-------------------------------+
| If/Then/Else |
+---+--------------------+------+
| | | then +-->
-->| a | [ a=="x" ? 0 : 8 ] | |
| | | else +-->
+---+--------------------+------+
This generates results which might seem counterintuitive, but logically
follow the rules of the If/Then/Else object: if the triadic comparison
succeeds, it generates a "0" -- which the If/Then/Else object interprets as a
failure, and so generates a "0" on the "else" pin. If the triadic comparison
fails, it generates an "8" -- which the If/Then/Else object interprets as a
success, and so fires it out the "then" pin.
[20.8] SEEDING THE RANDOM-NUMBER GENERATOR
randomseed((10^9)*fracPart(now()/100))
This gets the subsecond value of the current time and magnifies it to a large
value. This is implemented as a UserFunction named "rndseed()" in the
xgenfunc.vee User Library.
[20.9] USING THE FUNCTION GENERATOR VIRTUAL INSTRUMENT
+--------------------------+
| Function Generator |
+--------------------------+
| Function [ Cosine ][v] |
| Frequency [ 1000 ] |
| Amplitude [ 1 ] |
| DcOffset [ 0 ] |
| Phase [Deg][v][ 0 ] |
| Time Span [ 20m ] |
| Num Points [ 256 ] |
+--------------------------+
All the settings available on the panel view can be brought out as input pins
to allow them to be controlled programmably.
+--------------------------+
| Function Generator |
+--------------------------+
| Function [ Cosine ][v] |
| Frequency [ 200 ] |
| Amplitude [ 1 ] |
| DcOffset [ 0 ] +---+
| Phase [Deg][v][ 0 ] | |
| Time Span [ 20m ] | |
| Num Points [ 256 ] | |
+--------------------------+ +---->+-------+
| a * b +-->
+--------------------------+ +---->+-------+
| Function Generator | |
+--------------------------+ |
| Function [ Cosine ][v] | |
| Frequency [ 5000 ] | |
| Amplitude [ 1 ] | |
| DcOffset [ 0 ] +---+
| Phase [Deg][v][ 0 ] |
| Time Span [ 20m ] |
| Num Points [ 256 ] |
+--------------------------+
Multiplying two waveforms in VEE simply results in each element of one
waveform being multiplied by the corresponding element of another. Note use
of the word "corresponding": this is only going to work if the two waveforms
have the same timespan and number of points, otherwise you'll get an error
message (which is just as well, since the operation then doesn't make any
sense).
+------+ +---+------+
| Fgen +--->| F | FGen |
+------+ +---+------+
However, this won't work. You'll get back an error message saying that the
frequency input demands a scalar. This means that the way the FGen is
defined, it can only generate a waveform data object of a fixed frequency,
which is (as far as I am concerned) also just as well, since making it more
flexible than that would be complicated and the demand for doing it isn't
there.
+------+
| Fgen +--+
+------+ |
|
+--------------+
|
| +------------------------+
| | UnBuild Waveform |
| +---------+-+------------+
+-->| Wf Data | | Real Ary +---+
| | | Time Span | |
+---------+-+------------+ |
|
+--------------------------------+
|
| +------------+ +-----------+
+-->| totSize(x) +-->| For Count +--+
| +------------+ +-----------+ |
| |
| +-------------------------------+
| |
| | +-----------------+
| | | Formula |
| | +---+---------+---+ +---+------+
| +-->| I |[ F[I] ]| R +-->| F | FGen +----->+--------------+
+------>| F | | | +---+------+ | Concatenator +--+-->
+---+---------+---+ +-->+--------------+ |
| |
+---------------------+
This works, sort of. It produces a set of waveform objects of the desired
frequency and links them together. However, the results are pretty useless.
This is because each of elements has a time duration unrelated to the actual
modulating frequency's rate of change.
[20.10] ENTERING NON-ASCII CHARACTERS UNDER VEE
[20.11] A VEEWIN HIDDEN FEATURE