The real return type is a temporary proxy object forwarding all access operations to the internal representation
of the numerator or denominator and taking care of the data consistency.
One of Integer, int, or long.
Any combination of Integer, int, and long.
Any combination of Rational, Integer, int, and long.
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.
Give separate access to the numerator and denominator.
Exception is raised by the attempt to assign 0 to the denominator.
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.
Indeed, 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.
Find an integral number nearest to the given rational. The return
type is declared Rational for the sake of congruence to
the standard floor(double) and ceil(double) functions.
Integer div_exact(const Integer& a, const Integer& b);
Compute a/b efficiently, provided that b is a multiple of a.
Otherwise result is undefined.
Integer::div_t std::div(const Integer& a, const Integer& b);
Compute a/b and a mod b simultaneously. The result type
is built analogously to std::div_t :
Compute the least common multiple of two integral numbers.
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.
Parse an ASCII representation and assign a converted value. The denominator
must be separated by '/'; lacking denominator is assumed to be equal to 1.
Numbers may start with 0 or 0x,
which assumes octal or hexadecimal numeric base instead of the default decimal.
Read a value from an input stream. The numeric base of the input ASCII representation is
chosen according to the std::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 std::ios::failbit of the stream is set.
Print the ASCII representation to the output stream. The numeric base and appearance
are chosen according to the std::ios::basefield and std::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
std::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
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.