| # OptiVec Version 5 ## for C/C++ and Delphi
OptiCode Dr. Martin Sander Software Development Steinachstr. 9A D-69198 Schriesheim Germany http://www.optivec.com e-mail: optivec@gmx.de | # Part I. A: Handbook

This HANDBOOK describes the basic principles of the OptiVec libraries and gives an overview over VectorLib, the first part of OptiVec. The object-oriented interface, VecObj, is described in chapter 3. The other parts have their own descriptions in separate files, see MATRIX.HTM and CMATH.HTM.
Chapter 1.2 of this Handbook contains the licence terms for the Shareware version, Chapter 1.3 for the Registered version.
OptiCode™ and OptiVec™ are trademarks of Dr. Martin Sander Software Dev. Other brand and product names mentioned in this handbook for identification purposes are trademarks or registered trademarks of their respective holders.

| German-speaking users: Um die Kosten für das Herunterladen der Shareware-Version über das Internet für alle so gering wie möglich zu halten, enthält diese nur die englische Dokumentation. Sie finden die deutsche Beschreibung separat unter http://www.optivec.de/download/OVDOCD.ZIP.

# Contents

1. Introduction

| 1.1 Why Vectorized Programming Pays Off on the PC

| 1.1.1 General OptiVec Optimization Strategies
| 1.1.2 Multi-Processor Optimization

| 1.2 License Terms for the Shareware Version
| 1.3 Registered Versions

| 1.3.1 Registered Versions: Ordering
| 1.3.2 License Terms for the Registered Versions

| 1.4 Getting Started

2. The Elements of OptiVec Routines

| 2.1 Synonyms for Some Data Types
| 2.2 Complex Numbers: The Data Types fComplex, dComplex, eComplex, fPolar, dPolar, and ePolar
| 2.3 Vectors and Arrays: The Data Types fVector, dVector, eVector, cfVector, pfVector, iVector, uVector, etc.
| 2.4 Real-Number Functions: The Prefixes VF_, VD_, and VE_
| 2.5 Complex-Number Functions: The Prefixes VCF_, VCD_, VCE_, VPF_, VPD_, and VPE_
| 2.6 Functions of the Integer Data Types: The Prefixes VI_, VBI_, VSI_, VU_, etc.
| 2.7 Common Functions of Several Data Types: The Prefix V_

3. C++ only: VecObj, the Object-Oriented Interface for VectorLib
4. VectorLib Functions and Routines: A Short Overview

| 4.1 Generation, Initialization and De-Allocation of Vectors
| 4.2 Index-oriented Manipulations
| 4.3 Data-Type Interconversions
| 4.4 More about Integer Arithmetics
| 4.5 Basic Functions of Complex Vectors
| 4.6 Mathematical Functions

| 4.6.1 Rounding
| 4.6.2 Comparisons
| 4.6.3 Direct Bit-Manipulation
| 4.6.4 Basic Arithmetics, Accumulations
| 4.6.5 Geometrical Vector Arithmetics
| 4.6.6 Powers
| 4.6.7 Exponentials and Hyperbolic Functions
| 4.6.8 Logarithms
| 4.6.9 Trigonometric Functions

| 4.7 Analysis
| 4.8 Signal Processing: Fourier Transforms and Related Topics
| 4.9 Statistical Functions and Building Blocks
| 4.10 Data Fitting
| 4.11 Input and Output
| 4.12 Graphics

5. Error Handling

| 5.1 General Remarks
| 5.2 Integer Errors
| 5.3 Floating-Point Errors

| 5.3.1 C/C++ specific
| 5.3.2 Pascal/Delphi specific
| 5.3.3 Error Types (Both C/C++ and Pascal/Delphi)
| 5.3.4 Differences between Borland C++ 4.0 and earlier BC++ versions

| 5.4 The Treatment of Denormal Numbers
| 5.5 Advanced Error Handling: Writing Messages into a File
| 5.6 OptiVec Error Messages

6. Trouble-Shooting

| 6.1 General Problems
| 6.2 Problems with Windows 3.x?
| 6.3 Problems with 32-bit Windows?
| 6.4 Problems with Borland's 16-bit Linker?

7. The Include-Files and Units of OptiVec
P a r t I. B : File FUNCREF.HTM contains:
8. Alphabetical Reference of Vector Functions
9. Scalar (non-vectorized) Functions

#

1. Introduction

OptiVec offers a powerful set of routines for numerically demanding applications, making the philosophy of vectorized programming available for C/C++ and Pascal/Delphi languages. It serves to overcome the limitations of loop management of conventional compilers – which proved to be one of the largest obstacles in the programmer's way towards efficient coding for scientific and data analysis applications.

In comparison to the old vector language APL, OptiVec has the advantage of being incorporated into the modern and versatile languages C/C++ and Pascal/Delphi. Recent versions of C++ and Fortran do already offer some sort of vector processing, by virtue of iterator classes using templates (C++) and field functions (Fortran90). Both of these, however, are basically a convenient means of letting the compiler write the loop for you and then compile it to the usual inefficient code. The same is true for most implementations of the popular BLAS (Basic Linear Algebra Subroutine) libraries. In comparison to these approaches, OptiVec is superior mainly with respect to execution speed – on the average by a factor of 2-3, in some cases even up to 8. The performance is no longer limited by the quality of your compiler, but rather by the real speed of the processor!

There is a certain overlap in the range of functions offered by OptiVec and by BLAS, LINPACK, and other libraries and source-code collections. However, the latter must be compiled, and, consequently, their performance is determined mainly by the quality of the compiler chosen. To the best of our knowledge, OptiVec, was the first product on the market offering a comprehensive vectorized-functions library realized in a true Assembler implementation.

- All operators and mathematical functions of C/C++ and Pascal/Delphi are implemented in vectorized form; additionally many more mathematical functions are included which normally would have to be calculated by more or less complicated combinations of existing functions. Not only the execution speed, but also the accuracy of the results is greatly improved.
- A wide range of optimized matrix functions like matrix arithmetics, algebra, decompositions, data fitting, etc. is offered by MatrixLib.
  TensorLib is planned as a future extension of these concepts for general multidimensional arrays.
- Fast Fourier Transform techniques (both one- and two-dimensional) allow for efficient convolutions, correlation analyses, spectral filtering, and so on.
- Building blocks for statistical data analysis are supplied.
- Derivatives, integrals, interpolation schemes are included.
- Graphical representation of data offers a convenient way of monitoring the results of vectorized calculations.
- Each function exists for every data type for which this is reasonable. The data type is signalled by the prefix of the function name. No implicit name mangling or other specific C++ features are used in the C/C++ version, which makes OptiVec usable in plain-C as well as in C++ programs. Moreover, the names and the syntax of nearly all functions are the same in C/C++ and Pascal/Delphi languages.
- The input and output vectors/matrices of VectorLib and MatrixLib routines may be of variable size and it is possible to process only a part (e.g., the first 100 elements, or every 10th element) of a vector, which is another important advantage over other approaches, where only whole arrays are processed.
- A new object-oriented interface for C++, named VecObj, encapsulates all vector functions, offering even easier use and increased memory safety.
- Using OptiVec routines instead of loops can make your source code much more compact and far better readable.
- Besides the vectorized complex-number functions, CMATH is included. This is a comprehensive library of complex operations and functions, both in cartesian and polar coordinates. In comparison to complex class libraries shipped with C++ compilers and to the unit Complex of Delphi, CMATH is generally much faster, more complete, more accurate and numerically stable.

The wide range of routines and functions covered by OptiVec, the high numerical efficiency and increased ease of programming make this package a powerful programming tool for scientific and data analysis applications, competing with (and often beating) many high-priced integrated systems, but imbedded into your favourite programming language.

This documentation describes the OptiVec implementations for

- Borland / CodeGear C++ (all versions of RAD Studio, Borland C++ Builder and Borland C++, version 4.0 or higher, including Turbo C++ 2006) for Win32 (native Win32 only; no .NET applications!) or the Win32 mode of 64-bit Windows.
- Microsoft Visual C++ (all version of MSVC 5.0+ and Microsoft Visual Studio) for 32-bit Windows (or 32-bit mode of Win64) on PC platforms.
- Borland Delphi, versions 4 through 7, 2005, 2006 (including Turbo Delphi 2006), 2007 on 32-bit Windows (or 32-bit mode of Win64)

Please note that only the "outside appearance" and thus the documentation is the same for these different compilers. The libraries themselves are compiler-specific; each library can be used only with one compiler and, in the case of C/C++, with one memory model or one target:

1. Shareware Versions
   The Shareware version contain two libraries:
   1. General-Purpose debug version for full floating-point accuracy and widest processor compatibility range (down to 486DX / Pentium / Athlon). In order to prevent memory errors, possibly occurring through the violation of vector and matrix boundaries, this debug version checks all input vectors / matrices and their sizes for consistency.
      This version runs on all 32-bit Windows versions (Windows95/98/ME/2000/NT/XP/Vista, including the 32-bit mode of XP64 and Vista64).
   2. Multi-Processor library for use on AMD64 x2, Core2 Duo or their multi-core successors. In addition to the automatic distribution of the work load over the available processor cores (auto-threading), the speed is also enhanced by employing SSE and SSE2 commands for many floating-point operations. Be prepared, however, to accept a loss of accuracy of 1-3 significant digits as a consequence of the use of SSE commands.
      This version requires Windows XP or Vista.
2. Full Registered Versions
   The libraries come in four versions of processor utilization:
   - P4 (full floating-point accuracy and widest processor compatibility range, down to 486DX / Pentium / Athlon for all 32-bit Windows versions)
   - P6 (Pentium III or higher)
     up to 50% faster VF_ functions, with a certain loss of FP accuracy (1-3 digits). Needs at least Windows 2000 SP4.
   - P7 (min. Pentium IV)
     as P6; additionally up to 20% faster VD_ functions, again at the expense of 1-3 siginificant digits of accuracy.
   - P8 (Core2xxx or AMD64xxx)
     as P7; additionally up to 40% faster complex-number multiplications in VCF_ and MCF_ functions, again sacrificing 1-3 digits of accuracy. VCD_ functions profit only marginally (max. 10%).
   These versions are differentiated into sub-versions:
   - Normal Version:
     Optimized for vectors / matrices with up to several 100 elements and for single-core processors.
     The P4 library is compatible with all 32-bit Windows versions (including 32-bit mode on 64-bit Windows). From the P6 library on, at least Windows 2000 SP4 is required.
   - Debug Version with dimension-consistency checking:
     In order to prevent hard-to-track memory errors, possibly occurring through the violation of vector and matrix boundaries, this debug version checks all input vectors / matrices and their sizes for consistency. It is highly recommended to use the debug libraries during the debugging phase of application development and switch to the higher-performance versions only after you have made sure that no memory issues, as detected by the debug libraries, remain unsolved.
     Operating system requirements: as the normal version.
   - Multi-Processor Version:
     For larger vectors and matrices, the work load can be distributed among several processor cores. Thereby, the performance can theoretically be multiplied by the number of available processor cores. The Multi-Processor version initiates additional worker threads which, in turn, are attributed to the processor units by the operating system. Every function checks if the work can reasonably be distributed (auto-threading). Windows XP or Vista are needed.
     The decision, if the multi-processor version should be employed, strongly depends on the nature of the desired calculations. The reason lies in the inter-thread communication "bureaucracy" involved in the auto-threading mechanism. Only from a certain vector / matrix size on, these overhead losses can be earned back. For the transcendental functions of complex arguments, this is the case already at vector lengths of 60-80 elements, whereas, for a function like VF_addV, the performance scales with the number of processors only from about 10,000 elements on. Normally, the decision between the normal and the multi-processor version will be made just by trying both.
     An application, linked with the multi-processor library, runs without problems on a single-processor computer. However, the speed will typically be 10-20ß% slower than with the normal library.
   - Large-Vector Version
     For very large vectors / matrices, which would not fit into cache memory anyway, some performance can be gained through bypassing the cache. On the other hand, mis-use of such a library for smaller vectors generally results in a drastic loss of performance. Accordingly, the Large-Vector Version is intended for the processing of very large vectors / matrices on single-core computers. As single-core machines seem to disappear from the market in the near future, and as the available cache sizes and memory transfer speeds are getting higher and higher, the Large-Vector version may be dropped in the future.
     Operating system requirements: as the normal version, i.e. down to Windows95 for the P4 library; at least Windows 2000 SP4 for P6 or higher libraries.

#### Specifics of the OptiVec versions for the different compilers

- Borland / CodeGear C++:
  You have to choose between static and dynamic BC++ run-time library and link VCFS.LIB + VCF4D.LIB with the static BC++ RTL, but VCFD.LIB + VCF4D.LIB with the dynamic RTL.
- Microsoft Visual C++:
  The Shareware version comes with libraries for "single-thread debug", "multi-thread debug", and "multi-thread DLL debug". The full (registered) version contains additional libraries for the three corresponding release libraries. Additionally, a set of libraries to be used only with Visual Studio 2005 or higher incorporates some safety improvements of VS 2005.
- Borland Delphi:
  Each Delphi release requires its own OptiVec library version. Only Delphi 2006 and 2007 share the same OptiVec units.

Back to Table of Contents

## 1.1 Why Vectorized Programming Pays Off on the PC

To process one-dimensional data arrays or "vectors", a programmer would normally write a loop over all vector elements. Similarly, two- or higher-dimensional arrays ("matrices" or "tensors") are usually processed through nested loops over the indices in all dimensions. The alternative to this classic style of programming are vector and matrix functions.
Vector functions act on whole arrays/vectors instead of single scalar arguments. They are the most consequent form of "vectorization", i.e., organisation of program code (by clever compilers or by the programmer himself) in such a way as to optimize vector treatment.

Vectorization has always been the magic formula for supercomputers with their multi-processor parallel architectures. On these architectures, one tries to spread the computational effort equally over the available processors, thus maximizing execution speed. The so-called "divide and conquer" algorithms break down more complicated numerical tasks into small loops over array elements. Sophisticated compilers then find out the most efficient way how to distribute the array elements among the processors. Many supercomputer compilers also come with a large set of pre-defined proprietary vector and matrix functions for many basic tasks. These vectorized functions offer the best way to achieve maximum throughput.

Obviously, the massive parallel processing of, say, a Cray is not possible even on modern PCs with their modest 2 or 4-processor core configurations, let alone on the classical single-processor PC. Consequently, at first sight, it might seem difficult to apply the principle of vectorized programming to the PC. Actually, however, there are many vector-specific optimizations possible, even for computers with only one CPU. Most of these optimizations are not available to present compilers. Rather, one has to go down to the machine-code level. Hand-optimized, Assembler-written vector functions outperform compiled loops by a factor of two to three, on the average. This means that vectorization, properly done, is indeed worth the effort, also for PC programs.

### 1.1.1 General OptiVec Optimization Strategies

Here are the most important optimization strategies, employed in OptiVec to boost the performance on any PC (regardless of the number of processor cores):

Prefetch of chunks of vector elements
Beginning with the Pentium III processor, Intel introduced the very useful feature of explicit memory prefetch. With these commands, it is possible to "tell" the processor to fetch data from memory sufficiently in advance, so that no time is waisted waiting for them when they are actually needed.

Cache control
The Pentium III+ processors offer the possibility to mark data as "temporal" (will be used again) or "non-temporal" (used only once), while they are fetched or stored. In OptiVec functions, it is assumed that input vectors (and matrices) will not be used again, whereas the output vectors are likely to become the input for some ensuing procedure. Consequently, the cache is bypassed while loading input data, but the output data are written into the cache. Of course, this approach breaks down if the vectors or matrices become too large to fit into the cache. For these cases, a large-vector version of the OptiVec libraries is available which bypasses the cache also while writing the output vectors. For simple arithmetic functions, up to 20% in speed are gained as compared to the small-and-medium-size version. On the other hand, as this large-vector version effectively switches the cache off, a drastic performance penalty (up to a factor of three or four!) will result, if it is used for smaller systems. For the same reason, you should carefully check if your problem could perhaps be split up into smaller vectors, before resorting to the large-vector version. This would allow to achieve the much higher performance resulting from efficient data caching.

Use of SIMD commands
You might wonder why this strategy is not listed first. The SSE or "Streaming Single-Instruction-Multiple-Data Extensions" of Pentium III, Pentium 4 and their successors provide explicit support for vectorized programming with floating-point data in float / single or double precision (the latter only for Pentium 4). At first sight, therefore, they should revolutionize vector programming. Given the normal relation between processor and data bus speeds, however, many of the simple arithmetic operations are data transfer limited, and the use of SIMD commands does not make the large difference (with respect to well-written FPU code) it could make otherwise. In most cases, the advantage of treating four floats in a single command melts down to a 20-30% increase in speed (which is not that bad, anyway!). For more complicated operations, on the other hand, SIMD commands often cannot be employed, either because conditional branches have to be taken for each vector element individually, or because the "extra" accuracy and range, available by traditional FPU commands (with their internal extended accuracy), allows to simplify algorithms so much that the FPU code is still faster. As a consequence, we use SIMD commands only where a real speed gain is possible. Please note, however, that, the SIMD-employing library versions (P6, P7 etc.) generally sacrifices 2-3 digits of accuracy in order to attain the described speed gain. If this is not acceptable for your specific task, please stay with the P4 libraries.

