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 the value in N is zero |
IsNegative(N) |
true iff the value in 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 the value in N can be extracted as a signed long |
AsUnsignedLong(N) |
extract the value of N as an unsigned long -- see NOTE! |
AsSignedLong(N) |
extract the value of 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