source: git/libpolys/coeffs/rmodulon.cc @ 9f7665

spielwiese
Last change on this file since 9f7665 was 9f7665, checked in by Oleksandr Motsak <motsak@…>, 10 years ago
Removed HAVE_CONFIG guards fix: fixed the inclusion of configure-generated *config.h's
  • Property mode set to 100644
File size: 17.1 KB
Line 
1/****************************************
2*  Computer Algebra System SINGULAR     *
3****************************************/
4/*
5* ABSTRACT: numbers modulo n
6*/
7
8
9
10
11#include <misc/auxiliary.h>
12
13#ifdef HAVE_RINGS
14
15#include <misc/mylimits.h>
16#include <coeffs/coeffs.h>
17#include <reporter/reporter.h>
18#include <omalloc/omalloc.h>
19#include <coeffs/numbers.h>
20#include <coeffs/longrat.h>
21#include <coeffs/mpr_complex.h>
22#include <coeffs/rmodulon.h>
23#include "si_gmp.h"
24
25#include <string.h>
26
27/// Our Type!
28static const n_coeffType ID = n_Zn;
29static const n_coeffType ID2 = n_Znm;
30
31extern omBin gmp_nrz_bin;
32
33void    nrnCoeffWrite  (const coeffs r, BOOLEAN /*details*/)
34{
35  long l = (long)mpz_sizeinbase(r->modBase, 10) + 2;
36  char* s = (char*) omAlloc(l);
37  s= mpz_get_str (s, 10, r->modBase);
38  if (nCoeff_is_Ring_ModN(r)) Print("//   coeff. ring is : Z/%s\n", s);
39  else if (nCoeff_is_Ring_PtoM(r)) Print("//   coeff. ring is : Z/%s^%lu\n", s, r->modExponent);
40  omFreeSize((ADDRESS)s, l);
41}
42
43static BOOLEAN nrnCoeffsEqual(const coeffs r, n_coeffType n, void * parameter)
44{
45  /* test, if r is an instance of nInitCoeffs(n,parameter) */
46  return (n==n_Zn) && (mpz_cmp_ui(r->modNumber,(long)parameter)==0);
47}
48
49static char* nrnCoeffString(const coeffs r)
50{
51  long l = (long)mpz_sizeinbase(r->modBase, 10) +2;
52  char* b = (char*) omAlloc(l);
53  b= mpz_get_str (b, 10, r->modBase);
54  char* s = (char*) omAlloc(7+2+10+l);
55  if (nCoeff_is_Ring_ModN(r)) sprintf(s,"integer,%s",b);
56  else /*if (nCoeff_is_Ring_PtoM(r))*/ sprintf(s,"integer,%s^%lu",b,r->modExponent);
57  omFreeSize(b,l);
58  return s;
59}
60
61/* for initializing function pointers */
62BOOLEAN nrnInitChar (coeffs r, void* p)
63{
64  assume( (getCoeffType(r) == ID) || (getCoeffType (r) == ID2) );
65  ZnmInfo * info= (ZnmInfo *) p;
66  r->modBase= info->base;
67
68  nrnInitExp (info->exp, r);
69
70  /* next computation may yield wrong characteristic as r->modNumber
71     is a GMP number */
72  r->ch = mpz_get_ui(r->modNumber);
73
74  r->cfCoeffString = nrnCoeffString;
75
76  r->cfInit        = nrnInit;
77  r->cfDelete      = nrnDelete;
78  r->cfCopy        = nrnCopy;
79  r->cfSize        = nrnSize;
80  r->cfInt         = nrnInt;
81  r->cfAdd         = nrnAdd;
82  r->cfSub         = nrnSub;
83  r->cfMult        = nrnMult;
84  r->cfDiv         = nrnDiv;
85  r->cfIntDiv      = nrnIntDiv;
86  r->cfIntMod      = nrnMod;
87  r->cfExactDiv    = nrnDiv;
88  r->cfNeg         = nrnNeg;
89  r->cfInvers      = nrnInvers;
90  r->cfDivBy       = nrnDivBy;
91  r->cfDivComp     = nrnDivComp;
92  r->cfGreater     = nrnGreater;
93  r->cfEqual       = nrnEqual;
94  r->cfIsZero      = nrnIsZero;
95  r->cfIsOne       = nrnIsOne;
96  r->cfIsMOne      = nrnIsMOne;
97  r->cfGreaterZero = nrnGreaterZero;
98  r->cfWriteLong   = nrnWrite;
99  r->cfRead        = nrnRead;
100  r->cfPower       = nrnPower;
101  r->cfSetMap      = nrnSetMap;
102  r->cfNormalize   = ndNormalize;
103  r->cfLcm         = nrnLcm;
104  r->cfGcd         = nrnGcd;
105  r->cfIsUnit      = nrnIsUnit;
106  r->cfGetUnit     = nrnGetUnit;
107  r->cfExtGcd      = nrnExtGcd;
108  r->cfName        = ndName;
109  r->cfCoeffWrite  = nrnCoeffWrite;
110  r->nCoeffIsEqual = nrnCoeffsEqual;
111  r->cfInit_bigint = nrnMapQ;
112  r->cfKillChar    = ndKillChar;
113#ifdef LDEBUG
114  r->cfDBTest      = nrnDBTest;
115#endif
116  return FALSE;
117}
118
119/*
120 * create a number from int
121 */
122number nrnInit(long i, const coeffs r)
123{
124  int_number erg = (int_number) omAllocBin(gmp_nrz_bin);
125  mpz_init_set_si(erg, i);
126  mpz_mod(erg, erg, r->modNumber);
127  return (number) erg;
128}
129
130void nrnDelete(number *a, const coeffs)
131{
132  if (*a == NULL) return;
133  mpz_clear((int_number) *a);
134  omFreeBin((void *) *a, gmp_nrz_bin);
135  *a = NULL;
136}
137
138number nrnCopy(number a, const coeffs)
139{
140  int_number erg = (int_number) omAllocBin(gmp_nrz_bin);
141  mpz_init_set(erg, (int_number) a);
142  return (number) erg;
143}
144
145int nrnSize(number a, const coeffs)
146{
147  if (a == NULL) return 0;
148  return sizeof(mpz_t);
149}
150
151/*
152 * convert a number to int
153 */
154int nrnInt(number &n, const coeffs)
155{
156  return (int)mpz_get_si((int_number) n);
157}
158
159/*
160 * Multiply two numbers
161 */
162number nrnMult(number a, number b, const coeffs r)
163{
164  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
165  mpz_init(erg);
166  mpz_mul(erg, (int_number)a, (int_number) b);
167  mpz_mod(erg, erg, r->modNumber);
168  return (number) erg;
169}
170
171void nrnPower(number a, int i, number * result, const coeffs r)
172{
173  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
174  mpz_init(erg);
175  mpz_powm_ui(erg, (int_number)a, i, r->modNumber);
176  *result = (number) erg;
177}
178
179number nrnAdd(number a, number b, const coeffs r)
180{
181  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
182  mpz_init(erg);
183  mpz_add(erg, (int_number)a, (int_number) b);
184  mpz_mod(erg, erg, r->modNumber);
185  return (number) erg;
186}
187
188number nrnSub(number a, number b, const coeffs r)
189{
190  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
191  mpz_init(erg);
192  mpz_sub(erg, (int_number)a, (int_number) b);
193  mpz_mod(erg, erg, r->modNumber);
194  return (number) erg;
195}
196
197number nrnNeg(number c, const coeffs r)
198{
199  if( !nrnIsZero(c, r) )
200    // Attention: This method operates in-place.
201    mpz_sub((int_number)c, r->modNumber, (int_number)c);
202  return c;
203}
204
205number nrnInvers(number c, const coeffs r)
206{
207  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
208  mpz_init(erg);
209  mpz_invert(erg, (int_number)c, r->modNumber);
210  return (number) erg;
211}
212
213/*
214 * Give the smallest k, such that a * x = k = b * y has a solution
215 * TODO: lcm(gcd,gcd) better than gcd(lcm) ?
216 */
217number nrnLcm(number a, number b, const coeffs r)
218{
219  number erg = nrnGcd(NULL, a, r);
220  number tmp = nrnGcd(NULL, b, r);
221  mpz_lcm((int_number)erg, (int_number)erg, (int_number)tmp);
222  nrnDelete(&tmp, NULL);
223  return (number)erg;
224}
225
226/*
227 * Give the largest k, such that a = x * k, b = y * k has
228 * a solution.
229 */
230number nrnGcd(number a, number b, const coeffs r)
231{
232  if ((a == NULL) && (b == NULL)) return nrnInit(0,r);
233  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
234  mpz_init_set(erg, r->modNumber);
235  if (a != NULL) mpz_gcd(erg, erg, (int_number)a);
236  if (b != NULL) mpz_gcd(erg, erg, (int_number)b);
237  return (number)erg;
238}
239
240/* Not needed any more, but may have room for improvement
241   number nrnGcd3(number a,number b, number c,ring r)
242{
243  int_number erg = (int_number) omAllocBin(gmp_nrz_bin);
244  mpz_init(erg);
245  if (a == NULL) a = (number)r->modNumber;
246  if (b == NULL) b = (number)r->modNumber;
247  if (c == NULL) c = (number)r->modNumber;
248  mpz_gcd(erg, (int_number)a, (int_number)b);
249  mpz_gcd(erg, erg, (int_number)c);
250  mpz_gcd(erg, erg, r->modNumber);
251  return (number)erg;
252}
253*/
254
255/*
256 * Give the largest k, such that a = x * k, b = y * k has
257 * a solution and r, s, s.t. k = s*a + t*b
258 */
259number nrnExtGcd(number a, number b, number *s, number *t, const coeffs r)
260{
261  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
262  int_number bs  = (int_number)omAllocBin(gmp_nrz_bin);
263  int_number bt  = (int_number)omAllocBin(gmp_nrz_bin);
264  mpz_init(erg);
265  mpz_init(bs);
266  mpz_init(bt);
267  mpz_gcdext(erg, bs, bt, (int_number)a, (int_number)b);
268  mpz_mod(bs, bs, r->modNumber);
269  mpz_mod(bt, bt, r->modNumber);
270  *s = (number)bs;
271  *t = (number)bt;
272  return (number)erg;
273}
274
275BOOLEAN nrnIsZero(number a, const coeffs)
276{
277#ifdef LDEBUG
278  if (a == NULL) return FALSE;
279#endif
280  return 0 == mpz_cmpabs_ui((int_number)a, 0);
281}
282
283BOOLEAN nrnIsOne(number a, const coeffs)
284{
285#ifdef LDEBUG
286  if (a == NULL) return FALSE;
287#endif
288  return 0 == mpz_cmp_si((int_number)a, 1);
289}
290
291BOOLEAN nrnIsMOne(number a, const coeffs r)
292{
293#ifdef LDEBUG
294  if (a == NULL) return FALSE;
295#endif
296  mpz_t t; mpz_init_set(t, (int_number)a);
297  mpz_add_ui(t, t, 1);
298  bool erg = (0 == mpz_cmp(t, r->modNumber));
299  mpz_clear(t);
300  return erg;
301}
302
303BOOLEAN nrnEqual(number a, number b, const coeffs)
304{
305  return 0 == mpz_cmp((int_number)a, (int_number)b);
306}
307
308BOOLEAN nrnGreater(number a, number b, const coeffs)
309{
310  return 0 < mpz_cmp((int_number)a, (int_number)b);
311}
312
313BOOLEAN nrnGreaterZero(number k, const coeffs)
314{
315  return 0 < mpz_cmp_si((int_number)k, 0);
316}
317
318BOOLEAN nrnIsUnit(number a, const coeffs r)
319{
320  number tmp = nrnGcd(a, (number)r->modNumber, r);
321  bool res = nrnIsOne(tmp, r);
322  nrnDelete(&tmp, NULL);
323  return res;
324}
325
326number nrnGetUnit(number k, const coeffs r)
327{
328  if (mpz_divisible_p(r->modNumber, (int_number)k)) return nrnInit(1,r);
329
330  int_number unit = (int_number)nrnGcd(k, 0, r);
331  mpz_tdiv_q(unit, (int_number)k, unit);
332  int_number gcd = (int_number)nrnGcd((number)unit, 0, r);
333  if (!nrnIsOne((number)gcd,r))
334  {
335    int_number ctmp;
336    // tmp := unit^2
337    int_number tmp = (int_number) nrnMult((number) unit,(number) unit,r);
338    // gcd_new := gcd(tmp, 0)
339    int_number gcd_new = (int_number) nrnGcd((number) tmp, 0, r);
340    while (!nrnEqual((number) gcd_new,(number) gcd,r))
341    {
342      // gcd := gcd_new
343      ctmp = gcd;
344      gcd = gcd_new;
345      gcd_new = ctmp;
346      // tmp := tmp * unit
347      mpz_mul(tmp, tmp, unit);
348      mpz_mod(tmp, tmp, r->modNumber);
349      // gcd_new := gcd(tmp, 0)
350      mpz_gcd(gcd_new, tmp, r->modNumber);
351    }
352    // unit := unit + modNumber / gcd_new
353    mpz_tdiv_q(tmp, r->modNumber, gcd_new);
354    mpz_add(unit, unit, tmp);
355    mpz_mod(unit, unit, r->modNumber);
356    nrnDelete((number*) &gcd_new, NULL);
357    nrnDelete((number*) &tmp, NULL);
358  }
359  nrnDelete((number*) &gcd, NULL);
360  return (number)unit;
361}
362
363BOOLEAN nrnDivBy(number a, number b, const coeffs r)
364{
365  if (a == NULL)
366    return mpz_divisible_p(r->modNumber, (int_number)b);
367  else
368  { /* b divides a iff b/gcd(a, b) is a unit in the given ring: */
369    number n = nrnGcd(a, b, r);
370    mpz_tdiv_q((int_number)n, (int_number)b, (int_number)n);
371    bool result = nrnIsUnit(n, r);
372    nrnDelete(&n, NULL);
373    return result;
374  }
375}
376
377int nrnDivComp(number a, number b, const coeffs r)
378{
379  if (nrnEqual(a, b,r)) return 2;
380  if (mpz_divisible_p((int_number) a, (int_number) b)) return -1;
381  if (mpz_divisible_p((int_number) b, (int_number) a)) return 1;
382  return 0;
383}
384
385number nrnDiv(number a, number b, const coeffs r)
386{
387  if (a == NULL) a = (number)r->modNumber;
388  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
389  mpz_init(erg);
390  if (mpz_divisible_p((int_number)a, (int_number)b))
391  {
392    mpz_divexact(erg, (int_number)a, (int_number)b);
393    return (number)erg;
394  }
395  else
396  {
397    int_number gcd = (int_number)nrnGcd(a, b, r);
398    mpz_divexact(erg, (int_number)b, gcd);
399    if (!nrnIsUnit((number)erg, r))
400    {
401      WerrorS("Division not possible, even by cancelling zero divisors.");
402      WerrorS("Result is integer division without remainder.");
403      mpz_tdiv_q(erg, (int_number) a, (int_number) b);
404      nrnDelete((number*) &gcd, NULL);
405      return (number)erg;
406    }
407    // a / gcd(a,b) * [b / gcd (a,b)]^(-1)
408    int_number tmp = (int_number)nrnInvers((number) erg,r);
409    mpz_divexact(erg, (int_number)a, gcd);
410    mpz_mul(erg, erg, tmp);
411    nrnDelete((number*) &gcd, NULL);
412    nrnDelete((number*) &tmp, NULL);
413    mpz_mod(erg, erg, r->modNumber);
414    return (number)erg;
415  }
416}
417
418number nrnMod(number a, number b, const coeffs r)
419{
420  /*
421    We need to return the number rr which is uniquely determined by the
422    following two properties:
423      (1) 0 <= rr < |b| (with respect to '<' and '<=' performed in Z x Z)
424      (2) There exists some k in the integers Z such that a = k * b + rr.
425    Consider g := gcd(n, |b|). Note that then |b|/g is a unit in Z/n.
426    Now, there are three cases:
427      (a) g = 1
428          Then |b| is a unit in Z/n, i.e. |b| (and also b) divides a.
429          Thus rr = 0.
430      (b) g <> 1 and g divides a
431          Then a = (a/g) * (|b|/g)^(-1) * b (up to sign), i.e. again rr = 0.
432      (c) g <> 1 and g does not divide a
433          Then denote the division with remainder of a by g as this:
434          a = s * g + t. Then t = a - s * g = a - s * (|b|/g)^(-1) * |b|
435          fulfills (1) and (2), i.e. rr := t is the correct result. Hence
436          in this third case, rr is the remainder of division of a by g in Z.
437     Remark: according to mpz_mod: a,b are always non-negative
438  */
439  int_number g = (int_number)omAllocBin(gmp_nrz_bin);
440  int_number rr = (int_number)omAllocBin(gmp_nrz_bin);
441  mpz_init(g);
442  mpz_init_set_si(rr, 0);
443  mpz_gcd(g, (int_number)r->modNumber, (int_number)b); // g is now as above
444  if (mpz_cmp_si(g, (long)1) != 0) mpz_mod(rr, (int_number)a, g); // the case g <> 1
445  mpz_clear(g);
446  omFreeBin(g, gmp_nrz_bin);
447  return (number)rr;
448}
449
450number nrnIntDiv(number a, number b, const coeffs r)
451{
452  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
453  mpz_init(erg);
454  if (a == NULL) a = (number)r->modNumber;
455  mpz_tdiv_q(erg, (int_number)a, (int_number)b);
456  return (number)erg;
457}
458
459/*
460 * Helper function for computing the module
461 */
462
463int_number nrnMapCoef = NULL;
464
465number nrnMapModN(number from, const coeffs /*src*/, const coeffs dst)
466{
467  return nrnMult(from, (number) nrnMapCoef, dst);
468}
469
470number nrnMap2toM(number from, const coeffs /*src*/, const coeffs dst)
471{
472  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
473  mpz_init(erg);
474  mpz_mul_ui(erg, nrnMapCoef, (NATNUMBER)from);
475  mpz_mod(erg, erg, dst->modNumber);
476  return (number)erg;
477}
478
479number nrnMapZp(number from, const coeffs /*src*/, const coeffs dst)
480{
481  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
482  mpz_init(erg);
483  // TODO: use npInt(...)
484  mpz_mul_si(erg, nrnMapCoef, (NATNUMBER)from);
485  mpz_mod(erg, erg, dst->modNumber);
486  return (number)erg;
487}
488
489number nrnMapGMP(number from, const coeffs /*src*/, const coeffs dst)
490{
491  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
492  mpz_init(erg);
493  mpz_mod(erg, (int_number)from, dst->modNumber);
494  return (number)erg;
495}
496
497number nrnMapQ(number from, const coeffs src, const coeffs dst)
498{
499  int_number erg = (int_number)omAllocBin(gmp_nrz_bin);
500  mpz_init(erg);
501  nlGMP(from, (number)erg, src);
502  mpz_mod(erg, erg, dst->modNumber);
503  return (number)erg;
504}
505
506nMapFunc nrnSetMap(const coeffs src, const coeffs dst)
507{
508  /* dst = currRing->cf */
509  if (nCoeff_is_Ring_Z(src))
510  {
511    return nrnMapGMP;
512  }
513  if (nCoeff_is_Q(src))
514  {
515    return nrnMapQ;
516  }
517  // Some type of Z/n ring / field
518  if (nCoeff_is_Ring_ModN(src) || nCoeff_is_Ring_PtoM(src) ||
519      nCoeff_is_Ring_2toM(src) || nCoeff_is_Zp(src))
520  {
521    if (   (!nCoeff_is_Zp(src))
522        && (mpz_cmp(src->modBase, dst->modBase) == 0)
523        && (src->modExponent == dst->modExponent)) return nrnMapGMP;
524    else
525    {
526      int_number nrnMapModul = (int_number) omAllocBin(gmp_nrz_bin);
527      // Computing the n of Z/n
528      if (nCoeff_is_Zp(src))
529      {
530        mpz_init_set_si(nrnMapModul, src->ch);
531      }
532      else
533      {
534        mpz_init(nrnMapModul);
535        mpz_set(nrnMapModul, src->modNumber);
536      }
537      // nrnMapCoef = 1 in dst       if dst is a subring of src
538      // nrnMapCoef = 0 in dst / src if src is a subring of dst
539      if (nrnMapCoef == NULL)
540      {
541        nrnMapCoef = (int_number) omAllocBin(gmp_nrz_bin);
542        mpz_init(nrnMapCoef);
543      }
544      if (mpz_divisible_p(nrnMapModul, dst->modNumber))
545      {
546        mpz_set_si(nrnMapCoef, 1);
547      }
548      else
549      if (nrnDivBy(NULL, (number) nrnMapModul,dst))
550      {
551        mpz_divexact(nrnMapCoef, dst->modNumber, nrnMapModul);
552        int_number tmp = dst->modNumber;
553        dst->modNumber = nrnMapModul;
554        if (!nrnIsUnit((number) nrnMapCoef,dst))
555        {
556          dst->modNumber = tmp;
557          nrnDelete((number*) &nrnMapModul, dst);
558          return NULL;
559        }
560        int_number inv = (int_number) nrnInvers((number) nrnMapCoef,dst);
561        dst->modNumber = tmp;
562        mpz_mul(nrnMapCoef, nrnMapCoef, inv);
563        mpz_mod(nrnMapCoef, nrnMapCoef, dst->modNumber);
564        nrnDelete((number*) &inv, dst);
565      }
566      else
567      {
568        nrnDelete((number*) &nrnMapModul, dst);
569        return NULL;
570      }
571      nrnDelete((number*) &nrnMapModul, dst);
572      if (nCoeff_is_Ring_2toM(src))
573        return nrnMap2toM;
574      else if (nCoeff_is_Zp(src))
575        return nrnMapZp;
576      else
577        return nrnMapModN;
578    }
579  }
580  return NULL;      // default
581}
582
583/*
584 * set the exponent (allocate and init tables) (TODO)
585 */
586
587void nrnSetExp(unsigned long m, coeffs r)
588{
589  /* clean up former stuff */
590  if (r->modNumber != NULL) mpz_clear(r->modNumber);
591
592  r->modExponent= m;
593  r->modNumber = (int_number)omAllocBin(gmp_nrz_bin);
594  mpz_init_set (r->modNumber, r->modBase);
595  mpz_pow_ui (r->modNumber, r->modNumber, m);
596}
597
598/* We expect this ring to be Z/n^m for some m > 0 and for some n > 2 which is not a prime. */
599void nrnInitExp(unsigned long m, coeffs r)
600{
601  nrnSetExp(m, r);
602  assume (r->modNumber != NULL);
603  if (mpz_cmp_ui(r->modNumber,2) <= 0)
604    WarnS("nrnInitExp failed (m in Z/m too small)");
605}
606
607#ifdef LDEBUG
608BOOLEAN nrnDBTest (number a, const char *, const int, const coeffs r)
609{
610  if (a==NULL) return TRUE;
611  if ( (mpz_cmp_si((int_number) a, 0) < 0) || (mpz_cmp((int_number) a, r->modNumber) > 0) )
612  {
613    return FALSE;
614  }
615  return TRUE;
616}
617#endif
618
619/*2
620* extracts a long integer from s, returns the rest    (COPY FROM longrat0.cc)
621*/
622static const char * nlCPEatLongC(char *s, mpz_ptr i)
623{
624  const char * start=s;
625  if (!(*s >= '0' && *s <= '9'))
626  {
627    mpz_init_set_si(i, 1);
628    return s;
629  }
630  mpz_init(i);
631  while (*s >= '0' && *s <= '9') s++;
632  if (*s=='\0')
633  {
634    mpz_set_str(i,start,10);
635  }
636  else
637  {
638    char c=*s;
639    *s='\0';
640    mpz_set_str(i,start,10);
641    *s=c;
642  }
643  return s;
644}
645
646const char * nrnRead (const char *s, number *a, const coeffs r)
647{
648  int_number z = (int_number) omAllocBin(gmp_nrz_bin);
649  {
650    s = nlCPEatLongC((char *)s, z);
651  }
652  mpz_mod(z, z, r->modNumber);
653  *a = (number) z;
654  return s;
655}
656#endif
657/* #ifdef HAVE_RINGS */
Note: See TracBrowser for help on using the repository browser.