Preload of floating-point constants
Floating-point constants, employed in the evaluation of mathematical functions, are loaded onto the floating-point number stack outside of the actual loop and stay as long as they are needed. This saves a large amount of loading/unloading operations which are necessary if a mathematical function is called for each element of a vector separately.

Full FPU stack usage
Where necessary, all eight coprocessor registers and/or all eight XMM registers are employed. (For present compilers, it is already an excellent achievement to master the efficient bookkeeping for only four of them.)

Superscalar scheduling
By careful "pairing" of commands whose results do not depend upon each other, the two integer pipes and the two fadd/fmul units of the Pentium/PentiumXX are used as efficiently as possible. Older computers equipped just will not care about these optimizations which they cannot profit from.

Loop-unrolling
Where optimum pairing of commands cannot be achieved for single elements, vectors are often processed in chunks of two, four, or even more elements. This allows to fully exploit the parallel-processing capabilities of the Pentium and its successors. Moreover, the relative amount of time spent for loop management is significantly reduced.

Simplified addressing
The addressing of vector elements is still a major source of inefficiency with present compilers. Switching forth and back between input and output vectors, a large number of redundant addressing operations is performed. The strict (and easy!) definitions of all OptiVec functions allow to reduce these operations to a minimum.

Replacement of floating-point by integer commands
For any operations with floating-point numbers that can also be performed using integer commands (like copying, swapping, or comparing to preset values), the faster method is consistently employed.

Strict precision control
C compilers convert a float into a double – Borland Pascal/Delphi even into extended – before passing it to a mathematical function. This approach was useful at times when disk memory was too great a problem to include separate functions for each data type in the .LIB files, but it is simply inefficient on modern PCs. Consequently, no such implicit conversions are present in OptiVec routines. Here, a function of a float is calculated to float (i.e. single) precision, wasting no time for the calculation of more digits than necessary – which would be discarded anyway. There is also a brute-force approach to precision-control: You can call V_setFPAccuracy( 1 ); to actively switch the FPU to single precision, if that is enough for a given application. Thereby, execution can be slightly sped up from Pentium CPUs on. Be, however, prepared to accept even lower-than-single accuracy of your end results, if you elect this option. For further details and precautions, see V_setFPAccuracy.

All-inline coding
All external function calls are eliminated from the inner loops of the vector processing. This saves the execution time necessary for the "call / ret" pairs and for loading the parameters onto the stack.

Cache-line matching of local variables
The Level-1 cache of the Pentium and its 32-bit successors is organized in lines of 32 bytes each, modern 64-bit processors use 64-byte lines. Many OptiVec functions need double-precision or extended-precision real local variables on the stack (mainly for integer/floating-point conversions or for range checking). Present compilers align the stack on 4-byte boundaries, which means there is a certain chance that the 8 bytes of a double or the 10 bytes of an extended, stored on the stack, will cross a cache-line boundary. This, in turn, would lead to a cache line-break penalty, deteriorating the performance. Consequently, those OptiVec functions where this is an issue, use special procedures to align their local variables on 8-byte (for doubles), 16-byte (for extendeds), or 32-byte boundaries (for XMM values).

Unprotected and reduced-range functions
OptiVec offers alternative forms of some mathematical functions, where you have the choice between the fully protected variant with error handling and another, unprotected variant without. In the case of the integer power functions, for example, the absence of error checking allows the unprotected versions to be vectorized much more efficiently. Similarly, the sine and cosine functions can be coded more efficiently for arguments that the user can guarantee to lie in the range -2p and +2p. In these special cases, the execution time may be reduced by up to 40%, depending on the hardware environment. This increased speed has always to be balanced against the increased risk, though: If any input element outside the valid range is encountered, the unprotected and reduced-range functions will crash without warning.

### 1.1.2 Multi-Processor Optimization

Multithread support
All the above being said about single-CPU PCs, multi-processors computers (with Intel's Core2Duo, Quattro etc., AMD's Athlon 64 X2, X4 etc., or workstations and servers equipped with 2 or 4 PentiumXX chips) do allow the operating system to distribute threads among the available processors, doubling or quadrupling the overall performance. For that, any functions running in parallel must be prevented from interfering with each other through read/write operations on global variables. With very few exceptions (namely the plotting functions, which have to use global variables to store the current window and coordinate system settings, and the non-linear data-fitting functions), all other OptiVec functions are reentrant and may run in parallel.
Be careful with multi-threading, if you are using the P6, P7, or P8 versions of OptiVec: The earlier releases of 32-bit Windows do not save the XMM registers (employed in the SIMD commands) during task switches. No such problems occur with Windows XP or Vista.

When designing your multi-thread application, you have two options: functional parallelism and data parallelism.

Functional Parallelism
If different threads are performing different tasks – they are functionally different – one speaks of functional parallelism. As an example, consider one thread handling user input / output, while another one performs background calculations. Even on a single-core CPU, this kind of multi-threading may offer advantages (e.g., the user interface does not block during extensive background calculations, but still takes input). On a multi-core computer, the two (or more) threads can actually run simultaneously on the different processor cores. In general, however, the load balance between the processor cores is far from perfect: often, one processor is running at maximum load, while another one is sitting idle, waiting for input. Still, functional multithreading is the best option whenever your numerical tasks involve vectors and matrices of only small-to-moderate size.

Data Parallelism
In order to improve the load balance between the available processor cores, thereby maximizing throughput, it is possible to employ classical parallel processing: the data to be processed is split up into several chunks, each thread getting one of these chunks. This is aptly called data parallelism. The usefulness of this approach is limited by the overhead involved in the data distribution and in the thread-to-thread communication. Moreover, there are always parts of the code which need to be processed sequentially and cannot be parallelized. Therefore, data parallelism pays off only for larger vectors and matrices. Typical break-even sizes range from about 100 (for the calculation of transcendental functions of complex input values) to several 10,000 elements (as in the simple arithmetic functions). Only when your vectors and matrices are considerably larger than that threshold, the performance is actually improved over a functional-parallelism approach. The boost then quickly approaches (but never exactly reaches) the theoretical limit of a factor equal to the number of processor cores available.

Choosing the right OptiVec Library
Whenever you want your application to run on a wide range of supported platforms, and when your vectors and matrices are only of small-to-moderate size, we recommend to use the general-purpose libraries, OVVC4.LIB (for MS Visual C++), VCF4W.LIB (for Borland C++), or the units in OPTIVEC\LIB4 (for Delphi). These libraries combine good performance with back-compatibility to older hardware, down to 486DX, Pentium, old models of Athlon. They are all multi-thread safe and support functional parallelism. If you do not need full floating-point accuracy and that amount of back-compatibility, you can get higher performance by switching to the P6, P7, or P8 libraries (marked by the respective number in the in the library name).

For large vectors/matrices on single-core machines from Pentium III+ on, we offer versions gaining some performance by simply bypassing the data cache. These Large-Vector Libraries are marked by the letter "L": OVVC6L.LIB (for MS Visual C++), VCF6L.LIB (for Borland C++), or the units in OPTIVEC\LIB6L (for Delphi). Replace the "6" with "7" to get the Pentium 4+ versions, and so on. If mis-used for smaller vectors / matrices, the Large-Vector libraries will perform significantly slower than the general-purpose libraries!

Finally, for large vectors/matrices on multi-core machines, our new multi-core optimized libraries actively distribute the work load over the available processor cores for data parallel execution. These libraries are marked by the letter "M", as in OVVC7M.LIB (for MS Visual C++, using SSE2), VCF4M.LIB (for Borland C++, full FPU accuracy), or the units in OPTIVEC\LIB8M (for Delphi, using SSE3). These libraries are designed for AMD 64 x2, Intel Core2 Duo, or machines equipped with several discrete processors of the Pentium 4+ level.
The "M" libraries will still run on single-core machines, but – due to the thread-management overhead – somewhat slower than the general-purpose libraries. Although the "M" libraries are designed with medium to large vectors in mind, the penalty for using them with smaller vectors is almost negligible, as the OptiVec thread-engine automatically executes a function in a single thread, if the vector size is too small for parallel execution to earn back the cost involved in the thread-to-thread communication.
If you use the "M" libraries, your programme must call V_initMT( nAvailProcCores ) before any of the vector functions.

Back to Table of Contents

## 1.2 Licence Terms for the Shareware Version

The following licence terms apply to the Shareware version of OptiVec. For the licence terms of the Registered version, please see paragraph 1.3.

This is the Shareware version of OptiVec ("SOFTWARE").
It may be used under the following licence terms:

1. You may test the SOFTWARE free of charge for a period of up to 90 days on one computer.
2. Applications, created with the Shareware version of this SOFTWARE, will run only on the same computer on which this SOFTWARE has been installed. They cannot and may not be distributed to others. After the end of the trial period, they will cease functioning.
3. If you want to continue using this SOFTWARE after testing, and/or if you wish to distribute programs containing functions of this SOFTWARE, you have to purchase the registered version (see chapter 1.3).
4. This SOFTWARE is provided on an "as is" basis. Any explicit or implicit warranties for the SOFTWARE are excluded.
5. Despite thorough testing of the SOFTWARE, errors and bugs cannot be excluded with certainty. No claims as to merchantability or fitness for a particular purpose are made.
6. You may not use the SOFTWARE in any environment or situation where personal injury or excessive damage to anyone's property (including your own) could arise from malfunctioning of the SOFTWARE.
7. You may not decompile, disassemble, or otherwise reverse engineer the SOFTWARE into a machine-readable form. You may, however, inspect the functions it contains by means of debuggers like those included in the Borland and Microsoft compilers.

Copyright for the SOFTWARE and its documentation © 1996-2008 OptiCode – Dr. Martin Sander Software Dev.
All rights reserved, including those of translation into foreign languages.

Back to Table of Contents

## 1.3 Registered Versions

## 1.3.1 Registered Versions: Ordering

In order to make this product affordable also for those who will not themselves make money using it, we offer an educational edition at a strongly reduced rate, in addition to the full commercial edition. The contents of these two editions is identical. The only difference lies in the restrictions of use: The educational edition may not be used for commercial / business / government purposes, but is restricted to private and educational use.

Purchasing the full (registered) version gives you the right to use it on as many computers at a time as the number of units you bought.

The right to distribute applications employing functions of OptiVec is included in the commercial-version licence. No run-time licence are needed for your customers! Corporate site and world-wide licences are available upon request.

The full versions (both the commercial and the educational editions) of OptiVec

- support all targets of 32-bit Windows: GUI and Console, with static and with dynamic run-time library (Borland / CodeGear C++)
- single-thread, multi-thread, multi-thread DLL debug and release for Win32 (Microsoft Visual C++)
- Delphi 4 - 7, 2005, 2006, 2007 (Borland / CodeGear Delphi)
- have individually optimized libraries for each degree of floating-point treatment / processor backward-compatibility:
  P8 (requiring Intel Core2xxx or AMD64xxx)
  P7 (requiring Pentium 4+)
  P6 (requiring Pentium III+)
  P4: full FPU accuracy, optimized for Pentium II, requiring 486DX/Pentium+
  386/387+ and 286+ for 16-bit models (BC++ only): These libraries are no longer updated; the old versions are still available upon request
- (C/C++ versions only:) come with free printed documentation (which, however, is updated much less frequently than the electronic documentation).
- entitle you to two years of free updates (by downloading from our web site)
- can be ordered the following ways:
  a) International orders (credit-card or US cheque) by Internet, mail, FAX, or phone
  b) Orders within the European Union (pre-paid or upon invoice)

### a) International Orders: Pricing

#### OptiVec for Borland C/C++, Microsoft Visual C++, or Borland Delphi
(CD-ROM, printed handbook available for the C/C++ versions in English or German)

$ 99 for 1 unit of the educational edition
$ 249 for 1 unit of the commercial edition
$ 699 for 5 units (Purchase Order only)
$ 1199 for 10 units (Purchase Order only)
Add $ 5 for S&H (postal) and applicable VAT.

#### CMATH for Borland C/C++, Microsoft Visual C++, or Borland Delphi separately
(CD-ROM, manual in English)

$ 39 for 1 unit of the educational edition
$ 60 for 1 unit of the commercial edition
$ 200 for 5 units (Purchase Order only)
$ 350 for 10 units (Purchase Order only)
Add $ 5 for S&H (postal) and applicable VAT.

#### OptiVec for Borland (Turbo) Pascal

$ 35 for 1 unit + $ 5 for S&H

#### CMATH for Borland (Turbo) Pascal separately

$ 25 for 1 unit + $ 5 for S&H
Please note that the Turbo Pascal version is no longer updated. You can still get the libraries of version 4.0, but they come without support and without updates.

### International: Ordering Options

For your protection, we handle credit-card orders through our specialized partner, ShareIt!
When ordering online through ShareIt, please use the product-specific links below:
OptiVec for Borland C/C++ (English)
OptiVec für Borland C/C++ (Deutsch)
CMATH for Borland C/C++
OptiVec for Microsoft Visual C++
CMATH for Microsoft Visual C++
OptiVec for Borland Delphi
CMATH for Borland Delphi
OptiVec for Borland (Turbo) Pascal
CMATH for Borland (Turbo) Pascal

You may also order by e-mail to register@shareit.com.
US customers can also contact ShareIt! by telephone 1-724-850-8186 or FAX 1-724-850-8189 (only for orders, please).
Note the program No.:

| commercial | educational
OptiVec for Borland C/C++ (English) | 101557 | 102654
OptiVec für Borland C/C++ (Deutsch) | 101556 | 149813
CMATH for Borland C/C++ | 101353 | 102655
OptiVec for Microsoft Visual C++ | 103421 | 149811
CMATH for Microsoft Visual C++ | 103422 | 103441
OptiVec for Borland (Turbo) Pascal | 103423 |
CMATH for Borland (Turbo) Pascal | 103424 |
OptiVec for Borland Delphi | 103443 | 103859
CMATH for Borland Delphi | 103844 | 103860

Please note that the Turbo Pascal version is no longer updated. You can still get the libraries of version 4.0, but they come without support and without updates.

### b) Orders in the European Union, including Iceland, Norway, Switzerland

If you can pay in Euro and order directly from the author, the pricing is

#### OptiVec for Borland C/C++, Microsoft Visual C++, or Borland Delphi
(CD-ROM, printed handbook available for the C/C++ versions in English or German)

EUR 89 for 1 unit of the educational edition
EUR 199 for 1 unit of the commercial edition
EUR 649 for 5 units
EUR 1199 for 10 units
(incl. 19% German VAT, plus EUR 5,- handling charge).

#### CMATH for Borland C/C++, Microsoft Visual C++, or Borland Delphi separately
(CD-ROM, manual in English)

EUR 39 for 1 unit of the educational edition
EUR 59 for 1 unit of the commercial edition
EUR 199 for 5 units
EUR 349 for 10 units
(incl. 19% German VAT, plus EUR 5,- handling charge).

#### OptiVec for Borland (Turbo) Pascal

EUR 29 for 1 unit + EUR 5 for S&H (incl. 19% German VAT)

#### CMATH for Borland (Turbo) Pascal separately

EUR 19 for 1 unit + EUR 5 for S&H (incl. 19% German VAT)
Please note that the Turbo Pascal version is no longer updated. You can still get the libraries of version 4.0, but they come without support and without updates.

If you have a European VAT ID, or if you order from outside the European Union, you are exempt from German VAT, and it will be deduced from your bill, but you may have to pay your local VAT and/or import duties according to local laws.

Please send a print-out of this order form to

OptiCode – Dr. Martin Sander Software Dev.
Steinachstr. 9A
D-69198 Schriesheim
Germany

FAX +49 - 6203 - 601 733

For any other questions related to ordering OptiVec, please contact us at: optivec@gmx.de

Back to Table of Contents

### 1.3.2 License Terms for the Registered version

If you got this file with the registered version of OptiVec, these are the license terms valid for you:

This is a single copy license for OptiVec ("SOFTWARE"), granted by OptiCode – Dr. Martin Sander Software Development ("OptiCode").

The SOFTWARE in this package is licensed to you as the user. It is not sold. The term "user" means a programmer who links binary code of this SOFTWARE into his own applications. Those people using, in turn, his applications without the need of installing this SOFTWARE themselves, do not need any runtime license for the SOFTWARE. The right to distribute applications containing code of this SOFTWARE is included in the license fee for the commercial version.

Once you have paid the required license fee, you may use the SOFTWARE for as long as you like, provided you do not violate the copyright and if you observe the following rules:

1. You may use the SOFTWARE on any computer for which it is designed, as long as not more than one person uses it at any time.
2. You may make backup copies of the SOFTWARE for your personal use. You may only transfer the SOFTWARE to somebody else if you transfer the original and all copies, retaining no copies for yourself. You may not lease or rent the SOFTWARE to others.
3. You may not decompile, disassemble, or otherwise reverse engineer the SOFTWARE into a machine-readable form. You may, however, inspect the functions contained in this SOFTWARE by means of debuggers like those included in the Borland and Microsoft compilers.
4. If you payed the reduced licence fee for the "educational version" rather than the full rate for the "commercial version", the use of this SOFTWARE is restricted to private and educational purposes. In this case, you may not use the SOFTWARE for commercial purposes or for government purposes other than education.
   Applications using functions of this SOFTWARE may be freely distributed (i.e. without any run-time licence) only if created with the "commercial edition" and on condition that the functions of this SOFTWARE are permanently linked into a program etc., but do not appear as a library to the user of that application.
