The file ring.H
introduces several classes used for representing
rings and their elements. A normal user of the CoCoA library will use
principally the classes ring
and RingElem
: an object of type
ring
represents a mathematical ring with unity, and objects of
type RingElem
represent values from some ring. To make the
documentation more manageable it has been split into two: this file
describes operations on a RingElem
, whereas a separate file
describes the operations directly applicable to ring
s.
Documentation about the creation and use of homomorphisms between
rings can be found in RingHom
.
A variable of type RingElem
comprises two parts: the ring to which the
value belongs, and the value itself. An important point to note is that
the ring cannot be changed, but the value part can (if C++ const rules
permit). This means that you must specify the ring to which the
RingElem
belongs whenever you create one.
Here are a some examples of valid ways of creating a RingElem (see below for a full list of constructors).
VALID CODE | Comment |
---|---|
RingElem a(R); |
value is 0 |
RingElem a(R,3); |
value is 3 |
RingElem a(R, BigRat(3,7)); |
value is 3/7 (or error) |
RingElem a = one(RingZZ()); |
value is 1 in RingZZ() |
RingElem a = b; |
a copy of b : same value & same ring |
Here are a some examples of invalid ways of creating a RingElem
; note
in particular that integer and rational values are not automatically mapped
into the obvious rings, instead the ring to map into must be given explicitly.
INVALID CODE | Comment |
---|---|
RingElem a = 2; |
owning ring is not specified |
RingElem a(2); |
owning ring is not specified |
RingElem a = BigRat(3,7); |
owning ring is not specified |
RingElem
s are designed to be easy and safe to use; the various checks
do incur a certain run-time overhead, so a faster alternative is offered
(see below in the section Fast and Ugly Code). Arithmetic operations
between RingElem
s will fail if they do not belong to the same ring
(the exception has code ERR::MixedRings
).
You can get the zero and one of a ring directly using the following:
zero(R) |
the zero element of R |
one(R) |
the one element of R |
Let R
be a ring
,
n
an integer
q
a BigRat
(rational)
RingElem r(R); |
an element of R , initially zero |
RingElem r(R, n); |
an element of R , initially the image of n |
RingElem r(R, q); |
an element of R , initially the image of q (or error) |
RingElem r(R, s); |
an element of R , initially the value of symbol s |
RingElem r(r2); |
a copy of r2 , element of the same ring |
RingElem r = r2; |
(alternative syntax, discouraged) |
Construction from a rational may fail if the denominator is a zero divisor
in the ring; if it does fail then an exception is thrown (with code
ERR::DivByZero
).
Note: there is no constructor RingElem(R, r2)
, if you want to map a
RingElem
from one ring to another then you must apply a homomorphism (see RingHom
).
CoCoALib offers functions for querying various properties of RingElem
s,
and about relationships between RingElems
.
Let r1
and r2
be a (possibly const) RingElem
s, and
let N
be a variable of type BigInt
, and q
a variable of type BigRat
owner(r1) |
the ring to which r1 is associated |
IsZero(r1) |
true iff r1 is zero |
IsOne(r1) |
true iff r1 is one |
IsMinusOne(r1) |
true iff -r1 is one |
IsInteger(N, r1) |
true iff r1 is the image of an integer (if true, a preimage is placed in N ) |
IsRational(q, r1) |
true iff r1 is the image of a rational (if true, a preimage is placed in q ) |
IsDouble(d, r1) |
true iff r1 is the image of a rational whose approx is put into d (false if overflow) |
IsUnit(r1) |
true iff r1 has a multiplicative inverse |
IsDivisible(r1, r2) |
true iff r1 is divisible by r2 |
raw(r1) |
(see below, section "Fast and Ugly Code") |
R->myIsPrintAtom(r1) |
true iff r1 does not need brackets when a num or denom of a fraction |
R->myIsPrintedWithMinus(r1) |
true iff the printed form of r1 begins with a minus sign |
Note that IsDivisible
tests divisibility in the ring containing the
values: so 1 is not divisible by 2 in RingZZ
, but their images in
RingQQ
are divisible.
Arithmetic operations between RingElem
s will fail if they do not belong
to the same ring (the exception has code ERR::MixedRings
). You may
perform arithmetic between a RingElem
and a machine integer, a BigInt value
or a BigRat value -- the integer/rational is automatically mapped into the
same ring as the RingElem
.
Let r
be a non-const RingElem
,
and r1
, r2
be potentially const RingElem
s.
Assume they are all associated to the same ring.
Then the operations available are: (meanings are obvious)
cout << r1
-- output value of r1
(decimal only, see notes)
r1 == r2
-- equality test
r1 != r2
-- not-equal test
r = r1
-- assignment
-r1
-- negation (unary minus)
r1 + r2
-- sum
r1 - r2
-- difference
r1 * r2
-- product
r1 / r2
-- quotient, division must be exact (see IsDivisible)
r += r1
-- equivalent to r = r + r1
r -= r1
-- equivalent to r = r - r1
r *= r1
-- equivalent to r = r * r1
r /= r1
-- equivalent to r = r / r1
gcd(r1, r2)
-- an associate of the gcd (error if ring is not gcd domain)
lcm(r1, r2)
-- an associate of the lcm (error if ring is not gcd domain)
power(r1, n)
-- n
-th power of r1
; n
may be long
or BigInt
GcdQuot(&gcd, &xquot, &yquot, x, y)
-- procedure computes gcd and x/gcd and y/gcd
Attempting to compute a gcd
or lcm
in a ring which not an
effective GCD domain will produce an exception with code
ERR::NotGcdDom
.
If the ring is an ordered domain then these functions may also be used.
Comparison operations between RingElem
s will fail if they do not belong
to the same ring (the exception has code ERR::MixedRings
). You may
perform comparisons between a RingElem
and a machine integer, a BigInt
value
or a BigRat
value -- the integer/rational is automatically mapped into the
same ring as the RingElem
.
Let r1
and r2
belong to an ordered ring. Trying to use any
of these functions on elements belonging to a ring which is not ordered
will produce an exception with code ERR::NotOrdDomain
.
sign(r1)
-- value is -1, 0 or +1 according as r1
is negative, zero or positive
abs(r1)
-- absolute value of r1
floor(r1)
-- greatest integer <= r1
ceil(r1)
-- least integer >= r1
NearestInteger(r1)
-- returns nearest integer (BigInt
) to r1
, halves round towards +infinity.
cmp(r1, r2)
-- returns a value <0, =0, >0 according as r1-r2
is <0, =0, >0
CmpDouble(r1, z)
-- compare a ring elem with a double
, result is <0, =0, >0 according as r1-z
is <0, =0, >0
r1 < r2
-- standard inequalities
r1 > r2
-- ...
r1 <= r2
-- ...
r1 >= r2
-- ...
In addition to the standard ring operations, elements of a PolyRing
may
used in other functions.
Let P
denote a polynomial ring.
Let f
denote a non-const element of P
.
Let f1
, f2
denote const elements of P
.
IsMonomial(f)
; -- true iff f
is non zero and of the form coeff*pp
IsConstant(f)
; -- true iff f
is "constant",
i.e. the image of an element of the coeff ring.
IsIndet(f)
; -- equivalent to f
== x[i] for some index i
IsIndet(index, f)
; -- equivalent to f
== x[i]; and sets index = i
IsIrred(f)
-- true iff f
is irreducible in P
owner(f1)
-- the owner of f
as a ring
; NB to get the owner as
a PolyRing
use AsPolyRing(owner(f1))
.
NumTerms(f1)
-- the number of terms in f1
.
StdDeg(f1)
-- the standard degree of f1
(deg(x[i])=1); error if f1
is 0.
deg(f1)
-- same as StdDeg(f1)
.
MaxExponent(f1, var)
-- maximum exponent of var
-th indet in f1
where var
is the
index of the indet in P (result is of type long).
LC(f1)
-- the leading coeff of f1
; it is an element of CoeffRing(P).
content(f1)
-- gcd of the coeffs of f1
; it is an element of CoeffRing(P).
(content of zero poly is zero; if coeffs are in a field the content is 0 or 1)
CommonDenom(f1)
-- the simplest common denominator for the coeffs of f1
;
it is an element of BaseRing(AsFractionField(CoeffRing(P))).
(CoeffRing must be a FractionField of a GCD domain, otherwise error)
ClearDenom(f1)
-- f1
*CommonDenom(f1)
(same restrictions as above)
deriv(f1, var)
-- formal derivative of f1
wrt. indet having index var
.
deriv(f1, x)
-- derivative of f1
w.r.t. x
, x
must be an indeterminate
NOTE: to compute the weighted degree of a polynomial use the function
wdeg
defined for RingElem
of a SparsePolyRing
(see below).
In addition to the standard ring
and PolyRing
operations, elements of a
SparsePolyRing
may used in other functions.
Let P denote a SparsePolyRing
.
Let f denote a non-const element of P.
Let f1, f2 denote const elements of P.
Let expv be a vector<long>
of size equal to the number of indeterminates.
owner(f1)
-- the owner of f1
as a ring; NB to get the owner as
a SparsePolyRing
use AsSparsePolyRing(owner(f1))
.
NumTerms(f1)
-- the number of terms in f1
with non-zero coefficient.
LPP(f1)
-- the leading PP of f1
; it is an element of PPM(P).
Also known as LT(f) or in(f)
wdeg(f1)
-- the weighted degree of the leading PP of f1
(see [KR] Sec.4.3); error if f1
is 0.
NB result is of type CoCoA::degree
(see degree
).
(contrast with StdDeg(f1)
and deg(f1)
defined for general PolyRing
)
CmpWDeg(f1, f2)
-- compare the weighted degrees of the LPPs of f1
and f2
;
result is <0 =0 >0 according as deg(f1
) < = > deg(f2
)
IsHomog(f)
-- says whether f
is homogeneous wrt weighted degree.
homog(f, h)
-- returns f
homogenized with indet h
(requires
GrDim
=1 and wdeg(h)
=1)
NR(f, v)
-- returns the (normal) remainder of the Division
Algorithm by v
. If v
is a GBasis this is
the Normal Form
monomial(P,c,pp)
-- returns c
*pp
as an element of P
where
c
is in CoeffRing(P)
and pp
is in PPM(P)
.
monomial(P,c,expv)
-- returns c*x[0]^exps[0]*x[1]^exps[1]*...
where
c
is in CoeffRing(P)
, and x[i] are the indets of P
.
NB For running through the summands (monomial/terms) of a polynomial
use SparsePolyIter
s (see SparsePolyRing
).
We have still doubts on the usefullness of these two functions:
CmpWDegPartial(f1, f2, i)
-- compare the first i
weighted
degrees of the LPPs of f1
and f2
;
result is <0 =0 >0 according as deg(f1)
< = > deg(f2)
IsHomogPartial(f,i)
-- says whether f
is homogeneous wrt the
first i
components of the weighted degree
Use the following two functions with great care: they throw an error
if the PPOrdering
is not respected: (the coefficient c may be 0)
PushFront(f, c, expv)
-- add to f
the term c
*t
where t is the PP with exponent vector expv
,
and assuming that t > LPP(f)
or f
==0
PushBack(f, c, expv)
-- add to f
the term c
*t
where t is the PP with exponent vector expv
,
and assuming that t < t' for all t' appearing in f
.
The corresponding member functions myPushFront/myPushBack
will not
check the validity of these assumpions: they should have a
CoCoA_ASSERT
to check in DEBUG mode.
Operations combining elements of different rings will cause a run-time error.
In all functions involving two RingElem
s either r1
or r2
may be replaced by a machine integer, or by a big integer (an element
of the class BigInt
). The integer value is automatically mapped into
the ring owning the RingElem
in the same expression.
The exponent n
in the power function may be zero or negative, but a
run-time error will be signalled if one attempts to compute a negative
power of a non-invertible element or if one attempts to raise zero to
the power zero. NB You cannot use ^
to compute powers -- see Bugs section.
An attempt to perform an inexact division or to compute a GCD not in a GCD domain will produce a run-time error.
The printing of ring elements is always in decimal regardless of the
ostream
settings (this is supposed to be a feature rather than a bug).
At this point, if you are new to CoCoALib, you should probably look
at some of the example programs in the examples/
directory.
One would normally expect to use the type const RingElem&
for
read-only arguments which are RingElem
s, and RingElem&
for
read-write arguments. Unfortunately, doing so would lead to problems
with the CoCoA library. INSTEAD you should use the types:
ConstRefRingElem x |
for read-only arguments: morally const RingElem& x |
RefRingElem x |
for read-write arguments: morally RingElem& x |
If you are curious to know why this non-standard quirk has to be used, read on.
Internally, ring element values are really smart pointers to the true
value. Now the const
keyword in C++ when applied to a pointer
makes the pointer const while the pointed-to value remains alterable
-- this is not the behaviour we want for const RingElem&
. To get
the desired behaviour we have to use another type: the type we have
called ConstRefRingElem
.
You might wonder why Ref
appears in the names RefRingElem
and
ConstRefRingElem
. It indicates that you are working with a reference
to a value which is owned by another object (e.g. a variable of type
RingElem
, or maybe a matrix
).
The rest of this section is for more advanced use of ring
s and
RingElem
s (e.g. by CoCoA library contributors). If you are new to
CoCoA, you need not read beyond here.
WE DO NOT RECOMMEND that you use what is described in this section. If you are curious to know a bit more how rings are implemented, you might find this section informative.
RingElem
s are designed to be easy and pleasant to use, but this
convenience has a price: a run-time performance penalty (and a memory
space penalty too).
Both penalities may be avoided by using raw values but at a
considerable loss of programming convenience and safety. You should
consider using raw values only if you are desperate for speed; even
so, performance gains may be only marginal except perhaps for
operations on elements of a simple ring (e.g. a small finite field).
A RingElem
object contains within itself an indication of the owning
ring, and a raw value which is a pointer to where the real
representation of the ring element value lies. These raw values may be
accessed via the raw function. They may be combined arithmetically by
calling member functions of the owning ring. For instance, if x,y,z are
all RingElem objects all BELONGING TO EXACTLY THE SAME RING then we can
achieve
x = y+z;
slightly faster by calling
owner(x)->my.Add(raw(x), raw(y), raw(z));
It should now be clear that the syntax involved is cumbersome and
somewhat obscure. For the future maintainability of the code the
simpler x = y+z;
has many advantages. Furthermore, should x,y,z
somehow happen not all to lie in the same ring then x = y+z;
will act
in a reasonable way, whereas the supposedly faster call will likely lead
to many hours of debugging grief. The member functions for arithmetic
(e.g. myAdd
) DO NOT PERFORM sanity checks on their arguments:
e.g. attempting to divide by zero could well crash the program.
If you use a debugging version of the CoCoA Library then some member functions do use assertions to check their arguments. This is useful during development, but must not be relied upon since the checks are absent from the non-debugging version of the CoCoA Library. See the file config.txt for more information.
This fast, ugly, unsafe way of programming is made available for those who desperately need the speed. If you're not desperate, don't use it!
Read the section Fast and Ugly Code before using any of these!
Let r
be a non-const raw value, and r1
, r2
potentially
const raw values. Assume they are all owned by the ring R
.
Then the functions available are:
R->myNew()
-- construct a new element of R, value=0
R->myNew(n)
-- construct a new element of R, value=n
R->myNew(N)
-- construct a new element of R, value=N
R->myNew(r1)
-- construct a new element of R, value=r1
R->myDelete(r)
-- destroy r, element of R (frees resources)
R->mySwap(r, s)
-- swaps the two values (s is non-const raw value)
R->myAssignZero(r)
-- r = 0
R->myAssign(r, r1)
-- r = r1
R->myAssign(r, n)
-- r = n (n is a long)
R->myAssign(r, N)
-- r = n (N is a BigInt
)
R->myNegate(r, r1)
-- r = -r1
R->myAdd(r, r1, r2)
-- r = r1+r2
R->mySub(r, r1, r2)
-- r = r1-r2
R->myMul(r, r1, r2)
-- r = r1*r2
R->myDiv(r, r1, r2)
-- r = r1/r2 (division must be exact)
R->myIsDivisible(r, r1, r2)
-- r = r1/r2, and returns true iff division was exact
R->myIsUnit(r1)
-- IsUnit(r1)
R->myGcd(r, r1, r2)
-- r = gcd(r1, r2)
R->myLcm(r, r1, r2)
-- r = lcm(r1, r2)
R->myPower(r, r1, n)
-- r = power(r1, n) BUT n MUST be non-negative!!
R->myIsZero(r1)
-- r1 == 0
R->myIsZeroAddMul(r, r1, r2)
-- ((r += r1*r2) == 0)
R->myIsEqual(r1, r2)
-- r1 == r2
R->myOutput(out, r1)
-- out << r1
R->mySequentialPower(r, r1, n)
-- normally it is better to use R->myPower(r, r1, n)
R->myBinaryPower(r, r1, n)
-- normally it is better to use R->myPower(r, r1, n)
(NB consider consulting also QuotientRing
, FractionField
and PolyRing
)
The design underlying rings and their elements is more complex than I would have liked, but it is not as complex as the source code may make it appear. The guiding principles are that the implementation should be flexible and easy/pleasant to use while offering a good degree of safety; extreme speed of execution was not a goal (as it is usually contrary to good flexibility) though an interface offering slightly better run-time efficiency remains.
Regarding flexibility: in CoCoALib we want to handle polynomials whose
coefficients reside in (almost) any commutative ring. Furthermore, the
actual rings to be used will be decided at run-time, and cannot
restricted to a given finite set. We have chosen to use C++ inheritance
to achieve the implementation: the abstract class RingBase
defines the
interface that every concrete ring class must offer.
Regarding ease of use: since C++ allows the common arithmetic operators
to be overloaded, it is essential that these work as expected for
elements of arbitrary rings -- with the caveat that /
means exact
division, being the only reasonable interpretation. Due to problems of
ambiguity arithmetic between elements of different rings is forbidden:
e.g. let f in Q[x,y] and g in Z[y,x], where should f+g reside?
The classes in the file ring.H are closely interrelated, and there is no obvious starting point for describing them -- you may find that you need to read the following more than once to comprehend it. Here is a list of the classes:
ring |
value represents a ring; it is a smart pointer |
RingBase |
abstract class defining what a ring is |
RingElem |
value represents an element of a ring |
RefRingElem |
reference to a RingElem |
ConstRefRingElem |
const-reference to a RingElem |
RingElemConstRawPtr |
raw pointer to a const ring value |
RingElemRawPtr |
raw pointer to a ring value |
For the first two see ring
.
The classes RingElem
, RefRingElem
and ConstRefRingElem
are
related by inheritance: they are very similar but differ in important
ways. The three classes are used for representing values in rings
(e.g 1 as an element Z, or 1 as an element of Q[x], etc). In each
case an object of that C++ type comprises two components: one is the
identity of ring to which the element belongs, and the other is the
value in that ring (the value is stored in a format that only the
owning ring can comprehend). All operations on ring elements are
effected by member functions of the ring to which the value belongs.
The difference between ConstRefRingElem
and RefRingElem
is
quite simple: you cannot change the value of a ConstRefRingElem
,
for instance you cannot assign to it, while you are free to change the
value of a RefRingElem
.
The difference between a RefRingElem
and a RingElem
is also
quite simple, but possibly harder to grasp. A variable of type
RingElem
is the owner of the value that it represents: that value
will be destroyed when the variable passes out of scope. In contrast,
a variable of type RefRingElem
is not the owner; it merely refers
to value belonging to some other structure (e.g. a RingElem
, or a
matrix, or a polynomial). So you can create a RingElem
from
nothing, whereas you must already have a ring element to be able to
create a RefRingElem
which refers to it.
Why bother to distinguish between RingElem
and RefRingElem
?
The main reason was to allow matrices and iterators of polynomials to
be implemented cleanly and efficiently. Clearly a matrix
should
be the owner of the values appearing as its entries, but we also want
a way of reading the matrix entries without having to copy them.
Furthermore, the matrix can use a compact representation: the ring to
which its elements belong is stored just once, and not once for each
element.
The reason that ConstRefRingElem
and RefRingElem
are distinct
classes is that neither const RefRingElem&
nor const RingElem&
achieves what one might reasonably expect. Since a RingElem
is
effectively a pointer to the value represented, applying a C++ const
keyword merely makes the pointer const while leaving the pointed-to
value modifiable. Consider the following procedure
void CannotChange(const RefRingElem& x) { RefRingElem writable(x); // writable reference to value of x writable = writable+1; }
The above procedure will add one to the value of its argument even though it would seem that it should not be alter the value.
The inheritance structure between ConstRefRingElem
,
RefRingElem
and RingElem
implements the similarities and
differences between these classes while also allowing
ConstRefRingElem
and RefRingElem
to be used as types of
parameters to functions and procedures. Given that matrix entry
accessors return a ConstRefRingElem
it is important not to use
RingElem&
or const RingElem&
as the parameter type because
compilation would fail if a matrix entry were passed as parameter.
As already hinted above, the internal data layouts for objects of
types RingElem
, RefRingElem
and ConstRefRingElem
are
identical -- this is guaranteed by the C++ inheritance mechanism. The
subfield indicating the ring to which the value belongs is simply a
const ring
, which is just a reference counting smart pointer. The
subfield indicating the value is a raw pointer of type ``void*
const``; however, when the raw pointer value is to be handled outside
a ring element object then it is wrapped up as a RingElemRawPtr
or
RingElemConstRawPtr
-- these are simply wrapped copies of the
void*
. Make a careful note of the exact type of the data member
myValuePtr
: the pointer is constant while the pointed to value is
not constant. The constness of the pointer is ABSOLUTELY CRUCIAL to
the correct behaviour of RefRingElem
. The fact that the
pointed-to value is not const may seem contradictory (for an object of
type ConstRefRingElem
), but it allows slightly easier
implementation of the non-constant derived classes RefRingElem
and
RingElem
; the friend raw
function puts in the necessary
constness when it is called.
The classes RingElemRawPtr
and RingElemConstRawPtr
are used
for two reasons. One is that if a naked void*
were used outside
the ring element objects then C++ would find the call ``RingElem(R,
0)`` ambiguous because the constant 0
can be interpreted either as
an integer constant or as a null pointer: there are two constructors
which match the call equally well. The other reason is that it
discourages accidentally creating a ring element object from any old
pointer; it makes the programmer think -- plus I feel uneasy when
there are naked void*
pointers around. Note that the type of the
data member myPtr
is simply void*
as opposed to ``void
const*`` which one might reasonably expect. I implemented it this way
as it is simpler to add in the missing constness in the member
function RingElemConstRawPtr::myRawPtr
than it would be to cast it
away in the myRawPtr
function of RingElemRawPtr
.
In ConstRefRingElem
why did I chose to make the data member
myValuePtr
of type void* const
rather than ``RingElemRawPtr
const``?
The class RingBase
declares a number of pure virtual functions for
computing with ring elements. Since these functions are pure they
must all be fully defined in any instantiable ring class
(e.g. RingZZImpl
or RingFpImpl
). These member functions follow
certain conventions:
void*
pointing to the actual value). A read-only
arg is of type RingElemConstRawPtr
, while a writable arg is of type
RingElemRawPtr
. When there are writable args they normally appear
first. For brevity there are typedefs ConstRawPtr
and RawPtr
in
the scope of RingBase
or any derived class.
In a few cases there are non-pure virtual member functions in
RingBase
. They exist either because there is a simple universal
definition or merely to avoid having to define inappropriate member
functions (e.g. gcd functions when the ring cannot be a gcd domain).
Here is a list of them:
myIsUnit(x) |
by default checks that 1 is divisible by x |
myGcd(lhs, x, y) |
gives an error: either NotGcdDom or NYI |
myLcm(lhs, x, y) |
gives an error: either NotGcdDom or NYI |
myGcdQuot(lhs, xquot, yquot, x, y) |
gives an error: either NotGcdDom or NYI |
myExgcd(lhs, xcofac, ycofac, x, y) |
gives an error: either NotGcdDom or NYI |
myIsPrintAtom(x) |
defaults to false |
myIsPrintedWithMinus(x) |
gives a SERIOUS error |
myIsMinusOne(x) |
defaults to myIsOne(-x); calculates -x |
myIsZeroAddMul(lhs, y, z) |
computes lhs += y*z in the obvious way, and calls myIsZero |
myCmp(x, y) |
gives NotOrdDom error |
mySign(x) |
simply calls myCmp(x, 0), then returns -1,0,1 accordingly |
There are three non-virtual member functions for calculating powers: one uses the sequential method, the other two implement the repeated squaring method (one is an entry point, the other an implementation detail). These are non-virtual since they do not need to be redefined; they are universal for all rings.
For the moment I shall assume that the intended meaning of the pure virtual functions is obvious (given the comments in the source code).
Recall that arithmetic operations on objects of type RingElem
,
RefRingElem
and ConstRefRingElem
are converted into member function
calls of the corresponding owning ring. Here is the source code for
addition of ring elements -- it typifies the implementation of
operations on ring elements.
RingElem operator+(ConstRefRingElem x, ConstRefRingElem y) { const ring& Rx = owner(x); const ring& Ry = owner(y); if (Rx != Ry) error(CoCoAError(ERR::MixedRings, "RingElem + RingElem")); RingElem ans(Rx); Rx->myAdd(raw(ans), raw(x), raw(y)); return ans; }
The arguments are of type ConstRefRingElem
since they are
read-only, and the return type is RingElem
since it is new
self-owning value (it does not refer to a value belinging to some
other structure]. Inside the function we check that the rings of the
arguments are compatible, and report an error if they are not.
Otherwise a temporary local variable is created for the answer, and
the actual computation is effected via a member function call to the
ring in which the values lie. Note the use of the raw
function
for accessing the raw pointer of a ring element. In summary, an
operation on ring elements intended for public use should fully check
its arguments for compatibility and correctness (e.g. to avoid
division by zero); if all checks pass, the result is computed by
passing raw pointers to the appropriate member functions of the ring
involved -- this member function assumes that the values handed to it
are compatible and valid; if not, undefined behaviour will result
(i.e. a crash if you are lucky).
Most of the member functions of a ring are for manipulating raw values
from that same ring, a few permit one to query properties of the ring.
The type of a raw value is RingBase::RawValue
, which helpfully
abbreviates to RawValue inside the namespace of RingBase. Wherever
possible the concrete implementations should be exception safe, i.e. they
should offer either the strong exception guarantee or the no-throw
guarantee (according to the definitions in Exceptional C++ by Sutter).
I have chosen not to use operator^
for computing powers because of a
significant risk of misunderstanding between programmer and compiler.
The syntax/grammar of C++ cannot be changed, and operator^
binds less
tightly than (binary) operator*
, so any expression of the form a*b^c
will be parsed as (a*b)^c
; this is almost certainly not what the
programmer intended. To avoid such problems of misunderstanding I
have preferred not to define operator^
; it seems too dangerous.
How to swap RefRingElem
s? Must be careful when trying to swap a
RefRingElem
with a RingElem
to avoid possible orphans (memory
leaks) or doubly owned values.
Note about comparison operators (<,<=,>,>=, and !=). The C++ STL
does have templates which will define all the relational operators
efficiently assuming the existence of operator<
and operator==
.
These are defined in the namespace std::rel_ops
in the standard
header file <utility>. I have chosen NOT to use these because they can
define only homogeneous comparisons; so the comparisons between
ConstRefRingElem
and int
or BigInt
would still have to be written out
manually, and I prefer the symmetry of writing them all out.
See p.69ff of Josuttis for details.
The function myAssignZero
was NECESSARY because myAssign(x, 0)
was
ambiguous (ambiguated by the assignment from an mpz_t
). It is no longer
necessary, but I prefer to keep it (for the time being).
The requirement to use the type ConstRefRingElem
for function arguments
(which should normally be const RingElem&
is not ideal, but it seems hard
to find a better way. It is not nice to expect users to use a funny type
for their function arguments. How else could I implement (noncopying) access to
coefficients in a polynomial via an iterator, or access to matrix elements?
Would we want ++ and -- operators for RingElem
s???
Should (some of) the query functions return bool3
values?
What about properties which are hard to determine?
How to generate random elements from a ring?
Note the slightly unusual return types for operator+=
etc;
it seemed daft to have a reference-to-reference. In contrast, I left
the return type for RingElem::operator=
as RingElem&
; it felt
very odd thinking of some other type RefRingElem
as the return type.
The dtor for ConstRefRingElem
is deliberately not virtual; idem for
RefRingElem
. This could potentially cause trouble if you convert a
pointer to RingElem
into a pointer to RefRingElem
; but if you do that,
you'd better be doubly sure about what you're doing anyway.
Anna thinks that NearestInteger
could handle specially elements of
RingZZ
rather than doing the full wasteful computation. Not sure
if the extra code and complication would really make a difference in
practice.
gcd
and lcm
: there is no guarantee on sign/monic because may
be costly to compute and generally useless.