The variety of possible numeric types which may be used to store the values
of NDF array components (see §) poses another problem
for the applications programmer.
What type of arithmetic should be chosen to process the values?
The simplest option, and the one which is recommended for normal use, is:
Use single-precision floating-point arithmetic for calculations wherever possible...
and access all array components (other than quality) as `_REAL'
values, taking advantage of the implicit type conversion provided by the
NDF_ routines if necessary (see §).
However, when writing general-purpose software which may be heavily used,
the possibility of duplicating the main processing algorithm so that
calculations can be performed using several alternative types of arithmetic
might also be considered.
This allows applications to support the processing of unusual numeric types
(e.g. double-precision), while normally using less computationally
expensive arithmetic (e.g. single-precision) when that is adequate.
The ability to explicitly handle values with a variety of numeric types also
makes it less likely that an expensive type conversion will have to be
performed implicitly by the NDF_ routines (see
§).
Of course, the disadvantages of this approach are that extra work is
involved in duplicating the main processing algorithm.
The magnitude of this extra work should not be underestimated, although the
use of the GENERIC compiler (SUN/7) can simplify the
task to some extent.
To give a concrete example, suppose you decide to duplicate the main processing algorithm in an application so that it can perform calculations using either single- or double-precision arithmetic. Input NDFs with numeric array types of `_REAL' or `_DOUBLE' can then be processed directly, although with other numeric types (or with mixed types) implicit type conversion will still need to occur. This can get rather complicated to sort out, so a simple routine NDF_MTYPE is provided to make the decision about which version of an algorithm to invoke in a particular case, depending on the declared capabilities of an application, for instance:
INCLUDE 'NDF_PAR'
CHARACTER ITYPE * ( NDF__SZTYP ), DTYPE * ( NDF__SZFTP )
...
CALL NDF_MTYPE( '_REAL,_DOUBLE', INDF1, INDF2, 'Data', ITYPE, DTYPE, STATUS )
The first (TYPLST) argument is a list of the numeric types which the application can process explicitly (`_REAL' and `_DOUBLE') supplied in order of preference, i.e. in order of increasing computational cost in this instance. NDF_MTYPE will examine the two NDFs supplied and select a numeric type from this list to which the data component values should be converted for processing so that no unnecessary loss of information occurs. The conversion from `_REAL' to `_DOUBLE' would be acceptable, for instance, whereas the opposite conversion process would lose information.
The resulting implementation type is returned as an upper-case character string via the ITYPE argument, and may be used both as an input argument to NDF_MAP for accessing the NDFs' values, as well as for deciding which version of the main algorithm to invoke, for instance:
CALL NDF_MTYPE( '_REAL,_DOUBLE', INDF1, INDF2, 'Data', ITYPE, DTYPE, STATUS )
* Access the input array values using the selected implementation type.
CALL NDF_MAP( INDF1, 'Data', ITYPE, PNTR1, EL, STATUS )
CALL NDF_MAP( INDF2, 'Data', ITYPE, PNTR2, EL, STATUS )
* Invoke the appropriate version of the processing algorithm.
IF ( ITYPE .EQ. '_REAL' ) THEN
<invoke the single-precision algorithm>
ELSE IF ( ITYPE .EQ. '_DOUBLE' ) THEN
<invoke the double-precision algorithm>
END IF
The NDF_MTYPE routine also addresses the question of how to store the
results of the calculation since, ideally, the numeric type used for the
output array(s) should preserve the precision of the calculation without
unnecessarily increasing the storage space required.
A suitable numeric type is therefore returned as an upper-case character
string via the DTYPE argument and may be used as an input argument to a
routine such as NDF_CREAT which creates an output NDF.
More commonly, however, it will be used to change the numeric type of an NDF
which has already been created by a call to NDF_PROP (see §),
for instance:
CALL NDF_PROP( INDF1, ' ', 'OUT', INDF3, STATUS )
CALL NDF_MTYPE( '_REAL,_DOUBLE', INDF1, INDF2, 'Dat,Var', ITYPE, DTYPE, STATUS )
CALL NDF_STYPE( DTYPE, INDF3, 'Dat,Var', STATUS )
Here, a new output NDF is created based on the first input NDF by using
NDF_PROP, and an identifier INDF3 is obtained for it.
NDF_MTYPE is then called to determine an appropriate numeric type for
storing the output data and variance components, and NDF_STYPE
is invoked to set the output components' type appropriately.
In a typical application, the input and output arrays would probably then be
accessed and passed to the appropriate version of the main processing
algorithm.
A complete example of this sequence of events can be found in
§, where its integration with the handling of the
bad-pixel flag and matching of the NDFs' bounds is also
demonstrated.
Note that an equivalent routine NDF_MTYPN also exists for merging and matching the numeric types of an arbitrary number of NDFs, whose identifiers are supplied in an integer array, as follows:
INTEGER N, NDFS( N )
...
CALL NDF_MTYPN( '_INTEGER,_REAL', N, NDFS, 'Data', ITYPE, DTYPE, STATUS )
Both this routine and NDF_MTYPE will report an error and set STATUS to NDF__TYPNI (processing of data type not implemented), as defined in the include file NDF_ERR, if it is not possible to select an implementation type from the list supplied without leading to loss of information. This would be the case for instance, if one of the NDFs had a data component with a numeric type of `_DOUBLE' but the application could only perform single-precision arithmetic. In this case, the values returned via the ITYPE and DTYPE arguments would be the ``best compromise'' values and could still be used, if required, by ignoring the error condition. For instance:
INCLUDE 'NDF_ERR'
...
CALL ERR_MARK
CALL NDF_MTYPE( '_REAL', INDF1, INDF1, 'Data', ITYPE, DTYPE, STATUS )
IF ( STATUS .EQ. NDF__TYPNI ) CALL ERR_FLUSH( STATUS )
CALL ERR_RLSE
Here, the error system routine ERR_FLUSH has been used to deliver the error message to the user and reset STATUS (see SUN/104). The user would then receive a warning message explaining that `_DOUBLE' data could not be handled without loss of information, and that conversion to `_REAL' would have to be used. The application could then perform the conversion and processing, having warned the user about the unavoidable loss if information which will occur.