The class MachineInt is intended to help you write functions which
accept arguments whose type is a machine integer (see Why? below).
We recommend that you use MachineInt only to specify function
argument types; other uses may result in disappointing performance.
You cannot perform arithmetic directly with values of type MachineInt.
The primary operations are those for extracting a usable value from a
MachineInt object:
Here is a list of the operations available for a MachineInt value.
Let N, N1, N2 be values of type MachineInt, then
IsZero(N) -- true iff N is zero
IsNegative(N) -- true iff N is negative,
if false the value can be extracted as an unsigned long,
if true the value can be extracted as a signed long
IsSignedLong(N) -- true iff N can be extracted as a signed long
AsUnsignedLong(N) -- extract N as an unsigned long -- see NOTE!
AsSignedLong(N) -- extract N as a signed long -- see NOTE!
abs(N) -- extract the absolute value of N as an unsigned long
sign(N) -- value is -1,0,+1 according as N<0, N==0, N>0
IsOdd(N) -- true iff N is odd
IsEven(N) -- true iff N is even
cmp(N1,N2) -- value is int <0,==0,>0 according as N1<N2, N1==N2, N1>N2
RoundDiv(N1,N2) -- value is N1/N2 rounded to a long, halves round up
SmallPower(b,e) -- value is b^e if it fits into a long, otherwise undefined!!
out << N -- print out N in decimal
You should not call AsUnsignedLong if the value is negative, nor should
you call AsSignedLong if the value is large and positive --- currently, an
error is signalled only if debugging is active. Here's an outline of the
recommended usage:
void SomeProcedure(const MachineInt& N)
{
if (IsNegative(N))
{
long n = AsSignedLong(N);
...
}
else // N is non-negative
{
unsigned long n = AsUnsignedLong(N);
...
}
}
The class MachineInt was created in an attempt to circumvent C++'s
innate automatic conversions between the various integral types; most
particularly the silent conversion of negative signed values into unsigned ones.
Various C++ programming style guides recommend avoiding unsigned integer types. Unfortunately values of such types appear frequently as the result of various counting functions in the STL. So it is somewhat impractical to avoid unsigned values completely.
The class MachineInt employs automatic user-defined conversions to
force all integral values into the largest integral type, viz. long or
unsigned long. An extra "sign bit" inside a MachineInt indicates
whether the value is negative (i.e. must be regarded as a signed long).
Passing an argument as a MachineInt is surely not as fast as using a
built in integral type, but should avoid "nasty surprises" which can
arise with C+'s automatic conversions (e.g. a large unsigned long could
be viewed as a negative long).
On the whole everything is very simple; the hard part was establishing a reasonable design that interoperates with C++'s overload resolution rules.
An object of type MachineInt contains two data fields:
myValue -- the original integer value converted to unsigned long
IamNegative -- true iff the original value was (signed and) negative
The flag IamNegative allows the field myValue to be
interpreted correctly: if IamNegative is true then the correct
value of myValue may be obtained by casting it to a (signed)
long; conversely, if IamNegative is false then the value
of myValue is correct as it stands (i.e. as an unsigned long).
Most functions are so simple that an inline implementation is appropriate.
The implementation of the function abs will work correctly even if
the value being represented is the most negative signed long.
Note that the C++ standard allows the system to produce an error when
negating a long whose value is the most negative representable
value; in contrast, operations on unsigned long values will never
produce errors (except division by zero).
The implementation of cmp is more convoluted that I'd like; it also
requires explicit template parameter specification (otherwise you'd get
infinite recursion).
The implementation of RoundDiv was more difficult than I had expected.
Part of the problem was making sure that needless overflow would never
occur: this was especially relevant in the auxiliary functions
uround_half_up and uround_half_down. It would be nice if a
neater implementation could be achieved -- it seems strange that the
C/C++ standard libraries do not already offer such a function. The
standard C functions lround almost achieves what is needed here, but
there are two significant shortcomings: rounding is always away from zero
(rather than towards +infinity), and there could be loss of accuracy if
the quotient exceeds 1/epsilon. There is also a standard function ldiv
which computes quotient and remainder, but it seems to be faster to compute
the two values explicitly.
My biggest doubt is whether this is really the right way to tackle the
problem of silent automatic conversion between long and unsigned long.
This approach should be considered still "experimental". As a consequence
the rest of CoCoALib has only been partly converted to using MachineInt
in place of long or unsigned long for function parameters.
I do not like using user-defined conversions, but see no alternative here.
Is it better to pass arguments as const MachineInt& or simply as
MachineInt?
Can the implementation of RoundDiv be made neater or faster?
2011