5. You may not use the SOFTWARE in any environment or situation where personal injury or excessive damage to anyone's property (including your own) could arise from malfunctioning of the SOFTWARE.
6. OptiCode's liability is limited by the enclosed Limited Warranty. In no case shall OptiCode's liability exceed the license paid for the right to use the SOFTWARE.

Limited Warranty for the Registered version

1. OptiCode warrants that the magnetic or optic media on which the SOFTWARE is recorded are free from defects in materials and workmanship under normal use. The SOFTWARE itself will perform substantially in accordance with the specifications set forth in the documentation.
2. The above express warranties are made for a period of six months from the date the SOFTWARE is delivered to you as the first user.
3. Any magnetic/optic or printed media from this package proving defective in materials or workmanship will be replaced on an exchange basis.
4. Great care has been taken to ensure that the SOFTWARE operates in accordance with the specifications as described in the documentation. However, it is not guaranteed that this SOFTWARE will operate completely free of errors or that the documentation is free of errors.
5. Any implied warranties including any warranties of merchantability or of fitness for a particular purpose are limited to the terms of the above express warranties.
6. OptiCode shall not in any case be liable for special, incidental, consequential, indirect or other damages arising from any breach of these warranties or of the license conditions, even if he has been notified of the possibility of such damages.

Copyright for the SOFTWARE and its documentation © 1996-2008 OptiCode – Dr. Martin Sander Software Development. All rights reserved.

Back to Table of Contents

## 1.4 Getting Started

To install OptiVec, please follow these steps:

1. In order to use OptiVec, you need an already installed copy of your C/C++, Delphi, or Pascal compiler. Install OptiVec by executing INSTALL.EXE from the root directory of the installation disk or CD-ROM. Normally, OptiVec will be installed into a directory named "OPTIVEC". This directory holds the documentation.
2. Include the OptiVec lib and include (C/C++) or units (Pascal/Delphi) subdirectories into the search path.
3. a) C/C++:
   Assuming your OptiVec directory is C:\OPTIVEC, add
   C:\OPTIVEC\LIB to the library search path and
   C:\OPTIVEC\INCLUDE to the include-file search path of the IDE (and of the configuration file BCC32.CFG in case you are using Borland's command-line compilers).
   b) Delphi, Shareware version:
   The units (.DCU files) will be installed into the directories OPTIVEC\LIB4D and OPTIVEC\LIB7M.
   c) Delphi, Registered version:
   Delphi: The installation routine you have to execute is named after the target Delphi version: INSTALL4.EXE, INSTALL5.EXE, INSTALL6.EXE, etc. The installation routine for Delphi 2005 is INSTALL9.EXE, the one for both Delphi 2006 and 2007 (Borland Developer Suite, CodeGear RAD Studio, or Turbo Delphi) is INSTALL10.EXE.
   The units (.DCU files) of the normal version for 486DX/Pentium will be installed into OPTIVEC\LIB4, those for Pentium III+ into OPTIVEC\LIB6, those for Pentium 4+ into OPTIVEC\LIB7, and those for Core2xxx / AMD64xxx into OPTIVEC\LIB8.
   The debug library with dimension-consistency checking goes into OPTIVEC\LIB4D.
   The single-processor large-vector versions go into OPTIVEC\LIB6L (Pentium III+), OPTIVEC\LIB7L (Pentium 4+), and OPTIVEC\LIB8L (Core2xxx, AMD64xxx).
   The multi-processor libraries are installed into OPTIVEC\LIB4M, OPTIVEC\LIB6M, OPTIVEC\LIB7M, and OPTIVEC\LIB8M.
4. Choose the desired platform, target, and configuration:
   1. Borland C++:
      You have to include two OptiVec libraries: First the OptiVec base library, VCFS.LIB (interface to the static runtime library of BC++) or VCFD.LIB (interface to the dynamically-linked runtime library of BC++). Second, select the required processor-specific library from the following table (add the suffix .LIB) and add it to your project.
      The Shareware version contains the libraries VCFS.LIB, VCFD.LIB, VCF4D.LIB, and VCF7M.LIB.

      Processor | General-purpose library | Debug library | Large-Vector library | Multi-Processor library
      | P4: Full FPU accuracy, 486DX/Pentium | VCF4W.LIB | VCF4D.LIB | ---- | VCF4M.LIB | P6: Pentium III+ | VCF6W.LIB | ---- | VCF6L.LIB | VCF6M.LIB | P7: Pentium 4+ | VCF7W.LIB | ---- | VCF7L.LIB | VCF7M.LIB | P8: AMD64xxx, Core2xxx | VCF8W.LIB | ---- | VCF8L.LIB | VCF8M.LIB

      The libraries for multi-processor systems run on workstations with discrete processors, as well as on multi-core processors like AMD64x2, Core2 Duo and so on.
   2. Visual C++: You have to include two OptiVec libraries. The first one contains the interface between OptiVec and the VC++ runtime library; it has to be matched with the specific version of the VC++ runtime library you chose for your project. The second one is independent from the configuration and runtime library; you have to choose it according to the desired CPU support.
      First choose the project configuration and runtime library. The latter is set at Project / (Configuration) Settings / C/C++ / Code Generation / Runtime Library. Under Project / (Configuration) Settings / Linker / Input, add the matching OptiVec library to your project, according to the following table. The Single-Thread libraries can be used only with Visual C++ versions up to and including Visual Studio 2003. From Visual Studio 2005 on, you always have to use the multithread libraries, even if your application generates only one thread. The "normal" OptiVec base libraries are compatible with all versions of Visual C++. If you are working with Visual Studio 2005, however, we recommend to profit from some safety improvements by linking the OptiVec base libraries marked by an additional "8" (present only in the registered version).

      Runtime Library | OptiVec base library (all VC++-versions) | only Visual Studio 2005 or higher
      single-thread debug | OVVCSD.LIB | ----
      single-thread release | OVVCSR.LIB | ----
      multi-thread debug | OVVCMTD.LIB | OVVC8MTD.LIB
      multi-thread release | OVVCMTR.LIB | OVVC8MTR.LIB
      multi-thread DLL debug | OVVCMDD.LIB | OVVC8MDD.LIB
      multi-thread DLL release | OVVCMDR.LIB | OVVC8MDR.LIB

      After that, please add the second, processor-specific OptiVec library according to the following table:

      Processor | General-purpose library | Debug library | Large-Vector library | Multi-Processor library
      | P4: Full FPU accuracy, 486DX/Pentium | OVVC4.LIB | OVVC4D.LIB | ---- | OVVC4M.LIB | P6: Pentium III+ | OVVC6.LIB | ---- | OVVC6L.LIB | OVVC6M.LIB | P7: Pentium 4+ | OVVC7.LIB | ---- | OVVC7L.LIB | OVVC7M.LIB | P8: AMD64xxx, Core2xxx | OVVC8.LIB | ---- | OVVC8L.LIB | OVVC8M.LIB

      The libraries for multi-processor systems run on workstations with discrete processors, as well as on multi-core processors like AMD64x2, Core2 Duo and so on.
      In order to allow OptiVec to be used in applications both with and without MFC, it calls the Windows API only directly, not via MFC. However, if you use MFC (either as a static library or as a DLL), Visual C++ does not automatically link the import library, user32.lib. You have to explicitly do this yourself: The line, Project / (Configuration) Settings / Linker / Input / Object and Library Modules must contain user32.lib. Otherwise you would get the linker error "error LNK2001: Unresolved external symbol __imp__MessageBoxA@??".
   3. Borland / CodeGear Delphi:
      The choice between the various OptiVec libraries is made through the selection of the unit search path. The Shareware version comes with two sets of units, one in the directory LIB4D, the other in LIB7M.
      In the registered version, you have the following choices:

      Processor | General-purpose library | Debug library | Large-Vector library | Multi-Processor library
      | P4: Full FPU accuracy, 486DX/Pentium | LIB4 | LIB4D | ---- | LIB4M | P6: Pentium III+ | LIB6 | ---- | LIB6L | LIB6M | P7: Pentium 4+ | LIB7 | ---- | LIB7L | LIB7M | P8: AMD64xxx, Core2xxx | LIB8 | ---- | LIB8L | LIB8M

5. Declare the use of OptiVec functions in your program:
   - C/C++:
     Use #include directives to include the header files described in chapter 7.
     To get the whole OptiVec library at once, along with its new object-oriented interface, declare
     #include <OptiVec.h>
     To get only all vector functions (without the object-oriented interface),
     #include <VecAll.h>
     To add all data-fitting and matrix functions to that,
     #include <MatAll.h>.
     If you are writing MFC or Borland C++ ObjectWindows applications, any OptiVec header files should be included after the MFC or OWL header files.
   - Delphi:
     Declare the use of OptiVec units as usual with the "uses" statement. The OptiVec units are grouped according to the data type. See chapter 7. In Delphi, OptiVec units should always be used together with the WinProcs unit, as some data types are borrowed from it.
6. Have a look into the sample programs:
   - VDEMO.DSW (project map for Visual C++)
   - VDEMOB6.BPR and FITDEMOB6.BPR (project files for Borland C++ Builder 6 or higher)
   - VDEMOB.BPR and FITDEMOB.BPR (project files for Borland C++ Builder 4 or higher)
   - VDEMO.DPR, FITDEMO.DPR, and CDEMO.DPR (Delphi projects)

After these preparations, all OptiVec functions are available for your programs.
Should you wish to remove OptiVec from your computer, please run UNINSTAL.EXE or simply delete the directory OPTIVEC with its subdirectories.

Back to Table of Contents

#

2. Elements of OptiVec Routines

## 2.1 Synonyms for Some Data Types

To increase the versatility and completeness of OptiVec, additional data types are defined in <VecLib.h> or the unit VecLib:

### a) C/C++ only:

The data type ui (short for "unsigned index") is used for the indexing of vectors and is defined as "unsigned int".

The 64-bit integer data type (__int64 in BC++ Builder and MS Visual C++, Int64 in Delphi) is called quad (for "quadword integer") in OptiVec.
The type quad is always signed. At present, OptiVec does not offer functions for unsigned 64-bit integers.

- Borland C++ only: In order to maintain compatibility with older BC versions (which did not directly support 64-bit integers), the data type quad is implemented as a struct of two 32-bit values. Floating-point numbers (preferably long doubles with their 64-bit mantissa) have to be used as intermediates. The necessary interface functions are setquad, quadtod and _quadtold. Alternatively, the two 32-bit halves may explicitly be set, as in:
  xq.Hi = 0x00000001UL;
  xq.Lo = 0x2468ABCDUL;

The data type extended, which is familiar to Pascal/Delphi programmers, is defined as a synonym for "long double" in OptiVec for C/C++. As Visual C++ does not support 80-bit reals, we define "extended" as "double" in the OptiVec versions for that compiler.
The reason for the choice of the name "extended" is that all OptiVec routines shall have identical names in C/C++ and Pascal/Delphi languages. Since the function prefixes are derived from the data types of the processed vectors (see below), this necessitates the definition of alias names for some data types denoted differently in the various languages. While the letter "L" (which could possibly stand for "long double") is already overcrowded by the data types long int and unsigned long, the letter "E" is unique to the data type extended and therefore used in the prefixes for vectors and functions of long double precision. This way, the letters defining the real- number data types are in alphabetical proximity: "D" for double, "E" for extended, and "F" for float. Maybe the future will bring high-precision 128-bit and 256-bit real numbers which could find their place in this series as "G" for "great" and "H" for "hyper".

### b) Pascal/Delphi only:

The data type Float, which is familiar to C/C++ programmers, is defined as a synonym for Single. We prefer to have the letters defining the real-number data types in alphabetical proximity: "D" for Double, "E" for Extended, and "F" for Float. As noted above, possible future 128-bit and 256-bit real numbers could find their place in this series as "G" for Great and "H" for Hyper.

For historical reasons (dating back to the development of Turbo Pascal), the various integer data types have a somewhat confusing nomenclature in Delphi. The WinProcs unit of Delphi offers already a more systematic nomenclature. In order to make the derived function prefixes compatible with the C/C++ versions of OptiVec, we define a number of synonyms, as described in the following table:

type | Pascal / Delphi name | synonym | derived prefix
8 bit signed | ShortInt | ByteInt | VBI_
8 bit unsigned | Byte | UByte | VUB_
16 bit signed | SmallInt |  | VSI_
16 bit unsigned | Word | USmall | VUS_
32 bit signed | LongInt |  | VLI_
32 bit unsigned |  | ULong | VUL_
64 bit signed | Comp / Int64 | QuadInt | VQI_
16/32 bit signed | Integer |  | VI_
16/32 bit unsigned | Cardinal | UInt | VU_

Turbo Pascal only:
The unsigned 32-bit integer type ULong is treated as such only inside OptiVec routines. Otherwise, all ULong variables will be treated as LongInt. This means that the most significant bit will then be interpreted as the sign bit, which may lead to errors, unless proper care is taken to avoid mistakes. Delphi 4+ always treats ULong as such.
QuadInts are always signed. As yet, there is nothing like a "UQuad".

Delphi:
As Delphi supports 64-bit integers, QuadInt is not defined as Comp, but is used as a synonym for the data type Int64.

To have a Boolean data type available which is of the same size as Integer, we define the type IntBool. It is equivalent to WordBool in Pascal, but LongBool in Delphi. You will see the IntBool type as the return value of many mathematical VectorLib functions.

Back to Table of Contents

## 2.2 Complex Numbers:
The Data Types fComplex, dComplex, eComplex, fPolar, dPolar, and ePolar

Complex numbers are treated in C/C++ in quite a confusing way. ANSI C offers only a struct complex, Borland's C/C++ compiler additionally a struct _complexl for complex numbers of double and long double precision, resp. The real and imaginary parts are denoted as x and y.
From early versions on, Borland C++ has also offered a class complex which is of double precision; the real and imaginary parts are accessible via the functions real and imag. There is also a number of mathematical functions available for this class.
Finally, the Standard C++ library, included from Borland C++ 5 and Visual C++ 6 on, offers the classes complex<float>, complex<double>, and complex<long double>, equipped with basic functionality and the same range of mathematical functions as offered by the class complex.
Later versions of Delphi offer a unit Complex which treats complex number as variant types – with all the inefficiencies associated with such an implementation.
Functions in polar coordinates are not available in any of these approaches.

Most compilers and available libraries implement complex functions very inefficiently and inaccurately. (Just writing down the textbook formula for a complex function, like it is usually done, works fine only for a very limited range of arguments!)

Our aims are

- to make the use of complex numbers of all three data types possible in Pascal/Delphi and C as well as in C++,
- to support both cartesian and polar coordinates
- to allow for the most efficient implementation of all complex operations, using assembler code instead of C++ templates,
- and to introduce an easy, compact and consistent nomenclature.

To this end, the complex math library CMATH was created and is included in OptiVec. CMATH is described in greater detail in the file CMATH.HTM. If you use any of the non-vectorized functions contained in CMATH with C/C++, you should include <newcplx.h> (for C++ modules) or <cmath.h> (for plain-C modules) before (!) any of the VectorLib include files.

VectorLib itself contains the necessary initialization functions of complex numbers and all vectorized forms of complex math functions. If you are using only these, you need not explicitly include CMATH. In this case, the following complex data types are defined in <VecLib.h> for C/C++:
typedef struct { float Re, Im; } fComplex;
typedef struct { double Re, Im; } dComplex;
typedef struct { extended Re, Im; } eComplex;
typedef struct { float Mag, Arg; } fPolar;
typedef struct { double Mag, Arg; } dPolar;
typedef struct { extended Mag, Arg; } ePolar;
(the data type extended is used as a synonym for long double, see above.)

The corresponding definitions for Pascal/Delphi are contained in the unit VecLib:
type fComplex = record Re, Im: Float; end;
type dComplex = record Re, Im: Double; end;
type eComplex = record Re, Im: Extended; end;
type fPolar = record Mag, Arg: Float; end;
type dPolar = record Mag, Arg: Double; end;
type ePolar = record Mag, Arg: Extended; end;

If, for example, a complex number z is declared as "fComplex z;", the real and imaginary parts of z are available as z.Re and z.Im, resp. Complex numbers are initialized either by setting the constituent parts separately to the desired value, e.g.,
z.Re = 3.0; z.Im = 5.7;
p.Mag = 4.0; p.Arg = 0.7;
(of course, the assignment operator is := in Pascal/Delphi).
Alternatively, the same initialization can be accomplished by the functions fcplx or fpolr:
C/C++:
z = fcplx( 3.0, 5.7 );
p = fpolr( 4.0, 0.7 );
Pascal/Delphi:
fcplx( z, 3.0, 5.7 );
fpolr( p, 3.0, 5.7 );

For double-precision complex numbers, use dcplx and dpolr, for extended-precision complex numbers, use ecplx and epolr.
Pointers to arrays or vectors of complex numbers are declared using the data types cfVector, cdVector, and ceVector (for cartesian complex) and pfVector, pdVector, and peVector (for polar complex) described below.

Back to Table of Contents

## 2.3 Vectors and Arrays:
The Data Types fVector, dVector, eVector,
cfVector, cdVector, ceVector, pfVector, pdVector, peVector,
iVector, biVector, siVector, liVector, qiVector,
uVector, ubVector, usVector, ulVector, and uiVector

We define, as usual, a "vector" as a one-dimensional array of data containing, at least, one element, with all elements being of the same data type. Using a more mathematical definition, a vector is a rank-one tensor. A two-dimensional array (i.e. a rank-two tensor) is denoted as a "matrix", and higher dimensions are always referred to as "tensors".
In contrast to other approaches, VectorLib does not allow zero-size vectors!

