MachineInteger

© 2007-2010 John Abbott
GNU Free Documentation License, Version 1.2



index page

User documentation for MachineInteger

The class MachineInteger is intended to help you write functions which accept arguments whose type is a machine integer (see Why? below). We recommend that you use MachineInteger only to specify function argument types; other uses may result in disappointing performance.

You cannot perform arithmetic directly with values of type MachineInteger. The primary operations are those for extracting a usable value from a MachineInteger object:

Here is a list of the operations available for a MachineInteger value. Let N, N1, N2 be values of type MachineInteger, 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

NOTE

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 MachineInteger& N)
  {
    if (IsNegative(N))
    {
      long n = AsSignedLong(N);
      ...
    }
    else // N is non-negative
    {
      unsigned long n = AsUnsignedLong(N);
      ...
    }
  }

Why?

The class MachineInteger 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 MachineInteger 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 MachineInteger indicates whether the value is negative (i.e. must be regarded as a signed long).

Passing an argument as a MachineInteger 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).

Maintainer documentation for MachineInteger

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 MachineInteger 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.

Bugs, Shortcomings and other ideas

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 MachineInteger 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 MachineInteger& or simply as MachineInteger?

Can the implementation of RoundDiv be made neater or faster?