Please accept my resignation. I don't want to belong to any club that will accept me as a member.
Every time I paint a portrait I lose a friend
In addition to the constructor functions and operators already discussed, there exist further LINT
functions that make the C functions developed in Part I available to LINT
objects. In the following discussion we make a rough separation of the functions into the categories "arithmetic" and "number-theoretic." The implementation of the functions will be discussed together with examples; otherwise, we shall restrict ourselves to a table of information needed for their proper use. We shall give more extensive treatment in the following sections to the functions for the formatted output of LINT
objects, for which we shall make use of the properties of the stream classes contained in the C++ standard library. Possible applications, in particular for formatted output of objects of user-defined classes, are given rather short shrift in many C++ textbooks, and we are going to take the opportunity to explicate the construction of the functions needed to output our LINT
objects.
The following member functions implement the fundamental arithmetic operations as well as modular operations for calculation in residue class rings over the integers as accumulator operations: The object to which a called function belongs contains the function result as implicit argument after its termination. Accumulator functions are efficient, since they operate to the greatest extent without internal auxiliary objects and thus save unnecessary assignments and calls to constructors.
For the cases in which a free assignment of the results of calculations is unavoidable, or in which the automatic overwriting of the implicit argument of the member functions with the result is not desired, the member functions were extended by means of like-named analogous friend functions together with additional friend functions. These are not discussed further here, but are recorded in Appendix B. The treatment of possible error situations in LINT
functions that can arise from the use of CLINT
functions will be discussed in full in Chapter 16.
Before we list the public member functions, we consider first as an example of their implementation the functions
LINT& LINT::mexp (const LINT& e, const LINT& m );
and
LINT& LINT::mexp (USHORT e, const LINT& m);
for exponentiation, an operation for which C++, alas, offers no operator. The functions mexp()
were constructed in such a way that the functions used are, according to the type of the operands, the C functions mexpk_l()
, mexpkm_l()
, umexp_l()
, and umexpm_l()
, optimized for this purpose (with the corresponding arithmetic friend functions we are likewise dealing with the exponentiation functions wmexp_l()
and wmexpm_l()
with USHORT
base).
Function: | Modular exponentiation with automatic use of Montgomery exponentiation if the modulus is odd. |
Syntax: | const LINT& LINT::mexp (const LINT& e, const LINT& m); |
Input: | implicit argument (base) |
Return: | pointer to the remainder |
Example: |
|
const LINT& LINT::mexp (const LINT& e, const LINT& m) { int error; if (status == E_LINT_INV) panic (E_LINT_VAL, "mexp", 0, __LINE__);
if (status == E_LINT_INV) panic (E_LINT_VAL, "mexp", 1, __LINE__); if (status == E_LINT_INV) panic (E_LINT_VAL, "mexp", 2, __LINE__); err = mexp_l (n_l, e.n_l, n_l, m.n_l); /* mexp_l() uses mexpk_l() or mexpkm_l() */ switch (error) { case 0: status = E_LINT_OK; break; case E_CLINT_DBZ: panic (E_LINT_DBZ, "mexp", 2, __LINE__); break; default: panic (E_LINT_ERR, "mexp", error, __LINE__); } return *this; }
Function: | Modular exponentiation |
Syntax: |
const LINT& LINT::mexp (USHORT e, const LINT& m); |
Example: |
|
const LINT& LINT::mexp (USHORT e, const LINT& m) { int err; if (status == E_LINT_INV) panic (E_LINT_VAL, "mexp", 0, __LINE__); if (status == E_LINT_INV) panic (E_LINT_VAL, "mexp", 1, __LINE__); err = umexp_l (n_l, e, n_l, m.n_l); switch (err) { // Code as above with mexp (const LINT& e, const LINT& m) } return *this; }
We now present a collection of additional arithmetic and number-theoretic member functions.
Function: | addition |
Syntax: |
const LINT& LINT::add(const LINT& s); |
Input: | implicit argument (summand) |
Return: | pointer to the sum |
Example: |
|
Function: | |
Syntax: |
const LINT& LINT::sub (const LINT& s); |
Input: | implicit argument (minuend) |
Return: | pointer to the difference |
Example: |
|
Function: | |
Syntax: |
const LINT& LINT::mul (const LINT& s); |
Input: | implicit argument (factor) |
Return: | pointer to the product |
Example: |
|
Function: | squaring |
Syntax: | const LINT& LINT::sqr (void); |
Input: | implicit argument (factor) |
Return: | pointer to the implicit argument, which contains the square |
Example: |
Function: | division with remainder |
Syntax: |
const LINT& LINT::divr(const LINT& d, LINT& r); |
Input: | implicit argument (dividend) |
Output | |
Return: | pointer to the implicit argument, which contains the quotient |
Example: |
Function: | residue |
Syntax: |
const LINT& LINT::mod(const LINT& d); |
Input: | implicit argument (dividend) |
Return: | pointer to the implicit argument, which contains the remainder of the division modulo |
Example: |
|
Function: | test for equality modulo |
Syntax: |
int LINT::mequ (const LINT& b, const LINT& m); |
Input: | implicit argument |
Return: | 1 if |
Example: |
Function: | modular addition |
Syntax: | const LINT& LINT::madd(const LINT& s, const LINT& m); |
Input: | implicit argument (summand) |
Return: | pointer to the implicit argument, which contains the sum modulo |
Example: |
Function: | modular subtraction |
Syntax: |
const LINT& LINT::msub(const LINT& s, const LINT& m); |
Input: | implicit argument (minuend) |
Return: | pointer to the implicit argument, which contains the difference modulo |
Example: |
Function: | modular multiplication |
Syntax: | const LINT& LINT::mmul (const LINT& s, const LINT& m); |
Input: | implicit argument (factor) |
Return: | pointer to the implicit argument, which contains the product modulo |
Example: |
Function: | modular squaring |
Syntax: |
const LINT& LINT::msqr (const LINT& m); |
Input: | implicit argument (factor) |
Return: | pointer to the implicit argument, which contains the square modulo |
Example: |
Function: | modular exponentiation with exponent a power of 2 |
Syntax: | const LINT& LINT::mexp2 (USHORT e, const LINT& m); |
Input: | implicit argument (base) |
Return: | pointer to the implicit argument, which contains the power modulo |
Example: |
Function: | modular exponentiation (2k-ary method, Montgomery reduction) |
Syntax: |
const LINT& LINT::mexpkm (const LINT& e, const LINT& m); |
Input: | implicit argument (base) |
Return: | pointer to the implicit argument, which contains the power modulo |
Example: |
Function: | modular exponentiation (25-ary method, Montgomery reduction) |
Syntax: |
const LINT& LINT::mexp5m (const LINT& e, const LINT& m); |
Input: | implicit argument (base) |
Return: | pointer to the implicit argument, which contains the power modulo |
Example: |
Function: | |
Syntax: | const LINT& LINT::shift (int noofbits); |
Input: | implicit argument (multiplicand/dividend) |
Return: | pointer to the implicit argument, which contains the result of the shift operation |
Example: |
|
Function: | test for divisibility by 2 of a |
Syntax: | int LINT::iseven (void); |
Input: | test candidate a as implicit argument |
Return: | 1 if |
Example: |
Function: | set a binary digit of a |
Syntax: | const LINT& LINT::setbit (unsigned int pos); |
Input: | implicit argument a position |
Return: | pointer to |
Example: |
Function: | test a binary digit of a |
Syntax: | int LINT::testbit (unsigned int pos); |
Input: | implicit argument |
Return: | 1 if the bit at position |
Example: |
Function: | set a binary digit of a |
Syntax: | const LINT& LINT::clearbit (unsigned int pos); |
Input: | implicit argument |
Return: | pointer to |
Example: |
In contrast to the arithmetic functions, the following number-theoretic member functions do not overwrite the implicit first argument with the result. The reason for this is that with more complex functions it has been shown in practice not to be practical to overwrite, as is the case with simple arithmetic functions. The results of the following functions are thus returned as values rather than as pointers.
Function: | calculate the greatest integer less than or equal to the base-2 logarithm of a |
Syntax: | unsigned int LINT::ld (void); |
Input: | implicit argument |
Return: | integer part of the base-2 logarithm of |
Example: |
Function: | calculate the greatest common divisor of two |
Syntax: | LINT LINT::gcd (const LINT& b); |
Input: | implicit argument |
Return: | |
Example: |
|
Function: | |
Syntax: | LINT LINT::inv (const LINT& n); |
Input: | implicit argument |
Return: | multiplicative inverse of |
Example: |
|
Function: | calculate the greatest common divisor of |
Syntax: | LINT LINT::xgcd(const LINT& b, LINT& u, int& sign_u, LINT& v, int& sign_v); |
Input: | implicit argument |
Output: | Factor factor |
Return: |
|
Example: |
The friend function chinrem(int noofeq, LINT** coeff)
accepts a vector coeff of pointers to LINT
objects, which are passed as coefficients a1,m1,a2,m2,a3,m3,... of a system of linear congruences with "arbitrarily" many equations x ≡ ai mod mi, i = 1,..., noofeq
(see Appendix B).
Function: | |
Syntax: | int LINT::jacobi (const LINT& b); |
Input: | implicit argument |
Return: | Jacobi symbol of the input values |
Example: |
|
calculation of the integer part of the square root of a | |
Syntax: | LINT LINT::introot (void); |
Input: | implicit argument |
Return: | integer part of the square root of the input value |
Example: |
|
Function: | calculation of the integer part of the bth root of a |
Syntax: | LINT LINT::introot (const USHORT b); |
Input: | implicit argument |
Return: | integer part of the bth root of the input value |
Example: |
|
Function: | calculation of the square root modulo a prime |
Syntax: | LINT LINT::root (const LINT& p); |
Input: | implicit argument |
Return: | square root of |
Example: |
|
Function: | test of whether a |
Syntax: | int LINT::issqr(void);, |
Input: | test candidate |
Return: | square root of |
Example: |
Function: | probabilistic primality test of a |
Syntax: | int LINT::isprime (int nsp, int rnds); |
Input: | test candidate
|
Return: | 1 if |
Example: |
The classes contained in the C++ standard library such as istream
and ostream
are abstractions of input and output devices derived from the base class ios
. The class iostream
is in turn derived from istream
and ostream
, and it enables both writing and reading of its objects.[47] Input and output take place with the help of the insert and extract operators "<<" and ">>" (cf. [Teal], Chapter 8). These arise through overloading the shift operators, for example in the form
ostream& ostream::operator<< (int i); istream& istream::operator>> (int& i);
in which they enable output, respectively input, of integer values through expressions of the form
cout << i; cin >> i;
As special objects of the classes ostream
and istream
, cout
and cin
represent the same abstract files as the objects stdout
and stdin
of the standard C library.
The use of the stream operators "<<" and ">>" for input and output makes it unnecessary to consider particular properties of the hardware in use. In and of itself this is nothing new, for the C function printf()
behaves the same way: A printf()
instruction should always, regardless of platform, lead to the same result. However, above and beyond the altered syntax, which is oriented to the metaphorical image of the insertion of objects into a stream, the advantages of the C++ implementation of streams lie in the strict type checking, which in the case of printf()
is possible only to a limited degree, and in its extensibility. In particular, we make use of the latter property by overloading the insert and extract operators so that they support input and output of LINT
objects. To this end the class LINT
defines the following stream operators:
friend ostream& operator<< (ostream& s, const LINT& ln); friend fstream& operator<< (fstream& s, const LINT& ln); friend ofstream& operator<< (ofstream& s, const LINT& ln); friend fstream& operator>> (fstream& s, LINT& ln); friend ifstream& operator>> (ifstream& s, LINT& ln);
A simple formulation of the overloaded insert operators for the output of LINT
objects might look something like the following:
#include <iostream.h> ostream& operator<< (ostream& s, const LINT& ln) { if (ln.status == E_LINT_INV) LINT::panic (E_LINT_VAL, "ostream operator <<", 0, __LINE__); s << xclint2str (ln.n_l, 16, 0) << endl; s << ld (ln) << " bit" << endl; return s; }
The operator <<thus defined outputs the digits of a LINT
object as hexadecimal values and adds the binary length of the number in a separate line. In the next section we shall consider the possibilities of improving the appearance of the output of LINT
objects with the aid of formatting functions, and we shall also use manipulators to make the output customizable.
In this section we shall make use of the base class ios
of the C++ standard library and of its member functions to define our own LINT
-specific formatting functions for the purpose of controlling the output format of LINT
objects. Furthermore, we shall create manipulators that will make the customization of the output format for LINT
objects as simple as it is for the standard types defined in C++.
The crucial point in the creation of formatted output of LINT
objects is the possibility of setting formatting specifications that will be handled by the insert operator. To this end we shall consider the mechanism provided for the class ios
(for details see [Teal], Chapter 6, and [Pla2], Chapter 6), whose member function xalloc()
in the objects of the classes derived from ios
allocates a status variable of type long
and returns an index to this status variable also of type long
. We store this index in the long
variable flagsindex
. By means of this index the member function ios::iword()
can be used to access reading and writing to the allocated status variable (cf. [Pla2], page 125).
To ensure that this takes place before a LINT
object is output, we define, in the file flintpp.h
, the class LintInit
as follows:
class LintInit { public: LintInit (void); }; LintInit::LintInit (void) { // get index to long status variable in class ios LINT::flagsindex = ios::xalloc(); // set the default status in cout and in cerr cout.iword (LINT::flagsindex) = cerr.iword (LINT::flagsindex) = LINT::lintshowlength|LINT::linthex|LINT::lintshowbase; }
The class LintInit
has as its only element the constructor LintInit::LintInit()
. Furthermore, in the class LINT
we define a member datum setup
of type LintInit
, which is initialized via the constructor LintInit::LintInit()
. The call to xalloc()
takes place within this initialization, and the status variable thereby allocated is given the established standard output format for LINT
objects. In the following we shall show a section of the LINT
class declaration, which contains the declaration of LintInit()
as a friend of LINT
, the declaration of the variables flagsindex
and setup
, and various status values as enum
types:
class LINT { public: // ... enum { lintdec = 0x10, lintoct = 0x20, linthex = 0x40, lintshowbase = 0x80, lintuppercase = 0x100, lintbin = 0x200, lintshowlength = 0x400 }; // ... friend LintInit::LintInit (void);
// ... private: // ... static long flagsindex; static LintInit setup; // ... };
Setting the variable setup
as static
has the effect that this variable exists only once for all LINT
objects and thus the associated constructor LintInit()
is called only once.
We would like now to pause for a moment and consider what all this effort nets us. Setting the output format could just as well be managed via a status variable, which as a member of LINT
would be much simpler to deal with. The decisive advantage of the method that we have chosen is that the output format can be set for each output stream separately and independently of the others (cf. [Pla2], page 125), which could not be accomplished with an internal LINT
status variable. This is done through the power of the class ios
, whose mechanisms we employ for such purposes.
Now that the preliminaries have been taken care of, we can define the status functions as member functions of LINT
. These are displayed in Table 15-1.
We shall consider as an example of the implementation of the status functions the function LINT::setf()
, which returns the current value of the status variable as a long
with reference to an output stream:
long LINT::setf (ostream& s, long flag) { long t = s.iword (flagsindex); // the flags for the basis of the numerical representation // are mutually exclusive if (flag & LINT::lintdec) { s.iword (flagsindex) = (t & ~LINT::linthex & ~LINT::lintoct & ~LINT::lintbin) | LINT::lintdec; flag ^= LINT::lintdec; } if (flag & LINT::linthex) { s.iword (flagsindex) = (t & ~LINT::lintdec & ~LINT::lintoct & ~LINT::lintbin) | LINT::linthex; flag ^= LINT::linthex; } if (flag & LINT::lintoct) {
s.iword (flagsindex) = (t & ~LINT::lintdec & ~LINT::linthex & ~LINT::lintbin) | LINT::lintoct; flag ^= LINT::lintoct; } if (flag & LINT::lintbin) { s.iword (flagsindex) = (t & ~LINT::lintdec & ~LINT::lintoct & ~LINT::linthex) | LINT::lintbin; flag ^= LINT::lintbin; } // all remaining flags are mutually compatible s.iword (flagsindex) |= flag; return t; }
Table 15-1. LINT
status functions and their effects
Explanation | |
---|---|
static long LINT::flags (void); | read the status variable with reference to |
static long LINT::flags (ostream&); | read the status variable with reference to an arbitrary output stream |
static long LINT::setf (long); | set individual bits of the status variable with reference to |
static long LINT::setf (ostream&, long); | set individual bits of the status variable with reference to an arbitrary output stream and return the previous value |
static long LINT::unsetf (long); | restore individual bits of the status variable with reference with reference to |
static long LINT::unsetf (ostream&, long); | restore individual bits of the status variable with reference to an arbitrary output stream and return the previous value |
static long LINT::restoref (long); | set the status variable with reference to |
static long LINT::restoref (ostream&, long); | set the status variable with reference to an arbitrary output stream with a value and return the previous value |
With the help of these and the remaining functions of Table 15-1 we can determine the output formats in the following. First, the standard output format represents the value of a LINT
object as a hexadecimal number in a character string, where the output fills as many lines on the screen as required by the number of digits of the LINT
object. In an additional line the number of digits of the LINT
object is displayed flush left. The following additional modes for output of a LINT
object have been implemented:
Base for the representation of digits
The standard base for the representation of digits of LINT
objects is 16, and for the representation of the length it is 10. This default for LINT
objects can be set for the standard output stream cout
to a specified base by a call to
LINT::setf (LINT::base);
and to
LINT::setf (ostream, LINT::base);
for an arbitrary output stream. Here base
can assume any one of the values
linthex, lintdec, lintoct, lintbin,
which denote the corresponding output format. A call to LINT::setf(lintdec)
, for example sets the output format to decimal digits. The base for the representation of the length can be set with the function
ios::setf (ios::iosbase);
with iosbase = hex, dec, oct
.
Display of the prefix for the numerical representation
The default is for a LINT
object to be displayed with a prefix indicating how it is represented. A call to
LINT::unsetf(LINT::lintshowbase); LINT::unsetf (ostream, LINT::lintshowbase);
changes this setting.
Display of hexadecimal digits in uppercase letters
The default is the display of hexadecimal digits and the display of the prefix 0x
for a hexadecimal representation in lowercase letters a b c d e f
. However, a call to
LINT::setf (LINT::lintuppercase); LINT::setf (ostream, LINT::lintuppercase);
changes this, so that the prefix 0X
and uppercase letters A B C D E F
are displayed.
Display of the length of a LINT
object
The default is the display of the binary length of a LINT
objects. This can be changed by a call to
LINT::unsetf (LINT::lintshowlength); LINT::unsetf (ostream, LINT::lintshowlength);
so that the length is not displayed.
Restoring the status variable for the numerical representation
The status variable for the formatting of a LINT
object can be restored to a previous value oldflags
by a call to the two functions
LINT::unsetf (ostream, LINT::flags(ostream)); LINT::setf (ostream, oldflags);
Calls to these two functions are collected in the overloaded function restoref()
:
LINT::restoref (flag); LINT::restoref (ostream, flag);
Flags can be combined, as in the call
LINT::setf (LINT::bin | LINT::showbase);
This, however, is permitted only for flags that are not mutually exclusive.
The output function that finally generates the requested representational format for LINT
objects is an extension of the operator ostream& operator <<(ostream& s, LINT ln)
already sketched above, which evaluates the status variables of the output stream and generates the appropriate output. For this the operator uses the auxiliary function lint2str()
contained in flintpp.cpp
, which in turn calls the function xclint2str_l()
to represent the numerical value of a LINT
object in a character string:
ostream& operator << (ostream& s, const LINT& ln) { USHORT base = 16; long flags = LINT::flags (s); char* formatted_lint; if (ln.status == E_LINT_INV) LINT::panic (E_LINT_VAL, "ostream operator<<", 0, __LINE__); if (flags & LINT::linthex) { base = 16; } else
{ if (flags & LINT::lintdec) { base = 10; } else { if (flags & LINT::lintoct) { base = 8; } else { if (flags & LINT::lintbin) { base = 2; } } } } if (flags & LINT::lintshowbase) { formatted_lint = lint2str (ln, base, 1); } else { formatted_lint = lint2str (ln, base, 0); } if (flags & LINT::lintuppercase) { strupr_l (formatted_lint); } s << formatted_lint << flush; if (flags & LINT::lintshowlength) { long _flags = s.flags (); // get current status s.setf (ios::dec);// set flag for decimal display s << endl << ld (ln) << " bit" << endl; s.setf (_flags); // restore previous status } return s; }
Building on the previous mechanisms, we would like in this section to obtain more convenient possibilities for controlling the output format for LINT
objects. To this end we use manipulators, which are placed directly into the output stream and thus display the same effects as occur in calling the above status functions. Manipulators are addresses of functions for which there exist special insert operators that on their part accept a pointer to a function as argument. As an example we consider the following function:
ostream& LintHex (ostream& s) { LINT::setf (s, LINT::linthex); return s; }
This function calls the status function setf(s, LINT::linthex)
in the context of the specified output stream ostream& s
and thereby effects the output of LINT
objects as hexadecimal numbers. The name LintHex
of the function without parentheses is viewed as a pointer to the function (cf. [Lipp], page 202) and can be set in an output stream as a manipulator with the help of the insert operator
ostream& ostream::operator<< (ostream& (*pf)(ostream&)) { return (*pf)(*this); }
defined in the class ostream:
LINT a ("0x123456789abcdef0"); cout << LintHex << a; ostream s; s << LintDec << a;
The LINT
manipulator functions operate according to the same pattern as the standard manipulators in the C++ library, for example dec
, hex
, oct
, flush
, and endl
: The insert operator <<
simply calls the manipulator function LintHex()
or LintDec()
at the appropriate place. The manipulators ensure that the status flags belonging to the output streams cout
, respectively s
, are set. The overloaded operator <<
for the output of LINT
objects takes over the representation of the LINT
object a
in the requested form.
The format settings for the output of LINT
objects can all be carried out with the help of the manipulators presented in Table 15-2.
Table 15-2. LINT
manipulators and their effects
Manipulator | Effect: Form of the Output of LINT Values |
| as binary numbers |
| as decimal numbers |
| as hexadecimal numbers |
| as octal numbers |
| with lowercase letters |
| with uppercase letters |
| with prefix for the numerical representation ( |
| without prefix for numerical representation |
| indicating the number of digits |
| without indicating the number of digits |
In addition to the manipulators of Table 15-2, which require no argument, the manipulators
LINT_omanip<int> SetLintFlags (int flags)
and
LINT_omanip<int> ResetLintFlags (int flags)
are available, which can be used as alternatives to the status functions LINT::setf()
and LINT::unsetf()
:
cout << SetLintFlags (LINT::flag) << ...; // turn on cout << ResetLintFlags (LINT::flag) << ...; // turn off
For the implementation of these manipulators the reader is referred to the sources (flintpp.h
and flintpp.cpp
) in connection with the explanation of the template class omanip<T>
in [Pla2], Chapter 10. The LINT
flags are shown once again in Table 15-3.
We shall now clarify the use of the format functions and manipulators by means of the following example:
#include "flintpp.h" #include <iostream.h> #include <iomanip.h> main() { LINT n ("0x0123456789abcdef"); // LINT number with base 16 long deflags = LINT::flags(); // store flags cout << "Default representation: " << n << endl; LINT::setf (LINT::linthex | LINT::lintuppercase); cout << "hex representation with uppercase letters: " << n << endl; cout << LintLwr << "hex representation with lowercase letters: " << n << endl; cout << LintDec << "decimal representation: " << n << endl; cout << LintBin << "binary representation: " << n << endl; cout << LintNobase << LintHex; cout << "representation without prefix: " << n << endl; cerr << "Default representation Stream cerr: " << n << endl; LINT::restoref (deflags); cout << "default representation: " << n << endl; return; }
Functions for the output of LINT
objects to files and functions for reading them are unavoidable for practical applications. The input and output classes of the C++ standard library contain member functions that permit the setting of objects into an input or output stream for file operations, so we are fortunate in that we can use the same syntax as we used above. The operators needed for output to files are similar to those of the last section, where, however, we can do without the formatting.
We define the two operators
friend ofstream& operator<< (ofstream& s, const LINT& ln); friend fstream& operator<< (fstream& s, const LINT& ln);
for output streams of the class ofstream
and for streams of the class fstream
, which supports both directions, that is, both input and output. Since the class ofstream
is derived from the class ostream
, we can use its member function ostream::write()
to write unformatted data to a file. Since only the digits of a LINT
object that are actually used are stored, we can deal sparingly with the storage space of the data medium. Here the USHORT
digits of the LINT
object are actually written as a sequence of UCHAR
values. To ensure that this always occurs in the correct order, independent of the numerical representation scheme of a particular platform, an auxiliary function is defined that writes a USHORT
value as a sequence of two UCHAR
types. This function neutralizes the platform-specific ordering of the digits to base 256 in memory and thereby allows data that were written on one computer type to be read on another that possibly orders the digits of a number differently or perhaps interprets them differently when they are read from mass storage. Relevant examples in this connection are the little-endian and big-endian architectures of various processors, which in the former case order consecutive increasing memory addresses in increasing order, and in the latter case do so in decreasing order.[48]
template <class T> int write_ind_ushort (T& s, clint src) { UCHAR buff[sizeof(clint)]; unsigned i, j; for (i = 0, j = 0; i < sizeof(clint); i++, j = i << 3) { buff[i] = (UCHAR)((src & (0xff << j)) >> j); } s.write (buff, sizeof(clint));
if (!s) { return −1; } else { return 0; } }
The function write_ind_ushort()
returns in the case of error the value −1, while it returns 0
if the operation is successful. It is implemented as a template, so that it can be used with both ofstream
objects and fstream
objects. The function read_ind_ushort()
is created as its counterpart:
template <class T> int read_ind_ushort (T& s, clint *dest) { UCHAR buff[sizeof(clint)]; unsigned i; s.read (buff, sizeof(clint)); if (!s) { return −1; } else { *dest = 0; for (i = 0; i < sizeof(clint); i++) { *dest |= ((clint)buff[i]) << (i << 3); } return 0; } }
The output operators now use this neutral format to write from a LINT
object to a file. To elucidate the situation we shall present the implementation of the operator for the class ofstream
.
ofstream& operator<< (ofstream& s, const LINT& ln) { if (ln.status == E_LINT_INV) LINT::panic (E_LINT_VAL, "ofstream operator<<", 0, __LINE__);
for (int i = 0; i <= DIGITS_L (ln.n_l); i++) { if (write_ind_ushort (s, ln.n_l[i])) { LINT::panic (E_LINT_EOF, "ofstream operator<<", 0, __LINE__); } } return s; }
Before a LINT
object is written to a file, the file must be opened for writing, for which one could use the constructor
ofstream::ofstream (const char *, openmode)
or the member function
ofstream::open (const char *, openmode)
In each case the ios flag ios::binary
must be set, as in the following example:
LINT r ("0x0123456789abcdef"); // ... ofstream fout ("test.io", ios::out | ios::binary); fout << r << r*r; // ... fout.close();
The importation of a LINT
object from a file is effected in a complementary way, with analogous operators, to that of output of a LINT
object to a file.
friend ifstream& operator >> (ifstream& s, LINT& ln); friend fstream& operator >> (fstream& s, LINT& ln);
Both operators first read a single value, which specifies the number of digits of the stored LINT
object. Then the corresponding number of digits are read in. The USHORT
values are read according to the above description under the action of the function read_ind_ushort()
:
ifstream& operator>> (ifstream& s, LINT& ln) { if (read_ind_ushort (s, ln.n_l)) {
LINT::panic (E_LINT_EOF, "ifstream operator>>", 0, __LINE__); } if (DIGITS_L (ln.n_l) < CLINTMAXSHORT) { for (int i = 1; i <= DIGITS_L (ln.n_l); i++) { if (read_ind_ushort (s, &ln.n_l[i])) { LINT::panic (E_LINT_EOF, "ifstream operator>>", 0, __LINE__); } } } // No paranoia! Check the imported value! if (vcheck_l (ln.n_l) == 0) { ln.status = E_LINT_OK; } else { ln.status = E_LINT_INV; } return s; }
To open a file from which the LINT
object is to be read it is again necessary to set the ios flag ios::binary:
LINT r, s; // ... ifstream fin; fin.open ("test.io", ios::in | ios::binary); fin >> r >> s; // ... fin.close();
In the importation of LINT
objects the insert operator >> checks whether the values read represent the numerical representation of a valid LINT
object. If this is not the case, the member datum status
is set to E_LINT_INV
, and the specified target object is thereby marked as "uninitialized." On the next operation on this object the LINT
error handler is invoked, which is what we shall study in more detail in the next chapter.
[47] We use this name of the stream classes as a synonym for the terms now used in the C++ standard library, with which the class names known up to now are prefixed with basic_
. The justification for this comes from the standard library itself, where the class names known hitherto are provided with corresponding typedefs
(cf. [KSch], Chapter 12).
[48] Two bytes Bi and Bi+1 with addresses i and i + 1 are interpreted in the little-endian representation as USHORT
value w = 28 Bi+1 + Bi and in the big-endian representation as w = 28 Bi + Bi+1. The analogous situation holds for the interpretation of ULONG
values.
13.58.44.229