The basis of all VectorLib routines is formed by the various vector data types given below and declared in <VecLib.h> or the unit VecLib. In contrast to the fixed-size static arrays, the VectorLib types use dynamic memory allocation and allow for varying sizes. Because of this increased flexibility, we recommend that you predominantly use the latter. Here they are:

C/C++
typedef | float * | fVector
typedef | double * | dVector
typedef | extended * | eVector
typedef | fComplex * | cfVector
typedef | dComplex * | cdVector
typedef | eComplex * | ceVector
typedef | fPolar * | pfVector
typedef | dPolar * | pdVector
typedef | ePolar * | peVector
typedef | int * | iVector
typedef | byte * | biVector
typedef | short * | siVector
typedef | long * | liVector
typedef | quad * | qiVector
typedef | unsigned * | uVector
typedef | unsigned byte * | ubVector
typedef | unsigned short * | usVector
typedef | unsigned long * | ulVector
typedef | ui * | uiVector
|  | Pascal/Delphi
type | fVector | = ^Float;
type | dVector | = ^Double;
type | eVector | = ^Extended;
type | cfVector | = ^fComplex;
type | cdVector | = ^dComplex;
type | ceVector | = ^eComplex;
type | pfVector | = ^fPolar;
type | pdVector | = ^dPolar;
type | peVector | = ^ePolar
type | iVector | = ^Integer;
type | biVector | = ^ByteInt;
type | siVector | = ^SmallInt;
type | liVector | = ^LongInt;
type | qiVector | = ^QuadInt;
type | uVector | = ^UInt;
type | ubVector | = ^UByte;
type | usVector | = ^USmall;
type | ulVector | = ^ULong;

Internally, a data type like fVector means "pointer to float", but you may think of a variable declared as fVector rather in terms of a "vector of floats".

| Note: in connection with Windows programs, often the letter "l" or "L" is used to denote "long int" variables. In order to prevent confusion, however, the data type "long int" is signalled by "li" or "LI", and the data type "unsigned long" is signalled by "ul" or "UL". Conflicts with prefixes for "long double" vectors are avoided by deriving these from the alias name "extended" and using "e", "ce", "E", and "CE", as described above and in the following.

C/C++ specific:
Vector elements can be accessed either with the [] operator, like VA[375] = 1.234;
or by the type-specific functions VF_element (returns the value of the desired vector element, but cannot be used to overwrite the element) and VF_Pelement (returns the pointer to a vector element). The latter function may be used to set vector values, e.g.
*VF_Pelement( X, 3 ) = 5.7;
Especially for some older Borland C versions (which have a bug in the pointer-arithmetics), VF_Pelement has to be used instead of the syntax X+n.
In your programs, you may mix these vector types with the static arrays of classic C style.
For example:
float a[100]; /* classic static array */
fVector b=VF_vector(100); /* VectorLib vector */
VF_equ1( a, 100 ); /* set the first 100 elements of a equal to 1.0 */
VF_equC( b, 100, 3.7 ); /* set the first 100 elements of b equal to 3.7 */

Pascal/Delphi specific:
The elements of OptiVec vectors cannot be accessed with the [] operator here. Instead, the the type-specific functions VF_element (returns the value of the desired vector element, but cannot be used to overwrite the element) and VF_Pelement (returns the pointer to a vector element) have to be used. The latter function allows to set vector values, e.g.
VF_Pelement( X, 3 )^ := 5.7;
As in C/C++, you may mix the OptiVec vector types with the static arrays of classic Pascal style. Static arrays have to be passed to OptiVec functions with the "address of" operator. Here, the above example reads:
a: array[0..99] of Single; (* classic static array *)
b: fVector;(* VectorLib vector *)
b := VF_vector(100);
VF_equ1( @a, 100 ); (* set first 100 elements of a = 1.0 *)
VF_equC( b, 100, 3.7 ); (* set first 100 elements of b = 3.7 *)
Since Version 4, Delphi has also offered dynamically-allocated arrays, which may also be used as arguments for OptiVec functions. The following table compares the pointer-based vectors of VectorLib with the array types of Pascal/Delphi:

