Constant gmp_mpfr_sys::C::GMP::Internals [−][src]
pub const Internals: ();
This constant is a place-holder for documentation; do not use it in code.
Next: Contributors, Previous: Algorithms, Up: Top [Index]
16 Internals
This chapter is provided only for informational purposes and the various internals described here may change in future GMP releases. Applications expecting to be compatible with future releases should use only the documented interfaces described in previous chapters.
• Integer Internals | ||
• Rational Internals | ||
• Float Internals | ||
• Raw Output Internals | ||
• C++ Interface Internals |
Next: Rational Internals, Previous: Internals, Up: Internals [Index]
16.1 Integer Internals
mpz_t
variables represent integers using sign and magnitude, in space
dynamically allocated and reallocated. The fields are as follows.
_mp_size
The number of limbs, or the negative of that when representing a negative integer. Zero is represented by
_mp_size
set to zero, in which case the_mp_d
data is undefined._mp_d
A pointer to an array of limbs which is the magnitude. These are stored “little endian” as per the
mpn
functions, so_mp_d[0]
is the least significant limb and_mp_d[ABS(_mp_size)-1]
is the most significant. Whenever_mp_size
is non-zero, the most significant limb is non-zero.Currently there’s always at least one readable limb, so for instance
mpz_get_ui
can fetch_mp_d[0]
unconditionally (though its value is undefined if_mp_size
is zero)._mp_alloc
_mp_alloc
is the number of limbs currently allocated at_mp_d
, and normally_mp_alloc >= ABS(_mp_size)
. When anmpz
routine is about to (or might be about to) increase_mp_size
, it checks_mp_alloc
to see whether there’s enough space, and reallocates if not.MPZ_REALLOC
is generally used for this.mpz_t
variables initialised with thempz_roinit_n
function or theMPZ_ROINIT_N
macro have_mp_alloc = 0
but can have a non-zero_mp_size
. They can only be used as read-only constants. See Integer Special Functions for details.
The various bitwise logical functions like mpz_and
behave as if
negative values were twos complement. But sign and magnitude is always used
internally, and necessary adjustments are made during the calculations.
Sometimes this isn’t pretty, but sign and magnitude are best for other
routines.
Some internal temporary variables are setup with MPZ_TMP_INIT
and these
have _mp_d
space obtained from TMP_ALLOC
rather than the memory
allocation functions. Care is taken to ensure that these are big enough that
no reallocation is necessary (since it would have unpredictable consequences).
_mp_size
and _mp_alloc
are int
, although mp_size_t
is usually a long
. This is done to make the fields just 32 bits on
some 64 bits systems, thereby saving a few bytes of data space but still
providing plenty of range.
Next: Float Internals, Previous: Integer Internals, Up: Internals [Index]
16.2 Rational Internals
mpq_t
variables represent rationals using an mpz_t
numerator and
denominator (see Integer Internals).
The canonical form adopted is denominator positive (and non-zero), no common factors between numerator and denominator, and zero uniquely represented as 0/1.
It’s believed that casting out common factors at each stage of a calculation
is best in general. A GCD is an O(N^2) operation so it’s better to do
a few small ones immediately than to delay and have to do a big one later.
Knowing the numerator and denominator have no common factors can be used for
example in mpq_mul
to make only two cross GCDs necessary, not four.
This general approach to common factors is badly sub-optimal in the presence
of simple factorizations or little prospect for cancellation, but GMP has no
way to know when this will occur. As per Efficiency, that’s left to
applications. The mpq_t
framework might still suit, with
mpq_numref
and mpq_denref
for direct access to the numerator and
denominator, or of course mpz_t
variables can be used directly.
Next: Raw Output Internals, Previous: Rational Internals, Up: Internals [Index]
16.3 Float Internals
Efficient calculation is the primary aim of GMP floats and the use of whole limbs and simple rounding facilitates this.
mpf_t
floats have a variable precision mantissa and a single machine
word signed exponent. The mantissa is represented using sign and magnitude.
most least significant significant limb limb _mp_d |---- _mp_exp ---> | _____ _____ _____ _____ _____ |_____|_____|_____|_____|_____| . <------------ radix point <-------- _mp_size --------->
The fields are as follows.
_mp_size
The number of limbs currently in use, or the negative of that when representing a negative value. Zero is represented by
_mp_size
and_mp_exp
both set to zero, and in that case the_mp_d
data is unused. (In the future_mp_exp
might be undefined when representing zero.)_mp_prec
The precision of the mantissa, in limbs. In any calculation the aim is to produce
_mp_prec
limbs of result (the most significant being non-zero)._mp_d
A pointer to the array of limbs which is the absolute value of the mantissa. These are stored “little endian” as per the
mpn
functions, so_mp_d[0]
is the least significant limb and_mp_d[ABS(_mp_size)-1]
the most significant.The most significant limb is always non-zero, but there are no other restrictions on its value, in particular the highest 1 bit can be anywhere within the limb.
_mp_prec+1
limbs are allocated to_mp_d
, the extra limb being for convenience (see below). There are no reallocations during a calculation, only in a change of precision withmpf_set_prec
._mp_exp
The exponent, in limbs, determining the location of the implied radix point. Zero means the radix point is just above the most significant limb. Positive values mean a radix point offset towards the lower limbs and hence a value >= 1, as for example in the diagram above. Negative exponents mean a radix point further above the highest limb.
Naturally the exponent can be any value, it doesn’t have to fall within the limbs as the diagram shows, it can be a long way above or a long way below. Limbs other than those included in the
{_mp_d,_mp_size}
data are treated as zero.
The _mp_size
and _mp_prec
fields are int
, although the
mp_size_t
type is usually a long
. The _mp_exp
field is
usually long
. This is done to make some fields just 32 bits on some 64
bits systems, thereby saving a few bytes of data space but still providing
plenty of precision and a very large range.
The following various points should be noted.
- Low Zeros
The least significant limbs
_mp_d[0]
etc can be zero, though such low zeros can always be ignored. Routines likely to produce low zeros check and avoid them to save time in subsequent calculations, but for most routines they’re quite unlikely and aren’t checked.- Mantissa Size Range
The
_mp_size
count of limbs in use can be less than_mp_prec
if the value can be represented in less. This means low precision values or small integers stored in a high precisionmpf_t
can still be operated on efficiently._mp_size
can also be greater than_mp_prec
. Firstly a value is allowed to use all of the_mp_prec+1
limbs available at_mp_d
, and secondly whenmpf_set_prec_raw
lowers_mp_prec
it leaves_mp_size
unchanged and so the size can be arbitrarily bigger than_mp_prec
.- Rounding
All rounding is done on limb boundaries. Calculating
_mp_prec
limbs with the high non-zero will ensure the application requested minimum precision is obtained.The use of simple “trunc” rounding towards zero is efficient, since there’s no need to examine extra limbs and increment or decrement.
- Bit Shifts
Since the exponent is in limbs, there are no bit shifts in basic operations like
mpf_add
andmpf_mul
. When differing exponents are encountered all that’s needed is to adjust pointers to line up the relevant limbs.Of course
mpf_mul_2exp
andmpf_div_2exp
will require bit shifts, but the choice is between an exponent in limbs which requires shifts there, or one in bits which requires them almost everywhere else.- Use of
_mp_prec+1
Limbs The extra limb on
_mp_d
(_mp_prec+1
rather than just_mp_prec
) helps when anmpf
routine might get a carry from its operation.mpf_add
for instance will do anmpn_add
of_mp_prec
limbs. If there’s no carry then that’s the result, but if there is a carry then it’s stored in the extra limb of space and_mp_size
becomes_mp_prec+1
.Whenever
_mp_prec+1
limbs are held in a variable, the low limb is not needed for the intended precision, only the_mp_prec
high limbs. But zeroing it out or moving the rest down is unnecessary. Subsequent routines reading the value will simply take the high limbs they need, and this will be_mp_prec
if their target has that same precision. This is no more than a pointer adjustment, and must be checked anyway since the destination precision can be different from the sources.Copy functions like
mpf_set
will retain a full_mp_prec+1
limbs if available. This ensures that a variable which has_mp_size
equal to_mp_prec+1
will get its full exact value copied. Strictly speaking this is unnecessary since only_mp_prec
limbs are needed for the application’s requested precision, but it’s considered that anmpf_set
from one variable into another of the same precision ought to produce an exact copy.- Application Precisions
__GMPF_BITS_TO_PREC
converts an application requested precision to an_mp_prec
. The value in bits is rounded up to a whole limb then an extra limb is added since the most significant limb of_mp_d
is only non-zero and therefore might contain only one bit.__GMPF_PREC_TO_BITS
does the reverse conversion, and removes the extra limb from_mp_prec
before converting to bits. The net effect of reading back withmpf_get_prec
is simply the precision rounded up to a multiple ofmp_bits_per_limb
.Note that the extra limb added here for the high only being non-zero is in addition to the extra limb allocated to
_mp_d
. For example with a 32-bit limb, an application request for 250 bits will be rounded up to 8 limbs, then an extra added for the high being only non-zero, giving an_mp_prec
of 9._mp_d
then gets 10 limbs allocated. Reading back withmpf_get_prec
will take_mp_prec
subtract 1 limb and multiply by 32, giving 256 bits.Strictly speaking, the fact the high limb has at least one bit means that a float with, say, 3 limbs of 32-bits each will be holding at least 65 bits, but for the purposes of
mpf_t
it’s considered simply to be 64 bits, a nice multiple of the limb size.
Next: C++ Interface Internals, Previous: Float Internals, Up: Internals [Index]
16.4 Raw Output Internals
mpz_out_raw
uses the following format.
+------+------------------------+ | size | data bytes | +------+------------------------+
The size is 4 bytes written most significant byte first, being the number of subsequent data bytes, or the twos complement negative of that when a negative integer is represented. The data bytes are the absolute value of the integer, written most significant byte first.
The most significant data byte is always non-zero, so the output is the same on all systems, irrespective of limb size.
In GMP 1, leading zero bytes were written to pad the data bytes to a multiple
of the limb size. mpz_inp_raw
will still accept this, for
compatibility.
The use of “big endian” for both the size and data fields is deliberate, it
makes the data easy to read in a hex dump of a file. Unfortunately it also
means that the limb data must be reversed when reading or writing, so neither
a big endian nor little endian system can just read and write _mp_d
.
Previous: Raw Output Internals, Up: Internals [Index]
16.5 C++ Interface Internals
A system of expression templates is used to ensure something like a=b+c
turns into a simple call to mpz_add
etc. For mpf_class
the scheme also ensures the precision of the final
destination is used for any temporaries within a statement like
f=w*x+y*z
. These are important features which a naive implementation
cannot provide.
A simplified description of the scheme follows. The true scheme is complicated by the fact that expressions have different return types. For detailed information, refer to the source code.
To perform an operation, say, addition, we first define a “function object” evaluating it,
struct __gmp_binary_plus { static void eval(mpf_t f, const mpf_t g, const mpf_t h) { mpf_add(f, g, h); } };
And an “additive expression” object,
__gmp_expr<__gmp_binary_expr<mpf_class, mpf_class, __gmp_binary_plus> > operator+(const mpf_class &f, const mpf_class &g) { return __gmp_expr <__gmp_binary_expr<mpf_class, mpf_class, __gmp_binary_plus> >(f, g); }
The seemingly redundant __gmp_expr<__gmp_binary_expr<…>>
is used to
encapsulate any possible kind of expression into a single template type. In
fact even mpf_class
etc are typedef
specializations of
__gmp_expr
.
Next we define assignment of __gmp_expr
to mpf_class
.
template <class T> mpf_class & mpf_class::operator=(const __gmp_expr<T> &expr) { expr.eval(this->get_mpf_t(), this->precision()); return *this; } template <class Op> void __gmp_expr<__gmp_binary_expr<mpf_class, mpf_class, Op> >::eval (mpf_t f, mp_bitcnt_t precision) { Op::eval(f, expr.val1.get_mpf_t(), expr.val2.get_mpf_t()); }
where expr.val1
and expr.val2
are references to the expression’s
operands (here expr
is the __gmp_binary_expr
stored within the
__gmp_expr
).
This way, the expression is actually evaluated only at the time of assignment,
when the required precision (that of f
) is known. Furthermore the
target mpf_t
is now available, thus we can call mpf_add
directly
with f
as the output argument.
Compound expressions are handled by defining operators taking subexpressions as their arguments, like this:
template <class T, class U> __gmp_expr <__gmp_binary_expr<__gmp_expr<T>, __gmp_expr<U>, __gmp_binary_plus> > operator+(const __gmp_expr<T> &expr1, const __gmp_expr<U> &expr2) { return __gmp_expr <__gmp_binary_expr<__gmp_expr<T>, __gmp_expr<U>, __gmp_binary_plus> > (expr1, expr2); }
And the corresponding specializations of __gmp_expr::eval
:
template <class T, class U, class Op> void __gmp_expr <__gmp_binary_expr<__gmp_expr<T>, __gmp_expr<U>, Op> >::eval (mpf_t f, mp_bitcnt_t precision) { // declare two temporaries mpf_class temp1(expr.val1, precision), temp2(expr.val2, precision); Op::eval(f, temp1.get_mpf_t(), temp2.get_mpf_t()); }
The expression is thus recursively evaluated to any level of complexity and
all subexpressions are evaluated to the precision of f
.
Previous: Raw Output Internals, Up: Internals [Index]