#include <Integer.h>
#include <Rational.h>
Most calculations in polymake are made exactly, using rational numbers.
The best implementation known to us which provides arbitrary precision and
high-tuned performance on various computer platforms, is
GMP. On the other hand, we did like very
much the convenient interface of the Rational and Integer classes from
the (in the meanwhile obsolete and no more supported) libg++, which
allowed to use the rational values in expressions in the most natural way, as if
they were built-in numeric types. So we have decided to union the
advantages of both and have written thin wrapper classes mimicking the
old Rational and Integer.
They perform almost no calculations on their own, but delegate the whole hard job to GMP
functions.
In the meanwhile, GMP has got its own C++ wrapper classes. They are implemented differently from our classes: all arithmetic operators are "lazy", returning expression templates instead of ready results. While this technique has proven to improve the performance in longer chained expressions, and is massively used in polymake vector and matrix classes too, we consider it a bit dangerous for the basic numerical type.
In an innocent expression like (a+b)*M, where a and b are rational scalars and M is a rational matrix, the sum a+b would be repeatedly calculated for each element of the resulting matrix. Since such mixed expressions are prevailing in polymake code, we have decided to stay with our own wrappers, relying on the so called return value optimization of the compiler. Future changes in GMP may let us revise this decision.
GMP numbers can be constructed from the most built-in types. They can be converted to each other and to the most built-in types.
Integer, int, or long.
Integer, int, and long.
Rational, Integer, int, and long.
We have intentionally not defined conversions from and to unsigned integral built-in types. It would have exploded the number of all possible function prototypes to an extent that would have been impossible to maintain. The unsigned numbers are almost never used for real arithmetic purposes, in opposite to the GMP numbers, so they shouldn't ever get in contact with each other. In the seldom case you really need it, you should cast the unsigned value to the signed type of the appropriate size.
All arithmetic operators defined for the built-in integral types are available for every combination
of Integer, Rational, int and long operands too.
Surely, the assignment variants are also defined.
The only exception makes up the residual operator %, which is not defined for rational numbers.
Unlike the complex classes in PTL, GMP wrappers don't implement reference counting nor lazy evaluation.
Thus, all operators that seem to create an Integer or Rational object,
are really doing this.
Rational for the sake of congruence to
the standard floor(double) and ceil(double) functions.
std::div_t:
struct Integer::div_t {
Integer quot, rem;
};
GMP also contains much more sofisticated number theoretic algorithms; we haven't needed them yet. They can be added to the interface by demand in a quite straightforward manner.
All comparison operators are defined for any combination of Integer, Rational,
int and long operands.
abs(a)==abs(b)
namespace std_ext { template <> struct hash<Integer>; template <> struct hash<Rational>; }
std_ext::hash_set and
std_ext::hash_map.
ios::basefield flags of the stream; if they are not set,
the base is recognized automatically by the prefix: 0 (octal),
0x (hexadecimal), or default decimal. The input is consumed up to the first
character not matching the chosen numeric base. If no characters were read at all,
the ios::failbit of the stream is set.
ios::basefield and ios::showbase
flags of the stream; default base is 10. Rational numbers are printed in the form
numerator/denominator; they are treated as one field with respect to the
ios::witdh. By integral values the fractional line and denominator are suppressed.
There are not so horribly many ways to play havoc with GMP numbers. They can't get overflowed as long as there is a piece of virtual memory available. The only remaining case is an attempt to divide thru 0. It is caught by GMP internally and mapped to a (platform-specific notion of) divide-by-zero signal. This is the same reaction one would expect from built-in types.
Unfortunately, GMP doesn't always test the denominators of Rationals; hence this test is performed in the wrapper class. Setting the denominator to zero will cause an exception of type
gmp_error : public std::domain_error
to be raised. Probably it would be better to emulate a zero division in this case too; every well-grounded opinion is welcome.
The second source of error conditions is parsing ASCII input. The
handling of invalid data is context-dependent: input stream read operators
report the error via the standard stream state flags, while the string
conversion functions raise the gmp_error condition.