| OptiVec vectors | Pascal/Delphi static/dynamic arrays
alignment of first element | on 32-byte boundary for optimum cache-line matching | 2 or 4-byte boundary (may cause line-break penalty for double, QuadInt)
alignment of following elements | packed (i.e., no dummy bytes between elements, even for 10- and 20-bit types | arrays must be declared as "packed" for Delphi to be compatible with OptiVec
index range checking | none | automatic with built-in size information
dynamic allocation | function VF_vector, VF_vector0 | procedure SetLength (Delphi only)
initialization with 0 | optional by calling VF_vector0 | always (Delphi only)
de-allocation | function V_free, V_freeAll | procedure Finalize (Delphi only)
reading single elements | function VF_element: a := VF_element(X,5); Delphi only: typecast into array also possible: a := fArray(X)[5]; | index in brackets: a := X[5];
setting single elements | function VF_Pelement: VF_Pelement(X,5)^ := a; Delphi only: typecast into array also possible: fArray(X)[5] := a; | index in brackets: X[5] := a;
passing to OptiVec function | directly: VF_equ1( X, sz ); | address-of operator: VF_equ1( @X, sz );
passing sub-vector to OptiVec function | function VF_Pelement: VF_equC( VF_Pelement(X,10), sz-10, 3.7); | address-of operator: VF_equC( @X[10], sz-10, 3.7 );

Summarizing the properties of OptiVec vectors and of Pascal/Delphi arrays, the latter are somewhat more convenient and, due to the index range checking, safer, whereas the pointer-based OptiVec vectors are processed faster (due to the better alignment and to the absence of checking routines).

Back to Table of Contents

## 2.4 Real-number Functions:
The Prefixes VF_, VD_, and VE_

OptiVec supports the three floating-point data types that are used by the coprocessors of the 80x87 family and the FPU units integrated into the 486DX and Pentium processors and their successors: float, double, and extended (i.e., long double). BCD numbers are not supported.

Any of the algebraic and mathematical functions included in this library exists in one variant for each floating-point format. The data type of all floating-point vector elements, parameters, and of the return value is always the same within one function. The data type is signalled by the second letter of the prefix: VF_ denotes the variant of a function that uses exclusively the data type float (Pascal: Single), VD_ stands for the data type double, and VE_ for the data type extended, i.e., long double. (The first letter, "V", stands for "Vector function", of course.) VF_ functions thus work on arrays declared as fVector, use parameters of the type float, and, if there is any floating-point return value, this will also be of the type float. Except for a very few cases, there are no mixed-type functions (that would, e.g., work on vectors of type fVector, use parameters of type double and return a value of type long double).

One partial exception from this rule comes from the fact that floating-point return values of OptiVec functions are returned in extended precision on the number stack. Therefore, you may assign the return value of a function to a variable of another data type. For example, the product of all elements of a vector may easily overflow, and it is a good idea to define eProd as an extended (i.e., as a long double), before writing the line
eProd = VF_prod( X, size );

Borland C++ only:
To use this possibility, you must switch the option "Fast floating point" on (in the IDE in the menu "Options/Compiler/Advanced Code Generation", or the command-line compiler option "-ff"),

For the description of the functions in the Alphabetical Reference, generally only the VF_ version is described and its syntax explicitly given. The versions for the data types double and long double are exactly analogous to the VF_ variant. You have only to replace the prefix VF_ by VD_ (or VE_) and to use "dVector" and "double" (or "eVector" and "extended", resp.) wherever you find "fVector" and "float" in the VF_ version.

Back to Table of Contents

## 2.5 Complex-number Functions:
The Prefixes VCF_, VCD_, VCE_, VPF_, VPD_, and VPE_

Any prefix with its second letter being "C" denotes a function of cartesian complex numbers. If the second letter is "P", the function is for complex numbers in polar coordinates. By analogy with the nomenclature used for real-number functions, the prefix VCF_ signals the exclusive use of single-precision vectors, parameters and return values (fComplex, cfVector and float). Similarly, VCD_ is used for double-precision calculations, and VCE_ for extended precision. Wherever "fComplex", "cfVector", and "float" appear in the description of a function in the VCF_ version, the VCD_ and VCE_ versions are obtained by substituting with "dComplex", "cdVector" and "double" or "eComplex", "ceVector", and "extended" (or "long double"), respectively. Likewise, the VPF_, VPD_, and VPE_ functions work on vector elements of the types fPolar, dPolar, and ePolar, resp.

Return values of the complex data types are not possible in Pascal/Delphi. Therefore, the syntax of those functions returning a complex number is different in C/C++ and Pascal/Delphi.

In contrast to the carelessness with which complex mathematical functions are often treated (see above), the complex functions of OptiVec are designed in such a way as to achieve full accuracy over the complete range of input/output values possible with the respective data type.

In order to perform non-vectorized complex operations with the same level of speed and reliability as the vectorized ones, use CMATH. See CMATH.HTM for details.

Back to Table of Contents

## 2.6 Functions of the Integer Data Types:
The Prefixes VI_, VBI_, VSI_, VLI_, VQI_,
VU_, VUB_, VUS_, VUL_, and VUI_

The nomenclature for the integer data types is designed in a similar way as for the floating-point data types: VI_ indicates the use of the data type int, VBI_ stands for byte-sized int, VSI_ for short int, VLI_ for long int and VQI_ for quad integers. VU_ denotes operations with unsigned integers, VUB_ with unsigned byte, VUS_ with unsigned short and VUL_ is the prefix for functions of unsigned long arguments.
C/C++ only: For operations on index-arrays, functions with the prefix VUI_ allow to perform calculations using arguments of the data type ui defined above. The VUI_ versions are always defined as macros, and the compiler automatically substitutes either the VU_ or the VUL_ version, whichever is appropriate for the memory model actually used. In Pascal/Delphi, no VUI_ functions are defined.

Don't be afraid of so many data types. It is one of the advantages of modern computer languages to have them, and it is one of the disadvantages, at the same time, that a programming style is supported which mixes all the data types until it is no longer clear "who is who". In all normal cases, the VI_, VLI_, and VU_ functions should be sufficient; but keep in mind that there are more available in case you need them.

If present, the vectorized integer functions are always described together with their floating-point analogues. To obtain, for example, the VI_ version, vectors of type iVector have to be substituted for those of type fVector which are demanded by the VF_ version. In the same way, the other versions are obtained by changing "float" and "fVector" into the desired data type.

Back to Table of Contents

## 2.7 Common Functions of Several Data Types: The Prefix V_

Several functions exist that are either used independently of any data type or that are used to interconvert the data types. Functions like V_initPlot and V_free belong to the first case (you have to initialize the plotting routines regardless of the data type of the vectors you are going to plot, and the initialization is not specific for any data type).
A function like V_ULtoD belongs to the second case; here, a ulVector (a vector whose elements are of the data type unsigned long) is transformed into a dVector (a vector whose elements are doubles).
The type-independent functions are declared in <VecLib.h> and <Vgraph.h> or the units VecLib and Vgraph, respectively. The data-type interconversion functions are declared in the include-files or units belonging to the destination type (i.e. the type into which the numbers are converted).

Back to Table of Contents

#

3. VecObj, theObject-Oriented Interface for VectorLib

VecObj, the object-oriented C++ interface to OptiVec vector functions was written by Brian Dale, Case Western Reserve University. MatObj, the extension for matrices, is now included as well.
Among the advantages VecObj offers are the following:

- automatic allocation and deallocation of memory
- simplified vector handling
- greatly reduced risk of memory leaks
- increased memory access safety
- intuitive overloaded operators
- simpler function calls

There are a few draw-backs, though, which you should be aware of:

- increased compiler load
- larger overhead (as for any encapsulated C++ code!), leading to
- increased code size
- decreased computational efficiency
- vectors can be processed only as a whole, not in parts

VecObj is contained in the include-files <VecObj.h>, <fVecObj.h>, <dVecObj.h> etc., with one include-file for each of the data-types supported in OptiVec.
To get the whole interface (for all data types at once),
#include <OptiVec.h>.
For access to any of the vector graphics functions, always include <OptiVec.h>.

MS Visual C++ and Borland C++ Builder (but not previous Borland C++ versions): Programmers should put the directive
"using namespace OptiVec;"
either in the body of any function that usestVecObj, or in the global declaration part of the program. Placing the directive in the function body is safer, avoiding potential namespace conflicts in other functions.
The vector objects are defined as classes vector<T>, encapsulating the vector address (pointer) and size.
For easier use, these classes got alias names fVecObj, dVecObj, and so on, with the data-type signalled by the first one or two letters of the class name, in the same way as the vector types described above.

All functions defined in VectorLib for a specific vector data-type are contained as member functions in the respective tVecObj class.
The constructors are available in four forms:
vector(); // no memory allocated, size set to 0
vector( ui size ); // vector of size elements allocated
vector( ui size, T fill ); // as before, but initialized with value "fill"
vector( vector<T> init ); // creates a copy of the vector "init"

For all vector classes, the arithmetic operators
+ - * / += -= *= /=
are defined, with the exception of the polar-complex vector classes, where only multiplications and divisions, but no additions or subtractions are supported. These operators are the only cases in which you can directly assign the result of a calculation to a vector object, like
fVecObj Z = X + Y; or
fVecObj Z = X * 3.5;
Note, however, that the C++ class syntax rules do not allow a very efficient implementation of these operators. The arithmetic member functions are much faster. If speed is an issue, use
fVecObj Z.addV( X, Y ); or
fVecObj Z.mulC( X, 3.5 );
instead of the operator syntax.
The operator * refers to element-wise multiplication, not to the scalar product of two vectors.

All other arithmetic and math functions can only be called as member functions of the respective output vector as, for example, Y.exp(X). Although it would certainly be more logical to have these functions defined in such a way that you could write "Y = exp(X)" instead, the member-function syntax was chosen for efficiency considerations: The only way to implement the second variant is to store the result of the exponential function of X first in a temporary vector, which is then copied into Y, thus considerably increasing the work-load and memory demands.

While most VecObj functions are member functions of the output vector, there is a number of functions which do not have an output vector. In these cases, the functions are member functions of an input vector.
Example: s = X.mean();.

If you ever need to process a VecObj vector in a "classic" plain-C VectorLib function (for example, to process only some part of it), you may use the member functions
getSize() to retrieve its size,
getVector() for the pointer (of data type tVector, where "t" stands for the usual type prefix), and
Pelement( n ) for a pointer to the to the n'th element.

The syntax of all VecObj functions is described in FUNCREF.HTM together with the basic VectorLib functions for which tVecObj serves as a wrapper.

Back to Table of Contents

#

4. VectorLib Functions and Routines: A Short Overview

## 4.1 Generation, Initialization and De-Allocation of Vectors

With VectorLib, you may use static arrays (like, for example, float a[100];) as well as dynamically allocated ones (see chapter 2.3). We recommend, however, that you use the more flexible vector types defined by VectorLib, using dynamic allocation.

The following functions manage dynamically allocated vectors:

VF_vector | memory allocation for one vector
VF_vector0 | memory allocation and initialization of all elements with 0
V_free | free one vector
V_nfree | free n vectors (only for C, not for Pascal)
V_freeAll | free all existing vectors

You should always take proper care to de-allocate the memory of vectors which are no longer needed. Internally, the allocated vectors are written into a table to keep track of the allocated memory. If you try to free a vector that has never been or is no longer allocated, you get a warning message, and nothing is freed.
You might wonder why we add still more memory allocation functions to the already rich omnium gatherum of C/C++ and Pascal/Delphi. The reason is that, for every environment and every memory model, the most appropriate memory management functions shall be selected automatically. This means that you, the user, need not deal yourself with the various methods, but can leave this task to OptiVec. Moreover, this makes your programs more easily portable.

#### Performance tips:

- In the 32 bit versions, the vectors allocated by VF_vector etc. are aligned on 64 byte boundaries for optimum cache-line matching (Intel Pentium XX: 32 byte, AMD Athlon: 64 byte). On the other hand, static arrays as well as vectors created by the operator new or by the standard functions, malloc, calloc, GetMem, LocalAlloc, GlobalAlloc etc. are aligned on 4 byte boundaries only. Consequently, for optimum performance, you should use only vectors allocated by VF_vector etc.
- The SIMD commands employed in the P6 version require data to be aligned on 16 byte boundaries. Once more: Use the dynamic vectors of OptiVec to guarantee the necessary alignment. The penalty for not properly aligned data may amount to 25%.
- Instead of many small vectors, consider allocating one large vector. Define the small vectors as parts of the larger one:

  C/C++: X = VF_vector( 3*size); Z = (Y = X+size) + size; | Pascal/Delphi: X := VF_vector( 3*size ); Y := VF_Pelement( X, size ); Z := VF_Pelement( Y, size );

  Be sure size is rounded up so as to make size*sizeof( data type ) a multiple of 32 or 64.
- Avoid too frequent allocation and deallocation. Try to re-use vectors instead.

The following functions are used to initialize or re-initialize vectors that have already been created:

VF_equ0 | set all elements of a vector equal to 0
VF_equ1 | set all elements equal to 1
VF_equm1 | set all elements equal to -1
VF_equC | set all elements equal to a constant C
VF_equV | make one vector a copy of another
VFx_equV | "expanded" version of the equality operation: Yi = a * Xi + b
VF_ramp | "ramp": Xi = a * i + b.
VF_random | high-quality random numbers
VF_noise | white noise
VF_comb | "comb": equals a constant C at equidistant points, elsewhere 0

The following functions generate windows for use in spectral analysis:

VF_Hanning | Hanning window
VF_Parzen | Parzen window
VF_Welch | Welch window

Complex vectors may be initialized by these functions:

VF_ReImtoC | merge two vectors, Re and Im, into one cartesian complex vector
VF_RetoC | overwrite the real part of a cartesian complex vector
VF_ImtoC | overwrite the imaginary part of a cartesian complex vector
VF_PolartoC | construct a cartesian complex vector from polar coordinates, entered as separate vectors Mag and Arg
VF_MagArgtoP | merge two vectors, Mag and Arg into one polar complex vector
VF_MagArgtoPrincipal | merge two vectors, Mag and Arg into one polar complex vector, reducing the Arg range to the principal value, -p < Arg <= +p
VF_MagtoP | overwrite the Mag part of a polar complex vector
VF_ArgtoP | overwrite the Arg part of a polar complex vector
VF_ReImtoP | construct a polar complex vector from cartesian coordinates, entered as separate vectors Re and Im

Back to Table of Contents

## 4.2 Index-oriented Manipulations

VF_rev | reverse the element ordering
VF_reflect | set the upper half of a vector equal to the reversed lower half
VF_rotate | rotate the ordering of the elements
VF_rotate_buf | efficient rotation, employing user-specified buffer memory
VF_insert | insert one element into a vector
VF_delete | delete one element from a vector
VF_sort | fast sorting of the elements (ascending or descending order)
VF_sortind | sorting of an index array associated with a vector
VF_subvector | extract a subvector from a (normally larger) vector, using a constant sampling interval.
VF_indpick | fills a vector with elements "picked" from another vector according to their indices.
VF_indput | distribute the elements of one vector to the sites of another vector specified by their indices.

Operations performed only on a sampled sub-set of elements of a vector are provided by the VF_subvector_... family, where the omission mark stands for a suffix denoting the desired operation:

VF_subvector_equC | Xi*samp = C, i=0,...subsize-1
VF_subvector_addC | Xi*samp += C, i=0,...subsize-1
VF_subvector_subC | Xi*samp -= C, i=0,...subsize-1
VF_subvector_subrC | Xi*samp = C - Xi*samp, i=0,...subsize-1
VF_subvector_mulC | Xi*samp *= C, i=0,...subsize-1
VF_subvector_divC | Xi*samp /= C, i=0,...subsize-1
VF_subvector_divrC | Xi*samp = C / Xi*samp, i=0,...subsize-1
VF_subvector_equV | Yi*samp = Yi, i=0,...subsize-1
VF_subvector_addV | Xi*samp += Yi, i=0,...subsize-1
VF_subvector_subV | Xi*samp -= Yi, i=0,...subsize-1
VF_subvector_subrV | Xi*samp = Yi - Xi*samp, i=0,...subsize-1
VF_subvector_mulV | Xi*samp *= Yi, i=0,...subsize-1
VF_subvector_divV | Xi*samp /= Yi, i=0,...subsize-1
VF_subvector_divrV | Xi*samp = Yi / Xi*samp, i=0,...subsize-1

Searching tables for specific values is accomplished by:

VF_searchC | search for the element of a vector that is closest to a pre-set value C (closest, closest larger-or-equal, or closest smaller-or-equal value, depending on a parameter "mode")
VF_searchV | the same, but for a whole array of pre-set values

Interpolations are performed by:

VF_polyinterpol | polynomial interpolation
VF_ratinterpol | rational interpolation
VF_natCubSplineInterpol | natural cubic spline interpolation
VF_splineinterpol | general cubic spline interpolation

Back to Table of Contents

## 4.3 Data-Type Interconversions

The first thing that has to be said about the floating-point data-type interconversions is: do not use them too extensively. Decide which accuracy is appropriate for your application, and then use consistently either the VF_, or the VD_, or the VE_ version of the functions you need. Nevertheless, every data type can be converted into every other, in case it is necessary. Only a few examples are given; the rest should be obvious:

V_FtoD | float to double
V_CDtoCF | complex<double> to complex<float> (with overflow protection)
V_PFtoPE | polar<float> to polar<extended>
VF_PtoC | polar<float> to complex<float>
V_ItoLI | int to long int
V_ULtoUS | unsigned long to unsigned short
V_ItoU | signed int to unsigned int. Interconversions between signed and unsigned types can only be performed on the same level of accuracy. Functions like "V_UStoLI" do not exist.
V_ItoF | int to float

The conversion of floating-point numbers into integers is performed by the following functions, differing in the way a possible fractional part is treated:

VF_roundtoI | round to the closest integer
VF_choptoI | round by neglecting ("chopping off") the fractional part
VF_trunctoI | the same as VF_choptoI
VF_ceiltoI | round to the next greater-or-equal integer
VF_floortoI | round to the next smaller-or-equal integer

These operations are treated as mathematical functions and are further described in chapter 4.6.1.

Back to Table of Contents

## 4.4 More about Integer Arithmetics

Although the rules of integer arithmetics are quite straightforward, it appears appropriate to recall that all integer operations are implicitly performed modulo 2n, where n is the number of bits the numbers are represented with. This means that any result, falling outside the range of the respective data type, is made to fall inside the range by loosing the highest bits. The effect is the same as if as many times 2n had been added to (or subtracted from) the "correct" result as necessary to reach the legal range.
For example, in the data type short / SmallInt, the result of the multiplication 5 * 20000 is -31072. The reason for this seemingly wrong negative result is that the "correct" result, 100000, falls outside the range of short numbers which is -32768 <= x <= +32767. short / SmallInt is a 16-bit type, so n = 16, and 2n = 65536. In order to make the result fall into the specified range, the processor "subtracts" 2 * 65536 = 131072 from 100000, yielding -31072.
Note that overflowing intermediate results cannot be "cured" by any following operation. For example, (5 * 20000) / 4 is not (as one might hope) 25000, but rather -7768.
The 64-bit data type quad / QuadInt does not employ this implicit modulo-2n arithmetics. Overflow conditions lead to undefined results.

Back to Table of Contents

## 4.5 Basic Functions of Complex Vectors

The following functions are available for the basic treatment of cartesian complex vectors:

VF_ReImtoC | form a cartesian complex vector out of its real and imaginary parts
VF_RetoC | overwrite the real part
VF_ImtoC | overwrite the imaginary part
VF_CtoReIm | extract the real and imaginary parts
VF_CtoRe | extract the real part
VF_CtoIm | extract the imaginary part
VF_PolartoC | form a cartesian complex vector out of polar coordinates, entered as separate vectors Mag and Arg
VF_CtoPolar | transform cartesian complex into polar coordinates, returned in the separate vectors Mag and Arg
VF_CtoAbs | absolute value (magnitude of the pointer in the complex plane)
VF_CtoArg | argument (angle of the pointer in the complex plane)
VF_CtoNorm | norm (here defined as the square of the absolute value)
VCF_normtoC | norm, stored as a cartesian complex vector (with all imaginary parts equal to 0)

The corresponding functions for polar coordinates are:

VF_MagArgtoP | merge two vectors, Mag and Arg into one polar complex vector
VF_MagArgtoPrincipal | merge two vectors, Mag and Arg into one polar complex vector, reducing the Arg range to the principal value, -p < Arg <= +p
VF_MagtoP | overwrite the Mag part of a polar complex vector
VF_ArgtoP | overwrite the Arg part of a polar complex vector
VF_PtoMagArg | extract the Mag and Arg parts
VF_PtoMag | extract the Mag part
VF_PtoArg | extract the Arg part
VF_PtoNorm | norm (here defined as the square of the magnitude)
VF_ReImtoP | construct a polar complex vector from cartesian coordinates, entered as separate vectors Re and Im
VF_PtoReIm | transform a polar complex vector into two real vectors, representing the corresponding cartesian coordinates Re and Im
VF_PtoRe | calculate the real part of the polar complex input numbers
VF_PtoIm | calculate the imaginary part of the polar complex input numbers
VPF_principal | calculate the principal value. You might recall that each complex number has an infinite number of representations in polar coordinates, with the angles differing by an integer multiple of 2 p. The representation with -p < Arg <= +p is called the principal value.

Back to Table of Contents

## 4.6 Mathematical Functions

Lacking a more well-founded definition, we denote as "mathematical" all those functions which calculate each single element of a vector from the corresponding element of another vector by a more or less simple mathematical formula:
Yi = f( Xi ).
Except for the "basic arithmetics" functions, they are defined only for the floating-point data types. Most of these mathematical functions are vectorized versions of scalar ANSI C or Pascal functions or derived from them. In C/C++, errors are handled by _matherr and _matherrl. In Pascal/Delphi, OptiVec allows the user to control error handling by means of the function V_setFPErrorHandling.

In addition to this error handling "by element", the return values of the VectorLib math functions show if all elements have been processed successfully. In C/C++, the return value is of the data-type int, in Pascal/Delphi, it is IntBool. (We do not yet use the newly introduced data type bool for this return value in C/C++, in order to make VectorLib compatible also with older versions of C compilers.) If a math function worked error-free, the return value is FALSE (0), otherwise it is TRUE (any non-zero number).

Back to Table of Contents

## 4.6.1 Rounding

Some of the functions converting floating-point into integer vectors have already been noted above. The result of these rounding operations may either be left in the original floating-point format, or it may be converted into one of the integer types. The following functions store the result in the original floating-point format:

VF_round | round to the closest integer
VF_chop | round by neglecting ("chopping off") the fractional part
VF_trunc | the same as VF_chop
VF_ceil | round to the next greater-or-equal integer
VF_floor | round to the next smaller-or-equal integer

The following functions store the result as integers (type int / Integer):

VF_roundtoI | round to the closest integer
VF_choptoI | round by neglecting ("chopping off") the fractional part
VF_trunctoI | the same as VF_choptoI
VF_ceiltoI | round to the next greater-or-equal integer
VF_floortoI | round to the next smaller-or-equal integer

The target type may also be any of the other integer data types. A few examples should suffice:

VF_choptoSI | neglect the fractional part and store as short int / SmallInt
VF_ceiltoLI | round up and store as long int / LongInt
VF_floortoQI | round downwards and store as quadruple integer, quad / QuadInt
VF_roundtoU | round and store as unsigned / UInt
VF_ceiltoUS | round up and store as unsigned short / USmall
VD_choptoUL | neglect the fractional part and store as unsigned long / ULong

Back to Table of Contents

## 4.6.2 Comparisons

Functions performing comparisons are generally named VF_cmp... (where further letters and/or numbers specify the type of comparison desired). Every element of a vector can be compared either to 0, or to a constant C, or to the corresponding element of another vector. There are two possibilities: either the comparison is performed with the three possible answers "greater than", "equal to" or "less than". In this case, the results are stored as floating-point numbers (0.0, 1.0, or -1.0). Examples are:

VF_cmp0 | compare to 0
VD_cmpC | compare to a constant C
VE_cmpV | compare corresponding vector elements

The other possibility is to test if one of the following conditions is fulfilled: "greater than", "greater than or equal to", "equal to", "not equal to", "less than", or "less than or equal to". Here, the answers will be "TRUE" or "FALSE" (1.0 or 0.0). Examples are

VF_cmp_eq0 | check if equal to 0
VD_cmp_gtC | check if greter than a constant C
VE_cmp_leV | check if less than or equal to corresponding vector element

Alternatively, the indices of the elements for which the answer was "TRUE" may be stored in an index-array, as in:

VF_cmp_neCind | store indices of elements not equal to a constant C
VD_cmp_lt0ind | store indices of elements less than 0
VE_cmp_geVind | store indices of elements greater than or equal to corresponding vector elements

While the basic comparison functions check against one boundary, there is a number of functions checking if a vector elements falls into a certain range:

VF_cmp_inclrange0C | check if 0 <= x <= C (C positive) or 0 >= x >= C (C negative)
VF_cmp_exclrange0C | check if 0 < x < C (C positive) or 0 > x > C (C negative)
VF_cmp_inclrangeCC | check if CLo <= x <= CHi
VF_cmp_exclrangeCC | check if CLo < x < CHi
VF_cmp_inclrange0Cind | store indices of elements 0 <= x <= C (C positive) or 0 >= x > C (C negative)
VF_cmp_exclrange0Cind | store indices of elements 0 < x < C (C positive) or 0 > x > C (C negative)
VF_cmp_inclrangeCCind | store indices of elements CLo <= x <= CHi
VF_cmp_exclrangeCCind | store indices of elements CLo < x < CHi

The following functions test if one or more values can be found in a vector:

VF_iselementC | returns TRUE, if C is an element of a vector
VF_iselementV | checks for each element of a vector if it is contained in a table

Back to Table of Contents

## 4.6.3 Direct Bit-Manipulation

For the integer data types, a number of bit-wise operations is available, which can be used, e.g., for fast multiplication and divisions by integer powers of 2.

VI_shl | shift the bits to the left
VI_shr | shift the bits to the right
VI_or | apply a bit mask in an OR operation
VI_xor | apply a bit mask in an XOR operation
VI_not | invert all bits

Back to Table of Contents

## 4.6.4 Basic Arithmetics, Accumulations

As before, only the VF_ function is explicitly named, but the VD_ and VE_ functions exist as well; if it makes sense, the same is true for the complex and for the integer-type versions:

VF_neg | Yi = - Xi
VF_abs | Yi = | Xi |
VCF_conj | Yi.Re = Xi.Re; Yi.Im = -(Xi.Re)
VF_inv | Yi = 1.0 / Xi

VF_equC | Xi = C
VF_addC | Yi = Xi + C
VF_subC | Yi = Xi - C
VF_subrC | Yi = C - Xi
VF_mulC | Yi = Xi * C
VF_divC | Yi = Xi / C
VF_divrC | Yi = C / Xi
VF_modC | Yi = Xi mod C
VF_equV | Yi = Xi
VF_addV | Zi = Xi + Yi
VF_subV | Zi = Xi - Yi
VF_subrV | Zi = Yi - Xi
VF_mulV | Zi = Xi * Yi
VF_divV | Zi = Xi / Yi
VF_divrV | Zi = Yi / Xi
VF_modV | Zi = Xi mod Yi
VF_add2V | Zi = Xi + Y1i + Y2i
VF_sub2V | Zi = Xi - Y1i - Y2i

Besides these basic operations, several frequently-used combinations of addition and division have been included, not to forget the Pythagoras formula:

VF_hypC | Yi = Xi / (Xi + C)
VF_redC | Yi = (Xi * C) / (Xi + C)
VF_visC | Yi = (Xi - C) / (Xi + C)
VF_hypotC | Yi = sqrt( Xi² + C² )
VF_hypV | Zi = Xi / (Xi + Yi)
VF_redV | Zi = (Xi * Yi) / (Xi + Yi)
VF_visV | Zi = (Xi - Yi) / (Xi + Yi)
VF_hypotV | Zi = sqrt( Xi² + Yi²)

All functions in the right column of the above two sections also exist in an expanded form (with the prefix VFx_...) in which the function is not evaluated for Xi itself, but for the expression
(a * Xi + b), e.g.

VFx_addV | Zi = (a * Xi + b) + Yi
VFx_divrV | Zi = Yi / (a * Xi + b)

The simple algebraic functions exist also in yet another special form, with the result being scaled by some arbitrary factor. This scaled form gets the prefix VFs_:

VFs_addV | Zi = C * (Xi + Yi)
VFs_subV | Zi = C * (Xi - Yi)
VFs_mulV | Zi = C * (Xi * Yi)
VFs_divV | Zi = C * (Xi / Yi)

Other simple operations include:

VF_maxC | set Yi equal to Xi or C, whichever is greater
VF_minC | choose the smaller of Xi and C
VF_maxV | set Zi equal to Xi or Yi, whichever is greater
VF_minV | set Zi equal to Xi or Yi, whichever is smaller
VF_limit | limit the range of values
VF_flush0 | set all values to zero which are below a preset threshold
VF_intfrac | split into integer and fractional parts
VF_mantexp | split into mantissa and exponent

While, in general, all OptiVec functions are for input and output vectors of the same type, the arithmetic functions exist also for mixed-type operations between the floating-point and the integer types. The result is always stored in the floating-point type. Examples are:

VF_addVI | fVector Z = fVector X + iVector Y
VD_mulVUL | dVector Z = dVector X * ulVector Y
VE_divrVBI | eVector Z = biVector Y / eVector X

Similarly, there exists a family of functions for the accumulation of data in either the same type or in higher-precision data types. Some examples are:

VF_accV | fVector Y += fVector X
VD_accVF | dVector Y += fVector X
VF_accVI | fVector Y += iVector X
VQI_accVLI | qiVector Y += liVector X

Additionally, within the floating-point data-types, you can accumulate two vectors at once:

VF_acc2V | fVector Y += fVector X1 + fVector X2
VD_acc2VF | dVector Y += fVector X1 + fVector X2

Back to Table of Contents

## 4.6.5 Geometrical Vector Arithmetics

In its geometrical interpretation, a vector is a pointer, with its elements representing the coordinates of a point in n-dimensional space. There are a few functions for geometrical vector arithmetics, namely

VF_scalprod | scalar product of two vectors
VF_xprod | cross-product (or vector product) of two vectors
VF_Euclid | Euclidean norm

If, on the other hand, two real input vectors X and Y, or one complex input vector XY, define the coordinates of several points in a planar coordinate system, there is a function to rotate these coordinates:

VF_rotateCoordinates | counter-clockwise rotation of the input coordinates specified by the vectors X and Y; the result is returned in the vectors Xrot and Yrot.
VCF_rotateCoordinates | counter-clockwise rotation of the input coordinates specified by the cartesian complex vector XY; the result is returned in the vector XYrot.

Back to Table of Contents

## 4.6.6 Powers

The following functions raise arbitrary numbers to specified powers. While the "normal versions" perform full error handling, the extra-fast "unprotected versions" can be employed in situations where you are absolutely sure that all input elements yield valid results. Due to the much more efficient vectorization permitted by the absence of error checks, the unprotected functions are up to 1.8 times as fast as the normal, protected versions. (This is true from the Pentium CPU on; on older computers, almost nothing is gained.) Be, however, aware of the price you have to pay for this increase in speed: in case of an overflow error, the program will crash without any warning.

normal version | unprotected version | operation
VF_square | VFu_square | square
VF_cubic | VFu_cubic | cubic
VF_quartic | VFu_quartic | quartic (fourth power)
VF_ipow | VFu_ipow | arbitrary integer powers
VF_pow | n.a. | fractional powers
VF_powexp | n.a. | fractional powers, multiplied by exponential function: xrexp(x)
VF_poly | VFu_poly | polynomial

The following group of functions is used to raise specified numbers to arbitrary powers:

VF_pow10 | fractional powers of 10
VF_ipow10 | integer powers of 10 (stored as floating-point numbers)
VF_pow2 | fractional powers of 2
VF_ipow2 | integer powers of 2 (stored as floating-point numbers)
VF_exp | exponential function
VF_exp10 | exponential function to the basis 10 (identical to VF_pow10)
VF_exp2 | exponential function to the basis 2 (identical to VF_pow2)
VF_expArbBase | exponential function of an arbitrary base
VF_sqrt | square-root (which corresponds to a power of 0.5)

All of these functions exist also in the expanded "VFx_" form, like
VFx_square: Yi = (a * Xi + b)²
The expanded form of the unprotected functions has the prefix VFux_.

The complex-number equivalents are available as well, both for cartesian and polar coordinates. Additionally, two special cases are covered:

VCF_powReExpo | real, fractional powers of complex numbers
VCF_exptoP | takes a cartesian input vector, returning its exponential function in polar coordinates.

Back to Table of Contents

## 4.6.7 Exponentials and Hyperbolic Functions

A variety of functions are derived from the exponential function VF_exp (which itself has already been mentioned in the last section).

VF_exp | exponential function
VF_expc | complementary exponential function Yi = 1 - exp[Xi]
VF_expmx2 | exponential function of the negative square of the argument, Yi = exp( -Xi² ). This is a bell-shaped function.
VF_Gauss | Gaussian distribution function
VF_erf | Error function (Integral over the Gaussian distribution)
VF_erfc | complementary error function, 1 - erf( Xi )
VF_powexp | n.a. | fractional powers, multiplied by exponential function, Xirexp(Xi)

The vectorized hyperbolic functions are available as:

VF_sinh | hyperbolic sine
VF_cosh | hyperbolic cosine
VF_tanh | hyperbolic tangent
VF_coth | hyperbolic cotangent
VF_sech | hyperbolic secant
VF_cosech | hyperbolic cosecant
VF_sech2 | square of the hyperbolic secant

Back to Table of Contents

## 4.6.8 Logarithms

VF_log10 | decadic logarithm (to the basis 10)
VF_log | natural logarithm (to the basis e)
VF_ln | synonym for VF_log
VF_log2 | binary logarithm (to the basis 2)

Again, the cartesian-complex equivalents exist as well. The polar-complex versions, however, are special in that their output is always in cartesian coordinates:

VPF_log10toC | decadic logarithm (to the basis 10)
VPF_logtoC | natural logarithm (to the basis e)
VPF_lntoC | synonym for VPF_logtoC
VPF_log2toC | binary logarithm (to the basis 2)

As a special form of the decadic logarithm, the Optical Density is made available by a family of functions of which some examples are contained in the following table:

VF_OD | OD = log10( X0/X ) for fVector as input and as output
VF_ODwDark | OD = log10( (X0-X0Dark) / (X-XDark) ) for fVector as input and as output
VUS_ODtoF | OD, calculated in float precision for usVector input
VUL_ODtoD | OD, calculated in double precision for ulVector input
VQI_ODtoEwDark | OD with dark-current correction, calculated in extended precision for qiVector input

Back to Table of Contents

## 4.6.9 Trigonometric Functions

Some of the basic trigonometric functions are available in two variants. The first variant follows the usual rules of error handling for math functions, whereas the second is for situations where you know beforehand that all input arguments will be in the range -2p <= Xi <= +2p. If you choose to employ these extra-fast reduced-range functions, you really have to be absolutely sure about your input vectors, as these functions will crash without warning in the case of any input number outside the range specified above. The reduced-range functions are available only for the sine and cosine, as all other trigonometric functions need error checking and handling anyway, even in this range.

VF_sin | sine
VFr_sin | extra-fast "reduced-range" sine function for -2p <= Xi <= +2p
VF_cos | cosine
VFr_cos | cosine for -2p <= Xi <= +2p
VF_sincos | sine and cosine at once
VFr_sincos | sine and cosine for -2p <= Xi <= +2p
VF_tan | tangent
VF_cot | cotangent
VF_sec | secant
VF_cosec | cosecant

The following functions yield the squares of the trigonometric functions in a more efficient way than by first calculating the basic functions and squaring afterwards:

VF_sin2 | sine²
VFr_sin2 | sine² for -2p <= Xi <= +2p
VF_cos2 | cosine²
VFr_cos2 | cosine² for -2p <= Xi <= +2p
VF_sincos2 | sine² and cosine² at once
VFr_sincos2 | sine² and cosine² for -2p <= Xi <= +2p
VF_tan2 | tangent²
VF_cot2 | cotangent²
VF_sec2 | secant²
VF_cosec2 | cosecant²

A very efficient way to calculate the trigonometric functions for arguments representable as rational multiples of p (PI) is supplied by the trigonometric functions with the suffix "rpi" (meaning "rational multiple of p"). Here, r = p / q, where q is constant and p is given by the input vector elements:

VF_sinrpi | sine of p/q * p
VF_cosrpi | cosine of p/q * p
VF_sincosrpi | sine and cosine of p/q * p at once
VF_tanrpi | tangent of p/q * p
VF_cotrpi | cotangent of p/q * p
VF_secrpi | secant of p/q * p
VF_cosecrpi | cosecant of p/q * p

Even more efficient versions use tables to obtain frequently-used values; these versions are denoted by the suffixes "rpi2" (multiples of p divided by an integer power of 2) and "rpi3" (multiples of p over an integer multiple of 3). Examples are:

VF_sinrpi2 | sine of p / 2n * p
VF_tanrpi3 | tangent of p / (3*n) * p

Two more special trigonometric functions are:

VF_sinc | sinc function, Yi = sin( Xi ) / Xi
VF_Kepler | Kepler function, calculating the time-dependent angular position of a planet or comet

Vectorized inverse trigonometric functions are available as

VF_asin | arc sin
VF_acos | arc cos
VF_atan | arc tan
VF_atan2 | arc tan of ratios, Zi = atan( Yi / Xi )

Back to Table of Contents

## 4.7 Analysis

There is a number of functions probing the analytical properties of data arrays:

VF_derivV | derivative of a Y-array with respect to an X-array
VF_derivC | the same for constant intervals between the X-values
VF_integralV | value of the integral of a Y-array over an X-array
VF_runintegralV | point-by-point ("running") integral
VF_integralC | integral over an equally spaced X-axis
VF_runintegralC | point-by-point integral over an equally spaced X-axis
VF_ismonoton | test if an array is monotonously rising or falling
VF_iselementC | test, if a given value occurs within a vector
VF_searchC | search an ordered table for the entry whose value comes closest to a preset value C
VF_localmaxima | detect local maxima (points whose right and left neighbours are smaller)
VF_localminima | detect local minima (points whose right and left neighbours are larger)
VF_max | detect global maximum
VF_min | detect global minimum
VF_maxind | global maximum and its index
VF_minind | global minimum and its index
VF_absmax | global maximum absolute value
VF_absmin | global minimum absolute value
VF_absmaxind | global maximum absolute value and its index
VF_absminind | global minimum absolute value and its index
VF_maxexp | global maximum exponent
VF_minexp | global minimum exponent
VF_runmax | "running" maximum
VF_runmin | "running" minimum

The complex equivalents of the last group of functions are:

VCF_maxReIm | maximum real and imaginary parts separately
VCF_minReIm | minimum real and imaginary parts separately
VCF_absmaxReIm | maximum absolute real and imaginary values separately
VCF_absminReIm | minimum absolute real and imaginary values separately
VCF_absmax | largest magnitude (absolute value; this is a real number)
VCF_absmin | smallest magnitude
VCF_cabsmax | complex number of largest magnitude
VCF_cabsmin | complex number of smallest magnitude
VCF_sabsmax | complex number for which the sum |Re| + |Im| is largest
VCF_sabsmin | smallest complex number in terms of the sum |Re| + |Im|
VCF_absmaxind | largest magnitude (absolute value) and its index
VCF_absminind | smallest magnitude and its index

Sums, products, etc. are available by functions grouped as statistical building blocks and summarized in chapter 4.9.

To determine the center of gravity of a vector, you have the choice between the following two functions:

VF_centerOfGravityInd | center of gravity, returned as an interpolated element index
VF_centerOfGravityV | center of gravity of a Y vector with explicitly given X axis

Back to Table of Contents

## 4.8 Signal Processing:
Fourier Transforms and Related Topics

The following list of functions is available for signal processing applications:

VF_FFTtoC | forward Fast Fourier Transform (FFT) of a real vector; the result is a cartesian complex vector
VF_FFT | forward and backward FFT of a real vector; the result of the forward FFT is packed into a real vector of the same size as the input vector
VCF_FFT | forward and backward FFT of a complex vector
VF_convolve | convolution with a given response function
VF_deconvolve | deconvolution, assuming a given response function
VF_filter | spectral filtering
VF_spectrum | spectral analysis
VF_autocorr | autocorrelation function of a data array
VF_xcorr | cross-correlation function of two arrays
VF_setRspEdit | set editing threshold for the filter in convolutions and deconvolutions (decides over the treatment of "lost" frequencies)
VF_getRspEdit | retrieve the current editing threshold

The FFT algorithm chosen for this PC implementation is a radix-2 Cooley-Tukey routine. Only for this radix-2 algorithm, the restricted number of eight coprocessor registers still allows to hold all intermediate results of the inner transform loop in coprocessor registers. Although featuring savings in the number of multiplications, radix-4 and radix-8 routines are rendered less efficient than the routine chosen by the need of storing intermediate results in memory.
There are three different versions of all FFT-based functions. Depending on the memory model, either of them is automatically chosen. You may, however, explicitly specify the one you wish to employ.

- The first version uses the already-mentioned table of sine values (see chapter 4.6.9. and the function VF_sinrpi2) as a look-up table for the Fourier coefficients needed. This table needs up to 10 kBytes in the 16-bit models and 40 kBytes in the 32-bit models. By default, this variant is used in the memory models COMPACT and LARGE. To explicitly specify it in the other memory models, please use the prefixes VFl_, VDl_, VEl_ (with the letter "l" for the "larger" amount of memory needed).
- The second variant, which is automatically chosen in the memory models SMALL, MEDIUM, and HUGE, employs trigonometric recursions to obtain the sine and cosine values with still satisfactory speed, though this procedure is not as fast as simply reading them from a table. You may explicitly specifiy this variant by adding the letter "s" (for the "smaller" amount of memory needed) in the function prefix. Examples are VFs_FFT, VDs_convolve, VEs_spectrum.
  If you decide to use this variant in order to economize memory in the models COMPACT and LARGE, use the prefix VFs_ for all(!) routines employing FFT. Otherwise, you might happen to load not only a second FFT routine, but also its whole look-up table into your already overcrowded memory.
- The new "parallel-enhanced" variant with the prefix VFp_, finally, uses additional buffer memory to temporarily re-order the input vector in such a way as to optimize both cache utilization and (in the Pentium III libraries) the use of SIMD commands. The minimum input vector size is 16. Below that, the VFp_ functions automatically pass the task on to the respective VFs_ function. For medium-size vectors, the VFp_ version is about 20 percent faster than the other two variants. For large vectors (above the L2 cache size), the speed gain increases to a factor of about 1.8. The analogous 2D-FFT for matrices – where the amount of attainable parallelism is obviously even larger – a speed increase by about a factor of 2.5 is obtained (measured for the Pentium III libraries; with the 486 libraries, the gain is 10% for medium-size vectors, up to a factor of 1.6 for large vectors, and about 30% for matrices). For extremely large vector sizes near or above the size of available RAM, however, the need of additional (virtual) memory slows execution down to a speed again comparable to the VFs_ version. The VFp_ version is automatically chosen for 32-bit programs (i.e., in the memory model FLAT).

As all FFT-based matrix functions internally rely on VF_FFT, all of them exist in three versions as well. Here, the prefixes are MFp_, MFl_ and MFs_ in the real-number case, or MCFp_, MCFl_ and MCFs_ in the complex case. Similarly to the one-dimensional case, the functions with the "normal" prefix (MF_, MCF_) will automatically be redirected to the MFP_, MFl_ or MFs_ variant, as determined by the memory model.

Although it does not use Fourier transform methods, VF_smooth should be remembered here as a crude form of frequency filtering which removes high-frequency noise.

Back to Table of Contents

## 4.9 Statistical Functions and Building Blocks

The following collection of statistical functions is offered by OptiVec:

VF_sum | sum of all elements
VI_fsum | sum of all elements of an integer vector, accumulated as a floating point number in double or extended precision
VF_prod | product of all elements
VF_ssq | sum-of-squares of all elements
VF_sumabs | sum of absolute values of all elements
VF_rms | root-of-the-mean-square of all elements
VF_runsum | running sum
VF_runprod | running product
VF_sumdevC | sum over the deviations from a preset constant, sum( |Xi-C| )
VF_sumdevV | sum over the deviations from another vector, sum( |Xi-Yi| )
VF_avdevC | average deviation from a preset constant, 1/N * sum( |Xi-C| )
VF_avdevV | average deviation from another vector, 1 / N * sum( |Xi-Yi| )
VF_ssqdevC | sum-of-squares of the deviations from a preset constant, sum( (Xi - C)² )
VF_ssqdevV | sum-of-squares of the deviations from another vector, sum( (Xi - Yi)² )
VF_chi2 | chi-square merit function
VF_chiabs | "robust" merit function, similar to VF_chi2, but based on absolute instead of squared deviations
VF_mean | equally-weighted mean (or average) of all elements
VF_meanwW | "mean with weights" of all elements
VF_meanabs | equally-weighted mean (or average) of the absolute values of all elements
VF_selected_mean | averages only those vector elements which fall into a specified range, thus allowing to exclude outlier points from the calculation of the mean
VF_varianceC | variance of a distribution with respect to a preset constant value
VF_varianceCwW | the same with non-equal weighting
VF_varianceV | variance of one distribution with respect to another
VF_varianceVwW | the same with non-equal weighting
VF_meanvar | mean and variance of a distribution simultaneously
VF_meanvarwW | the same with non-equal weighting
VF_median | median of a distribution
VF_corrcoeff | linear correlation coefficient of two distributions
VF_distribution | bins data into a discrete one-dimensional distribution function

Back to Table of Contents

## 4.10 Data Fitting

Ranging from a simple linear regression to complex fitting problems involving multiple data sets and nonlinear functions with many adjustable parameters, OptiVec offers routines for virtually all practically occurring tasks of data fitting. As all of them, except for simple linear regression, rely on matrix methods, they actually form a part of MatrixLib. This means you have to #include <MFstd.h> (<MDstd.h>< MEstd.h>) or the unit MFstd, (MDstd, MEstd). In the 16-bit models of Borland C/C++, you also have to include the MC??.LIB file for the respective model.

A detailed description of the various data-fitting concepts is given in chapter 13 of MATRIX.HTM. Therefore, at this place, the available X-Y fitting functions are only summarized in the following table:

VF_linregress | equally-weighted linear regression on X-Y data
VF_linregresswW | the same with non-equal weighting
VF_polyfit | fitting of one X-Y data set to a polynomial
VF_polyfitwW | the same for non-equal data-point weighting
VF_linfit | fitting of one X-Y data set to an arbitrary function linear in its parameters
VF_linfitwW | the same for non-equal data-point weighting
VF_setLinfitNeglect | set threshold to neglect (i.e. set equal to zero) a fitting parameter A[i], if its significance is smaller than the threshold
VF_getLinfitNeglect | retrieve current significance threshold
VF_nonlinfit | fitting of one X-Y data set to an arbitrary, possibly non-linear function
VF_nonlinfitwW | the same for non-equal data-point weighting
VF_multiLinfit | fitting of multiple X-Y data sets to one common linear function
VF_multiLinfitwW | the same for non-equal data-point weighting
VF_multiNonlinfit | fitting of multiple X-Y data sets to one common nonlinear function
VF_multiNonlinfitwW | the same for non-equal data-point weighting

Back to Table of Contents

## 4.11 Input and Output

VF_cprint | print the elements of a vector to the screen (or "console" – hence the "c" in the name) into the current text window, automatically detecting its height and width. After printing one page, the user is prompted to continue. (Only for DOS)
VF_print | is similar to VF_cprint in that the output is directed to the screen, but there is no automatic detection of the screen data; a default linewidth of 80 characters is assumed, and no division into pages is made. (Only for DOS and EasyWin)
VF_fprint | print a vector to a stream.
VF_write | write data in ASCII format in a stream
VF_read | read a vector from an ASCII file
VF_nwrite | write n vectors of the same data type as the columns of a table into a stream
VF_nread | read the columns of a table into n vectors of the same type
VF_store | store data in binary format
VF_recall | retrieve data in binary format

The following functions allow to modify the standard settings of VF_write, VF_nwrite and VI_read:

VF_setWriteFormat | define a certain number format
VF_setWriteSeparate | define a separation string between successive elements, written by VF_write
VF_setNWriteSeparate | define a separation string between the columns written by VF_nwrite
V_setRadix | define a radix different from the standard of 10 for the whole-number variants of the V.._read functions

Back to Table of Contents

## 4.12 Graphics

VectorLib includes a range of data-plotting routines. Before any of them may be used, VectorLib graphics has to be initialized:

V_initPlot | initialize VectorLib graphics functions (both Windows and DOS). For Windows, no shut-down is needed at the end, since the Windows graphics functions always remain accessible. V_initPlot automatically reserves a part of the screen for plotting operations. This part comprises about 2/3 of the screen on the right side. Above, one line is left for a heading. Below, a few lines are left empty. To change this default plotting region, call V_setPlotRegion after V_initPlot.
V_initGraph | simultaneously initialize Borland's graphics interface and VectorLib plotting functions (DOS only). Do not call initgraph after V_initGraph. If, on the other hand, you have already called initgraph, do not use V_initGraph, but V_initPlot instead. At the end of the graphics session, the BGI function closegraph has to be used to leave the graphics mode and to release graphics buffer memory.
V_initPrint | initialize VectorLib graphics functions and direct them to a printer (Windows only). By default, one whole page is reserved for plotting. In order to change this, call V_setPlotRegion after V_initPrint.
V_setPlotRegion | set a plotting region different from the default

VectorLib distinguishes between two sorts of plotting functions, AutoPlot and DataPlot. All AutoPlot functions (e.g., VF_xyAutoPlot) execute the following steps:

1. define a viewport within the plotting region (which is either the default region or the one defined by calling V_setPlotRegion)
2. clear the viewport
3. generate a Cartesian coordinate system with suitably scaled and labeled axes
4. plot the data according to the parameters passed to the function

All DataPlot functions execute only the last step. They assume that a coordinate system already exists from a previous call to any of the AutoPlot functions, to V_findAxes, or to V_drawAxes. The new plot is added to the existing one. All settings of this coordinate system have to be valid. The viewport must still be the active one and the scaling of the axes has to fit also for the new data plot.
To add text and lables, a new viewport must be defined. Use SetViewportOrgEx (32-bit Windows), SetViewportOrg (16-bit Windows), or setviewport (DOS).
If you wish to switch back into text mode in DOS, use restorecrtmode. After that, a new call to V_initPlot (not V_initGraph!) brings you back into graphics mode.
For all plotting functions, the different plot styles (symbols, lines, and colors), are specified as parameters, see VF_xyAutoPlot. Here is a list of the available AutoPlot and DataPlot routines:

VF_xyAutoPlot | display an automatically-scaled plot of an X-Y vector pair
VF_yAutoPlot | plot a single Y-vector, using the index as X-axis
VF_xy2AutoPlot | plot two X-Y pairs at once, scaling the axes in such a way that both vectors fit into the same coordinate system
VF_y2AutoPlot | the same for two Y-vectors, plotted against their indices
VF_xyDataPlot | plot one additional set of X-Y data
VF_yDataPlot | plot one additional Y vector over its index

Cartesian complex arrays are printed into the complex plane (the imaginary parts versus the real parts), using

VCF_autoPlot | plot one cartesian complex vector
VCF_2AutoPlot | plot two cartesian complex vectors simultaneously
VCF_dataPlot | plot one additional cartesian complex vector

At present, there are no plotting functions for polar complex vectors included.
It is possible to draw more than one coordinate systems into a given window on the screen. The position of each coordinate system must be specified by the above-mentioned function V_setPlotRegion. "Hopping" between the different coordinate systems and adding new DataPlots after defining new viewports (e.g., for text output) is made possible by the following functions:

V_continuePlot | go back to the viewport of the last plot and restore its scalings
V_getCoordSystem | get a copy of the scalings and position of the current coordinate system
V_setCoordSystem | restore the scalings and position of a coordinate system; these must have been stored previously, using V_getCoordSystem

DOS only:
When using multiple coordinate systems on the same screen, the default font used for axis labeling might be too large, so that neighbouring labels overlap each other. In these cases, use the BGI function settextstyle to switch to another font before calling an AutoPlot function.

Back to Table of Contents

#

5. Error Handling

## 5.1 General Remarks

There are two general types of error handling: by the hardware, or by the software. In order to prevent uncontrolled program crash, it is highly desirable that conditions, leading to hardware errors, be recognized before the errors actually occur. All high-level computer languages support this software error-handling to various degrees of perfection. Within the tightly-defined functions and routines of this OptiVec package, often an even more efficient error handling by the program itself is possible than provided by the compilers for user-written code.
However, it should be noted that no absolute overflow protection is possible for the extended-precision versions. They do not have a "safety margin" left as in the single- and double-precision versions, where internally all calculations are performed in extended precision. Especially the VEx_ and VCEx_ versions may fail if constant parameters are very large, or if the X vector elements themselves are already near the overflow limit. To be on the safe side, constant parameters should not exceed about 1.E32 for float, 1.E150 for double, and 1.E2000 for extended parameters.

In the "expanded" versions of all functions with extended accuracy (those with the prefixes VEx_ and VCEx_; for example VEx_exp), there is generally no overflow protection for the calculation of A*Xi+B, but only for the core of the function itself and for the final multiplication by C.

A series of identical errors occurring within one and the same OptiVec function leads to one error message only. Subsequent identical messages are suppressed.

There is a fundamental difference between floating-point and integer numbers with respect to OVERFLOW and DOMAIN errors: for floating-point numbers, these are always serious errors, whereas for integer numbers, by virtue of the implicit modulo-2n arithmetics, this is not necessarily the case. In the following two paragraphs, details are given on the error handling of integer and floating-point numbers, respectively.

Back to Table of Contents

## 5.2 Integer Errors

The only genuine integer errors are ZERODIVIDE errors (if a division by 0 is attempted). Other integer errors are neglected due to the implicit definition of integer operations as being performed modulo the respective power of 2 (see chapter 4.4). For those situations in which implicit modulo 2n arithmetics is not appropriate, OptiVec offers the possibility to trap these errors and print an error message and/or abort the program.
All functions where INTEGER OVERFLOW (e.g., in VI_ramp, VI_mulV, etc.) or INTEGER DOMAIN errors (e.g., in V_ItoU for negative X-values) may occur, exist in two versions: the "normal" version employs modulo 2n arithmetics and interchanges signed and unsigned data types according to their bit pattern. For the 16-bit and 32-bit integer types (but not for 8-bit and 64-bit), there is a second version which also employs modulo 2n arithmetics, but detects the errors. This second variant is denoted by the letter "o" (for "overflow detection") in the prefix: VIo_, VSIo_, VULo_, etc. (for the data-type interconversion functions, the prefix V_ is changed into Vo_). Here, the action to be taken in the case of INTEGER OVERFLOW or INTEGER DOMAIN errors has to be defined by calling V_setIntErrorHandling somewhere before the call to the VIo_ function. V_setIntErrorHandling takes an argument of the type V_ihand (defined in <VecLib.h> and the unit VecLib) with one of three possible values:

ierrNote | print an error message
ierrAbort | print an error message and exit the program
ierrIgnore | ignore the problem. With this last option, the error handling can be switched off intermediately.

Although you may use a call to
V_setIntErrorHandling( ierrIgnore );
to switch the error handling off, it is always better simply to use the "normal" VI_ version rather than the VIo_ version with the error-handling short-cut, as the normal version is always much faster.

C/C++ only:
To choose the overflow-detecting version not only for single function calls, but everywhere, the easiest way is to define symbolic constant V_trapIntError in the program header before(!) <VecLib.h> is included:
Example:
#define V_trapIntError 1
#include <VIstd.h>
#include <VImath.h>
.....
main() /* or WinMain(), or OwlMain() */
{
iVector I1, I2;
I1 = VI_vector( 1000 ); I2 = VI_vector( 1000 );
V_setIntErrorHandling( ierrNote );
VI_ramp( I1, 1000, 0, 50 ); /* an overflow will occur here! */
V_setIntErrorHandling( ierrIgnore );
VI_mulC( I2, I1, 1000, 5 );
/* here, even a whole series of overflows will occur; they are all ignored. */
....
}

Back to Table of Contents

## 5.3 Floating-Point Errors

## 5.3.1 C/C++ specific

In order to understand the details of the floating-point error handling outlined in the following sections, you may wish to refer to the description of the functions _matherr and signal in the documentation of your C++ compiler.
(Borland C++ only: prior to the version 4.0, instead of _matherr() the function matherr() – without the leading underbar – was used, see below).
Keep in mind that _matherr and _matherrl are the user-definable focal points for the handling of all software-detected errors, whereas signal is used to install a handler for hardware-detected errors (which should better be avoided in the first place). Within the VectorLib functions, _matherr is used for the error handling in the VF_, VCF_, VD_, and VCD_ versions. _matherrl is used in the VE_ and VCE_ versions (Borland C++ only, as Visual C++ does not support 80-bit real numbers).

If the function in which an error occurs has one real-valued argument, only the parameter e->x is defined in calling _matherr and e->y is left undefined. Only if there are two arguments (like in VF_atan2 or in VF_cotrpi), both e->x and e->y are needed to hold these arguments. For complex arguments, the real part (or the Mag part for polar coordinates) is stored in e->x and the imaginary part (or the Arg part) in e->y.

Back to Table of Contents

## 5.3.2 Pascal/Delphi specific

The types of errors occurring in mathematical functions are described in detail below. How OptiVec handles each type of error is defined by a call to V_setFPErrorHandling. The possible options are set by the fperrXXX constants described with V_setFPErrorHandling. When calling V_setFPErrorHandling, combine these constants by the OR operator. Note that this influences only the way errors are handled within OptiVec functions. It does not affect the way how the standard Borland Pascal/Delphi functions handle errors.

Back to Table of Contents

## 5.3.3 Error Types (Both C/C++ and Pascal/Delphi)

For each VectorLib function, the types of errors that are detected and handled are described in the Alphabetical Reference. All functions derived from ANSI C or Pascal math functions (those whose declarations are to be found in <V?math.h> or the units V?math) contain a fully-fledged mathematical error handling. In addition to the error handling "by element", their return value shows if all elements have been processed error-free (return value FALSE or 0) or if an error occurred and was handled (return value TRUE or different from 0).

In the following description of all floating-point error types, we denote by "HUGE_VAL" the largest number possible in the respective data type. Similarly, "TINY_VAL" is the smallest denormal number representable in the respective data type; this is not the same as "MIN_VAL", which is the smallest full-accuracy number of the respective data type.

- DOMAIN errors most often lead to the result NAN ("not-a-number"). Even if nothing happens within the function itself that detects a DOMAIN error, an uncontrolled program crash may result if subsequent operations are performed on the vector element set to NAN.
  For C/C++, we therefore recommend to modify _matherr and _matherrl in such a way that the program is aborted if a DOMAIN error occurs (for an example, see below; alternatively, the UNIX style may be adopted; see the file MATHERR.C supplied with the your C/C++ compiler). Changing the return value of _matherr is another possiblity, but the better way very clearly is to avoid any DOMAIN errors by performing appropriate checks before calling functions like VF_sqrt, VF_log, VF_atan2 etc.
  For Pascal/Delphi, we recommend not to change the default setting of V_FPErrorHandlingMode or to include "fperrAbortDOMAIN" in any changes.
  Note: the pseudo-numbers INF and NAN are not allowed as input for any functions of OptiVec. They are not tested for; their presence will normally result in a hardware interrupt.
- SING errors are treated like an extreme case of OVERFLOW (see below). In most cases, they arise from an implicit division by zero or from taking the logarithm of zero. The proposed result is never NAN, but always a "number", in most cases ±HUGE_VAL. Although it is recommended also in the case of SING errors to abort the program and take the necessary measures to avoid them, you may choose to continue program execution.
- OVERFLOW errors are the most abundant form of floating-point errors. They are always handled by proposing +HUGE_VAL or -HUGE_VAL as the result. Within many user algorithms, OVERFLOW errors may occur for intermediate results; if subsequent steps perform operations like taking the inverse, the final result may be acceptable despite the error. Therefore, we recommend to accept the return-value proposal and not to abort the program.
  C/C++ only: In principle, you may decide not to accept the return-value proposal of _matherr, but to substitute another one. However, for several reasons you are discouraged from doing that: the correct sign of the result is set by the calling ("complaining") function in many cases only after returning from _matherr; the x-value passed to _matherr (which should be inspected before the return value is modified) may either be Xi or (as in some of the expanded complex math functions of the VCEx_... family) the intermediate result x' = Ax + B. Note, furthermore, that all x-values are passed to _matherr as double-precision floating-point numbers, also in the case of integer input numbers (like in VF_tanrpi, where Pi and q are passed as x and y to _matherr).
- TLOSS ("total loss of precision") errors are detected only if a more serious error might occur in the respective function. For example, the sine function takes on values between -1 and +1 for all arguments. So, in case of an argument too big for the sine function to be evaluated with any accuracy, the result may nevertheless be "tacitly" set to 0.0 and no call to the OptiVec error handler is generated (whereas Borland C++ chooses NAN, "not a number", as the result, which is certainly even less correct than arbitrarily choosing 0.0).
  On the other hand, the cosecant, i.e. the inverse of the sine, is not defined for arguments of integer multiples of p. Therefore, a more serious error (in this case a SING or an OVERFLOW error) might be hidden under the TLOSS for very big arguments. This possibility is taken into account by calling the error handler, although the proposed result is again set to 0.0 (which is the mean of the two extremes +HUGE_VAL and -HUGE_VAL). Generally, the default result in the case of a TLOSS error is the mean of the results for arguments of +0.0 and -0.0.
- UNDERFLOW errors are never detected; underflowing results are always "tacitly" set to denormal numbers or finally to 0.0 by the floating-point processor itself. Indeed, you may very rarely wish to do something else in this case.
- As in all non-vectorized math functions of the Borland compilers and Visual C++, PLOSS ("partial loss of precision") errors are never detected and precision problems simply ignored.

Back to Table of Contents

### 5.3.4 Borland C++ only:
Differences between Borland C++ 4.0 and earlier versions

Borland C++ uses the function _matherr in the way described above only from version 4.0 on. Earlier versions employ the function matherr (without the leading underbar in the function name). In order to be usable both with the new and the old versions, VectorLib primarily calls matherr as for the older versions. The include-file <VecLib.h> provides a macro NEWMATHERR for the redirection of these calls to the new _matherr(). This macro should appear somewhere in the module containing the main() or WinMain() procedure, after the header:
#include <VecLib.h>
#include ...
NEWMATHERR
......
main()
{ ... }

Back to Table of Contents

## 5.4 The Treatment of Denormal Numbers

"Denormal" are very small numbers between zero and the smallest full-accuracy number available in the respective data type. You may understand the underlying principle from a simplified example: 1.175494E-38 is the smallest "normal" float, with 7-digit accuracy. What about 1/1024 of this value? This can only be represented as 0.001145E-38, which is accurate to only four digits, since the first three digits are needed to hold zeros. Thus, denormal numbers provide a smooth transition between the smallest representable normal numbers and zero.

In general, they may be treated just as ordinary numbers. In some instances, however, like taking the inverse, overflow errors may occur. In these cases, the somewhat academic distinction between SING and OVERFLOW errors is dropped and a SING error signalled (as if it was a division by exactly 0).

On the other hand, for functions like the logarithms, very small input numbers may give perfectly reasonable results, although the exact number 0.0 is an illegal argument, leading to a SING error. Here, the possible loss of precision is neglected and denormals are considered valid arguments. (This treatment is quite different from that chosen for the math functions of most compilers, where denormal arguments lead to SING errors also in these cases, which seems less appropriate to us.)

Back to Table of Contents

## 5.5 Advanced Error Handling: Writing Messages into a File

Quite generally, the libraries shipped with compilers do not offer the programmer much control over the way error messages are printed. While this is fine in most instances, there may be situations in which you might, for example, wish the error messages not to be printed to the screen, but rather into a file, so that you could check later what has gone wrong. An additional motivation could come from the fact that, for any error occurring in a Windows program, a message box is displayed and program execution interrupted until you acknowledge having taken notice of the error.

You might wish to circumvent this. To this end, OptiVec provides the function V_setErrorEventFile. This function needs as arguments the desired name of your event file and a switch named ScreenAndFile which decides if the error message is printed only into the file, or additionally to the screen as well.
Note that this redirection of error messages is valid only for errors occurring in OptiVec routines. If you wish to do so, however, there is a way in C/C++ to extend the redirection also to the "non-OptiVec" functions: you may modify _matherr and _matherrl such that the statement
return 0;
(which signals an unresolved error) is replaced by the sequence
V_noteError( e->name, e->type ); return 1;
Thereby the task of printing the error message for unresolved errors is passed to the OptiVec function V_noteError. Keep in mind that it is the return value of _matherr which decides if an error message is printed by the default error handler of your compiler. Thus, after the call to V_noteError, the printing of the default error messages is by-passed by returning "1". (Also, do not forget that OptiVec uses your _matherr routine to determine which errors you accept and which not!)
For example, your _matherr function (matherr – without the leading underbar – for Borland C++ 3.0 and 3.1) might look like the following one:
#include <math.h>
int _matherr( struct exception *e) /* "_exception" for MSVC */
{
if( (e->type == UNDERFLOW) || (e->type == TLOSS) ) ; /* ignore */
else /* all other errors deserve at least notice */
{
V_noteError( e->name, e->type );
if (e->type == DOMAIN) exit(1); /* really fatal */
}
return 1;
}
(Of course, if you decide to change _matherr, do not forget to change _matherrl in the same way, if you are using Borland C++!).

Both C/C++ and Pascal/Delphi: The default printing of error messages on the screen alone is restored by V_closeErrorEventFile.

A way to keep track also of those errors which do not lead to messages is opened by the return values of mathematical VectorLib functions. Any of the "silent" TLOSS along with the more serious DOMAIN, SING and OVERFLOW errors will lead to a TRUE (non-zero) return value. You may wish to check for a clean result after a group of functions, like in the following example:
unsigned ErrFlag;
...
/* part Trig1 */
ErrFlag=0; /* reset the flag */
ErrFlag |= VF_sin( Y1, X1, sz );
ErrFlag |= VF_cos( Y2, X1, sz );
ErrFlag |= VF_atan2( Z1, Y1, Y2, sz );
if( ErrFlag ) V_printErrorMsg( "Errors occurred in part Trig1 ! ");
...
As indicated in the example, it is better to use the |= operator (Pascal/Delphi: "ErrFlag := ErrFlag or") instead of += (since, in rare cases, all return values might add up to 65536, which is stored as 0 due to an overflow of the integer variable). Even if you chose addition of the individual return values, the number of occurred errors would not be obtainable from the result; in case of an error, any non-specified non-zero number is returned.

Back to Table of Contents

## 5.6 OptiVec Error Messages

Just as with any C/C++ or Pascal program, errors occurring within mathematical functions lead to the appropriate error messages. See paragraph 5.3.3 for details.
Apart from math errors, there are some run-time errors specific to OptiVec routines. These errors lead to the messages noted below. The name of the function where the error occurred is not always exactly the name you wrote in your program. Instead of the prefix VI_, the message will read VLI_ or VSI_, depending on the memory model used. Similarly, instead of VU_, you will find VUL_ or VUS_.
VFs_FFT or VFl_FFT will be substituted for VF_FFT, again depending on the memory model. The reason for this behavior is that many functions share code and even names.

(00) OptiVec/CMATH not installed correctly (must use INSTALL.EXE !)

Shareware version only: Either you are running a program containing OptiVec functions on a different computer than the one on which you installed OptiVec. The distribution of applications containing OptiVec functions is possible only with the Registered version.
Or you attempted to install OptiVec by unzipping the package by hand, without using INSTALL.EXE. This is not possible, as INSTALL.EXE also starts the clock for the trial period. With Windows 2000 / XP, you have to run InitOVBC.EXE (Borland C++ version) or InitOVVC.EXE (MSVC version) instead of INSTALL.EXE.

(0) Trial period for OptiVec/CMATH has expired!

Shareware version only: Consider ordering the registered version!

(1) Not enough memory.

You are trying to allocate memory, but there is not enough left. This error occurs mostly in connection with "forgotten" vectors that are allocated but never freed. Try these solutions:
* Look out for vectors that might be no longer needed and free them as soon as possible. Be sure that any vectors allocated in subroutines are freed before leaving the subroutine.
* Are you still working with 16-bit models? If you need to work with large amounts of data, the memory model FLAT should be used, working with Win32, WindowsNT, or Windows95/98.
* Store data intermediately on disk, using VF_store, and retrieve them using VF_recall, when needed. This method is slow and should be used only if really unavoidable.

(2) Vector > 64 kB not possible (16-bit). (2) Vector > 4 GB not possible (32-bit).

* Either you are trying to allocate a vector whose size exceeds the maximum of 4 GB (32-bit) or 64 kB (16-bit except HUGE).
* Or you are in the HUGE model and attempt to process a huge vector in a function where the size is limited to 64 kB even in this model. This might happen, e.g., if the table is too large in one of the interpolation routines. In this case, you must either use the model FLAT or split up your problem into smaller vectors.

(3) Vectors/matrices must not be identical.

In some functions where more than one input vector (or matrix) is used to calculate more than one output vector (or matrix), attention has to be paid, which output data may or may not overwrite which input data. See the specification of the function where the error occurred.

(4) Cannot use requested format (too many entries per line).

This error occurs with the printing functions. The parameter nperline was chosen too large. The function automatically selects the maximum nperline possible and continues execution, but you should nevertheless consider adapting your program.

(5) X-values of table must be distinct.

The routines performing interpolations need the tables to be ordered according to their x-values. Each x-value may occur only once, as it is impossible for one and the same x-value to belong to different y-values.

(6) Not possible with fewer than n elements.

Some functions require a minimum size of n elements of the vector processed.

(7) Not possible with more than n elements.

Some functions are limited to a maximum size of n elements. This is true, for example, for VF_polyinterpol, where only up to 10 table elements may be used for the each interpolation.

(8) Size must be an integer power of 2.

For all functions using – explicitly or implicitly – Fast Fourier Transform methods, the vector size has to be an integer power of 2. Enlarge or truncate your vector(s) to meet that condition.

(9) Invalid parameter(s).

In some functions, certain combinations of input parameters are illegal. For example, it is not possible to perform a 9-point interpolation on only 5 data points.

(10) Cannot scale symbols by more than a factor of 50.

(Windows only.) The symbols used in VectorLib plotting functions cannot be magnified by more than a factor of 50 (which means already filling the screen with a single symbol). Program execution is continued with a value of 50.0.

(11) Cannot use line thicker than 500 pixels.

(Windows only.) The lines used in VectorLib plotting functions cannot be thicker than 500 pixels (which is already nonsense). Program execution is continued with a value of 500.

(12) Cannot free non-existent vector.

You called V_free or V_nfree for a vector that has no memory allocated. Program execution is continued without freeing anything.

Back to Table of Contents

(21) Invalid dimension(s).

In one way or the other, the dimensions of input or output matrices do not meet the requirements.

(22) Matrix is singular.

In a function requiring regular input matrices, a singular matrix was encountered. Consider alternative functions, designed to handle (nearly) singular matrices.

(23) No convergence achieved.

Some iterative matrix methods may, in very rare cases, fail to achieve convergence.

(24) Only the first n elements of each row can be printed.

This error occurs with the matrix printing functions. The number of columns is too large for the available linewidth. The function automatically selects the maximum number of columns to be displayed and truncates the rows as needed.

#

6. Trouble-Shooting

## 6.1 General Problems

In case of problems, please check first if OptiVec is correctly installed (see chapter 1.4). If this is the case, carefully check the following points whose violation would inevitably lead to failure.

- The choice of the OptiVec library must match your selection of memory model, processor, and environment. With Borland C++, you are not going to have much fun with the library VCL3.LIB under Windows3.x (where you need VCL3W.LIB), and the libraries designed for Borland C++ will not work with Visual C++ or any other compiler. Similarly, OVVCSD.LIB, designed for single-thread debug in Visual C++, will not work in any multi-thread or any "release" link.
- You must not use vectors with a size of 0. All functions tacitly assume that the vectors have at least one element and do not waste your computer time testing for that.
- You must not use vectors that are only declared, but have no allocated memory (see the description of VF_vector). If you did not switch off warnings, you may be warned also by the compiler not to do that ("possible use of xxx before definition").
- Constant parameters should not exceed 1.E32 for floats, 1.E150 for doubles, or 1.E2000 for long doubles. Normally, these ranges should suffice for any application...
- 16-bit Borland C++ only: Do not forget to write the line
  NEWMATHERR
  after the header into the module containing main(), WinMain(), or OwlMain(), in order to maintain compatibility both with older and later versions of Borland C++ (see chapter 5.3.1).

Although OptiVec has been tested thoroughly, there is, of course, always the possibility that a problem might have escaped our attention. Should you feel you discovered a "bug" in OptiVec, please try to specify the situation causing the problem as exactly as possible and let us know at support@optivec.com!

Back to Table of Contents

## 6.2 Problems with Windows3.x?

Programming for 16-bit Windows is much more involved than programming for either DOS or 32-bit Windows. While DOS gives the programmer almost complete control over both the main processor and the coprocessor, Windows demands much of this control for itself. This introduces problems you should be aware of. They are not at all specific to OptiVec. However, since they seem not to be very widely known, here is a collection of some of them. Up to now, these problems have not been observed with the memory model FLAT used with Win32 (Microsoft's 32-bit extension of Windows 3.1), Windows NT or Windows95/98/2000.

- The background routines controlling intermediate results do not only work at the expense of your time, they may also at some point decide to load a NULL selector into the segment registers FS and GS. If you happen to use these registers (somehow, they were meant by Intel to be used!), Windows' answer on your next operation will be the familiar "General Protection Fault (Error 13)". Therefore, the Windows versions of OptiVec do not use FS and GS at all.
- If a floating-point multiplication or division happens to result in a so-called "denormal number" (see chapter 5.4), Windows at first accepts this result. The next time you use this denormal result, however, Windows decides that it had better been zero. Checking for zero by a comparison like
  if(x != 0.0)...
  yields the correct answer that x it is not zero, but, after (!) this check, Windows makes x exactly zero, if it is loaded onto the number stack. This leads to hard-to-find errors. If you inspect OptiVec routines with a debugger, you may at some points encounter strange, seemingly ineffecient code being used for comparisons. This is a fix for the described problem which costs time, but saves you from Windows- induced DIVIDE ERROR crashes.
- Related to the last problem is another "feature" of Windows3.x: after the comparison of two floats or two doubles, one of which is denormal, -NAN ("minus not-a-number") may appear on the number stack. Some time later, this leads to a "Floating-point invalid" or a "Stack Overflow" error – another means of killing your application. If you encounter -NAN on the number stack when debugging your programs (with or without VectorLib used), you should find out which comparison(s) caused the problem and add the line
  asm ffree ST(0);
  after this or these comparisons.

Back to Table of Contents

## 6.3 Problems with 32-bit Windows?

Until now, no problems of the type described above for 16-bit Windows have occurred. There has, however, been one nice surprise with the switch from Windows95 to Windows98: The Windows API function MessageBox now empties the number stack of the processor's FP unit – apparently for no justifiable reason. To be honest, it has taken us until version 3 of OptiVec to identify this Windows feature as the source of some spurious program crashes after math errors. (The constants A, B, C were suddenly missing in functions like VFx_exp after an overflow error.) Now, care is taken to save the number stack before all calls to MessageBox and restore it afterwards.

Back to Table of Contents

## 6.4 Problems with Borland's 16-bit Linker?

When working with large programs and libraries, older versions of TLINK sometimes run into problems. You may get error messages like "Linker stack overflow", "Out of memory", "Table limit exceeded", "Extended dictionaries ignored", or "Unresolved external xxx referenced from module yyy".
Try to give the linker as much memory as possible by closing applications, removing drivers etc. If that does not help, re-arrange your project list. Curiously enough, that solves the problem sometimes.
In the case of "Unresolved external" linker errors, there is only one way (if the error is not caused by wrong spelling). First you have to use TLIB in order to get a listing of the respective library (see the description of TLIB !). Screening the .LST file thus obtained with a text editor, you find the module containing the symbol which the linker was unable to locate. Using again TLIB, you have to extract this module from the library and add the resulting .OBJ file to your project list.

If you are still working with Borland (Turbo) C++ 3.x, you cannot simultaneously include VC??.LIB and MC??.LIB, as the linker is not able to handle these large libraries. As very few people are still using BC 3, we have discontinued full support for that compiler and do no longer offer the special BC 3 version OptiVec, previously fixing that problem by splitting the libraries up into many smaller ones. You still can extract the needed modules from the MC??.LIB libraries yourself, however, and include them as .OBJ nodes into your project.

Back to Table of Contents

#

7. The Include-Files and Units of OptiVec

All OptiVec C/C++ include-files have the same names as the corresponding Pascal/Delphi units. Naturally, the extension is different: ".H" for the C/C++ include-files, ".DCU" for Delphi units, and ".TPU" or "TPP" for Pascal units. Below, only the names are listed, without the extension.

C/C++ only: Declare the use of OptiVec functions with #include statements. If you are using MFC (Microsoft Foundation Classes) or Borland's OWL (ObjectWindows Library), the MFC or OWL include-files have to be #included before (!) the OptiVec include-files.

Pascal/Delphi only: Declare the use of OptiVec functions with the uses clause.

Include-file (add suffix .H) or unit | Contents
VecLib | Basic definitions of the data types along with the functions common to all data types (prefix V_) except for the graphics initialization functions.
VFstd, VDstd, VEstd | Floating-point "standard operations:" generation and initialization of vectors, index-oriented manipulations, data-type interconversions, statistics, analysis, geometrical vector arithmetics, Fourier-Transform related functions, I/O operations.
VCFstd, VCDstd, VCEstd, VPFstd, VPDstd, VPEstd | Standard operations for cartesian and polar complex vectors
VIstd, VBIstd, VSIstd, VLIstd, VQIstd | Standard operations for signed integer vectors
VUstd, VUBstd, VUSstd, VULstd, VUIstd | Standard operations for unsigned integer vectors (VUIstd only for C/C++)
VFmath, VDmath, VEmath | Algebraic, arithmetical and mathematical functions for floating-point vectors
VCFmath, VCDmath, VCEmath, VPFmath, VPDmath, VPEmath | Arithmetical and mathematical functions for complex vectors
VImath, VBImath, VSImath, VLImath, VQImath | Arithmetical and mathematical functions for signed integer vectors
VUmath, VUBmath, VUSmath, VULmath, VUImath | Arithmetical and mathematical functions for unsigned integer vectors (VUImath only for C/C++)
Vgraph | Graphics functions for all data types
VFNLFIT, VDNLFIT, VENLFIT | Non-linear fitting functions (Pascal/Delphi only; in C/C++, they are in M?std)
VFMNLFIT, VDMNLFIT, VEMNLFIT | Non-linear fitting functions for multiple data sets (Pascal/Delphi only; in C/C++, they are in M?std)
MFstd, MDstd, MEstd | Matrix operations for real-valued matrices
MCFstd, MCDstd, MCEstd | Matrix operations for cartesian complex matrices
Mgraph | Matrix graphics functions for all data types
MFNLFIT, MDNLFIT, MENLFIT | Non-linear fitting functions for Z = f(X, Y) data (Pascal/Delphi only; in C/C++, they are in M?std)
MFMNLFIT, MDMNLFIT, MEMNLFIT | Non-linear fitting functions for multiple Z = f(X, Y) data sets (Pascal/Delphi only; in C/C++, they are in M?std)
NEWCPLX | complex class library CMATH; C++ only
CMATH | complex library CMATH for Pascal/Delphi and plain C
CFMATH, CDMATH, CEMATH | C/C++ only: type-specific parts of CMATH.
XMATH | A few non-vectorized math functions needed internally by other OptiVec functions; they are publically accessible (see chapter 9). C/C++: declares also the sine, cosec, and tangent tables for VF_sinrpi2 etc.
FSINTAB2, DSINTAB2, ESINTAB3, FSINTAB3, DSINTAB3, ESINTAB3 | sine tables (Pascal/Delphi only; for C/C++, they are in XMATH)
FCSCTAB2, DCSCTAB2, ECSCTAB3, FCSCTAB3, DCSCTAB3, ECSCTAB3 | cosecant tables (Pascal/Delphi only; for C/C++, they are in XMATH)
FTANTAB2, DTANTAB2, ETANTAB3, FTANTAB3, DTANTAB3, ETANTAB3 | tangent tables (Pascal/Delphi only; for C/C++, they are in XMATH)
VecObj | basic definitions for VecObj, the object-oriented interface for C++
fVecObj, dVecObj, eVecObj | VecObj member functions for real-valued vector objects (C++ only)
cfVecObj, cdVecObj, ceVecObj pfVecObj, pdVecObj, peVecObj | VecObj member functions for complex vector objects (C++ only)
iVecObj, biVecObj, siVecObj, liVecObj, qiVecObj | VecObj member functions for signed-integer vector objects (C++ only)
uVecObj, ubVecObj, usVecObj, ulVecObj, uiVecObj | VecObj member functions for unsigned-integer vector objects (C++ only)
OptiVec | includes the whole OptiVec package (C++ only)
VecAll | includes all VectorLib and CMATH functions (C or C++ only)
MatAll | includes all MatrixLib functions (C or C++ only)

Back to Table of Contents

#

E N D

Copyright for OptiVec software and documentation
© 1996-2008 OptiCode – Dr. Martin Sander Software Dev.
All rights reserved!