New multiprecision integer arithmetic suite.
[u/mdw/catacomb] / mp.h
CommitLineData
d03ab969 1/* -*-c-*-
2 *
d3409d5e 3 * $Id: mp.h,v 1.2 1999/11/17 18:02:16 mdw Exp $
d03ab969 4 *
d3409d5e 5 * Simple multiprecision arithmetic
d03ab969 6 *
7 * (c) 1999 Straylight/Edgeware
8 */
9
10/*----- Licensing notice --------------------------------------------------*
11 *
12 * This file is part of Catacomb.
13 *
14 * Catacomb is free software; you can redistribute it and/or modify
15 * it under the terms of the GNU Library General Public License as
16 * published by the Free Software Foundation; either version 2 of the
17 * License, or (at your option) any later version.
18 *
19 * Catacomb is distributed in the hope that it will be useful,
20 * but WITHOUT ANY WARRANTY; without even the implied warranty of
21 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 * GNU Library General Public License for more details.
23 *
24 * You should have received a copy of the GNU Library General Public
25 * License along with Catacomb; if not, write to the Free
26 * Software Foundation, Inc., 59 Temple Place - Suite 330, Boston,
27 * MA 02111-1307, USA.
28 */
29
30/*----- Revision history --------------------------------------------------*
31 *
32 * $Log: mp.h,v $
d3409d5e 33 * Revision 1.2 1999/11/17 18:02:16 mdw
34 * New multiprecision integer arithmetic suite.
d03ab969 35 *
36 */
37
38#ifndef MP_H
39#define MP_H
40
41#ifdef __cplusplus
42 extern "C" {
43#endif
44
45/*----- Header files ------------------------------------------------------*/
46
d3409d5e 47#include <assert.h>
d03ab969 48#include <string.h>
49
d3409d5e 50#include <mLib/sub.h>
51
52#ifndef MPW_H
53# include "mpw.h"
d03ab969 54#endif
55
56#ifndef MPX_H
57# include "mpx.h"
58#endif
59
d03ab969 60/*----- Data structures ---------------------------------------------------*/
61
62typedef struct mp {
d3409d5e 63 mpw *v, *vl;
64 size_t sz;
65 unsigned f;
66 unsigned ref;
d03ab969 67} mp;
68
d3409d5e 69#define MP_NEG 1u
70#define MP_BURN 2u
71#define MP_CONST 4u
72#define MP_UNDEF 8u
d03ab969 73
d3409d5e 74/*----- Useful constants --------------------------------------------------*/
d03ab969 75
d3409d5e 76extern mp mp_const[];
d03ab969 77
d3409d5e 78#define MP_ZERO (&mp_const[0])
79#define MP_ONE (&mp_const[1])
80#define MP_TWO (&mp_const[2])
81#define MP_THREE (&mp_const[3])
82#define MP_FOUR (&mp_const[4])
83#define MP_FIVE (&mp_const[5])
84#define MP_TEN (&mp_const[6])
85#define MP_MONE (&mp_const[7])
d03ab969 86
d3409d5e 87#define MP_NEW ((mp *)0)
d03ab969 88
d3409d5e 89/*----- Memory allocation hooks -------------------------------------------*/
d03ab969 90
d3409d5e 91#ifndef MPARENA_H
92# include "mparena.h"
93#endif
94
95/* --- @MP_ARENA@ --- *
d03ab969 96 *
d3409d5e 97 * This selects where memory is allocated from. Tweak to use more fancy
98 * things like custom arenas.
99 */
100
101#ifndef MP_ARENA
102# define MP_ARENA MPARENA_GLOBAL
103#endif
104
105/* --- @MP_ALLOC@ --- *
d03ab969 106 *
d3409d5e 107 * Arguments: @size_t sz@ = size required
108 *
109 * Returns: Pointer to an allocated vector of the requested size.
d03ab969 110 *
d3409d5e 111 * Use: Hook for vector allocation.
d03ab969 112 */
113
d3409d5e 114#ifndef MP_ALLOC
115# define MP_ALLOC(sz) mpalloc(MP_ARENA, (sz))
116#endif
d03ab969 117
d3409d5e 118/* --- @MP_FREE@ --- *
119 *
120 * Arguments: @mpw *v@ = pointer to vector
d03ab969 121 *
d3409d5e 122 * Returns: ---
d03ab969 123 *
d3409d5e 124 * Use: Hook for vector deallocation.
d03ab969 125 */
126
d3409d5e 127#ifndef MP_FREE
128# define MP_FREE(v) mpfree(MP_ARENA, (v))
129#endif
d03ab969 130
d3409d5e 131/*----- Paranoia management -----------------------------------------------*/
132
133/* --- @mp_burn@ --- *
d03ab969 134 *
d3409d5e 135 * Arguments: @mp *m@ = pointer to a multiprecision integer
d03ab969 136 *
137 * Returns: ---
138 *
d3409d5e 139 * Use: Marks the integer as `burn-after-use'. When the integer's
140 * memory is deallocated, it is deleted so that traces can't
141 * remain in the swap file. In theory.
d03ab969 142 */
143
d3409d5e 144extern void mp_burn(mp */*m*/);
d03ab969 145
d3409d5e 146/*----- Trivial macros ----------------------------------------------------*/
147
148/* --- @MP_LEN@ --- *
d03ab969 149 *
d3409d5e 150 * Arguments: @mp *m@ = pointer to a multiprecision integer
d03ab969 151 *
d3409d5e 152 * Returns: Length of the integer, in words.
d03ab969 153 */
154
d3409d5e 155#define MP_LEN(m) ((m)->vl - ((m)->v))
d03ab969 156
d3409d5e 157/*----- Memory management and reference counting --------------------------*/
158
159/* --- @mp_create@ --- *
d03ab969 160 *
d3409d5e 161 * Arguments: @size_t sz@ = size of vector required
d03ab969 162 *
d3409d5e 163 * Returns: Pointer to pristine new MP structure with enough memory
164 * bolted onto it.
d03ab969 165 *
d3409d5e 166 * Use: Creates a new multiprecision integer with indeterminate
167 * contents. The integer has a single reference.
d03ab969 168 */
169
d3409d5e 170extern mp *mp_create(size_t /*sz*/);
d03ab969 171
d3409d5e 172/* --- @mp_build@ --- *
d03ab969 173 *
d3409d5e 174 * Arguments: @mp *m@ = pointer to an MP block to fill in
175 * @mpw *v@ = pointer to a word array
176 * @mpw *vl@ = pointer just past end of array
d03ab969 177 *
178 * Returns: ---
179 *
d3409d5e 180 * Use: Creates a multiprecision integer representing some smallish
181 * number. You must provide storage for the number and dispose
182 * of it when you've finished with it. The number is marked as
183 * constant while it exists.
d03ab969 184 */
185
d3409d5e 186extern void mp_build(mp */*m*/, mpw */*v*/, mpw */*vl*/);
d03ab969 187
d3409d5e 188/* --- @mp_destroy@ --- *
d03ab969 189 *
d3409d5e 190 * Arguments: @mp *m@ = pointer to a multiprecision integer
d03ab969 191 *
192 * Returns: ---
193 *
d3409d5e 194 * Use: Destroys a multiprecision integer. The reference count isn't
195 * checked. Don't use this function if you don't know what
196 * you're doing: use @mp_drop@ instead.
d03ab969 197 */
198
d3409d5e 199extern void mp_destroy(mp */*m*/);
d03ab969 200
d3409d5e 201/* --- @mp_copy@ --- *
d03ab969 202 *
d3409d5e 203 * Arguments: @mp *m@ = pointer to a multiprecision integer
d03ab969 204 *
d3409d5e 205 * Returns: A copy of the given multiprecision integer.
206 *
207 * Use: Copies the given integer. In fact you just get another
208 * reference to the same old one again.
d03ab969 209 */
210
d3409d5e 211extern mp *mp_copy(mp */*m*/);
d03ab969 212
d3409d5e 213#define MP_COPY(m) ((m)->ref++, (m))
214
215/* --- @mp_drop@ --- *
d03ab969 216 *
d3409d5e 217 * Arguments: @mp *m@ = pointer to a multiprecision integer
d03ab969 218 *
219 * Returns: ---
220 *
d3409d5e 221 * Use: Drops a reference to an integer which isn't wanted any more.
222 * If there are no more references, the integer is destroyed.
d03ab969 223 */
224
d3409d5e 225extern void mp_drop(mp */*m*/);
d03ab969 226
d3409d5e 227#define MP_DROP(m) do { \
228 mp *_mm = (m); \
229 if (_mm->ref > 1) \
230 _mm->ref--; \
231 else if (!(_mm->f & MP_CONST)) \
232 mp_destroy(_mm); \
233} while (0)
234
235/* --- @mp_split@ --- *
d03ab969 236 *
d3409d5e 237 * Arguments: @mp *m@ = pointer to a multiprecision integer
d03ab969 238 *
d3409d5e 239 * Returns: A reference to the same integer, possibly with a different
240 * address.
d03ab969 241 *
d3409d5e 242 * Use: Splits off a modifiable version of the integer referred to.
d03ab969 243 */
244
d3409d5e 245extern mp *mp_split(mp */*m*/);
246
247#define MP_SPLIT(m) do { \
248 mp *_mm = (m); \
249 if ((_mm->f & MP_CONST) || _mm->ref != 1) { \
250 mp *_dd = mp_create(_mm->sz); \
251 _dd->vl = _dd->v + MP_LEN(_mm); \
252 _dd->f = _mm->f & (MP_NEG | MP_BURN); \
253 memcpy(_dd->v, _mm->v, MPWS(MP_LEN(_mm))); \
254 _dd->ref = 1; \
255 _mm->ref--; \
256 (m) = _dd; \
257 } \
258} while (0)
d03ab969 259
d3409d5e 260/* --- @mp_resize@ --- *
d03ab969 261 *
d3409d5e 262 * Arguments: @mp *m@ = pointer to a multiprecision integer
263 * @size_t sz@ = new size
d03ab969 264 *
d3409d5e 265 * Returns: ---
d03ab969 266 *
d3409d5e 267 * Use: Resizes the vector containing the integer's digits. The new
268 * size must be at least as large as the current integer's
269 * length. The integer's length is increased and new digits are
270 * filled with zeroes. This isn't really intended for client
271 * use.
d03ab969 272 */
273
d3409d5e 274extern void mp_resize(mp */*m*/, size_t /*sz*/);
275
276#define MP_RESIZE(m, ssz) do { \
277 mp *_m = (m); \
278 size_t _sz = (ssz); \
279 size_t _len = MP_LEN(_m); \
280 mpw *_v = MP_ALLOC(_sz); \
281 memcpy(_v, _m->v, MPWS(_len)); \
282 if (_m->f & MP_BURN) \
283 memset(_m->v, 0, MPWS(_m->sz)); \
284 MP_FREE(_m->v); \
285 _m->v = _v; \
286 _m->vl = _v + _len; \
287 _m->sz = _sz; \
288} while (0)
d03ab969 289
d3409d5e 290/* --- @mp_ensure@ --- *
d03ab969 291 *
d3409d5e 292 * Arguments: @mp *m@ = pointer to a multiprecision integer
293 * @size_t sz@ = required size
d03ab969 294 *
295 * Returns: ---
296 *
d3409d5e 297 * Use: Ensures that the integer has enough space for @sz@ digits.
298 * The value is not changed.
d03ab969 299 */
300
d3409d5e 301extern void mp_ensure(mp */*m*/, size_t /*sz*/);
302
303#define MP_ENSURE(m, ssz) do { \
304 mp *_mm = (m); \
305 size_t _ssz = (ssz); \
306 size_t _len = MP_LEN(_mm); \
307 if (_ssz > _mm->sz) \
308 MP_RESIZE(_mm, _ssz); \
309 if (!(_mm->f & MP_UNDEF) && _ssz > _len) { \
310 memset(_mm->vl, 0, MPWS(_ssz - _len)); \
311 _mm->vl = _mm->v + _ssz; \
312 } \
313} while (0)
d03ab969 314
d3409d5e 315/* --- @mp_modify@ --- *
d03ab969 316 *
d3409d5e 317 * Arguments: @mp *m@ = pointer to a multiprecision integer
318 * @size_t sz@ = size required
d03ab969 319 *
d3409d5e 320 * Returns: Pointer to the integer (possibly different).
d03ab969 321 *
d3409d5e 322 * Use: Prepares an integer to be overwritten. It's split off from
323 * other references to the same integer, and sufficient space is
324 * allocated.
d03ab969 325 */
326
d3409d5e 327extern mp *mp_modify(mp */*m*/, size_t /*sz*/);
d03ab969 328
d3409d5e 329#define MP_MODIFY(m, sz) do { \
330 size_t _rq = (sz); \
331 mp *_m = (m); \
332 if (_m == MP_NEW) \
333 _m = mp_create(_rq); \
334 else { \
335 MP_SPLIT(_m); \
336 MP_ENSURE(_m, _rq); \
337 } \
338 _m->vl = _m->v + _rq; \
339 (m) = _m; \
340} while (0)
341
342/*----- Size manipulation -------------------------------------------------*/
343
344/* --- @mp_shrink@ --- *
d03ab969 345 *
d3409d5e 346 * Arguments: @mp *m@ = pointer to a multiprecision integer
d03ab969 347 *
348 * Returns: ---
349 *
d3409d5e 350 * Use: Reduces the recorded length of an integer. This doesn't
351 * reduce the amount of memory used, although it can improve
352 * performance a bit. To reduce memory, use @mp_minimize@
353 * instead. This can't change the value of an integer, and is
354 * therefore safe to use even when there are multiple
355 * references.
d03ab969 356 */
357
d3409d5e 358extern void mp_shrink(mp */*m*/);
d03ab969 359
d3409d5e 360#define MP_SHRINK(m) do { \
361 mp *_mm = (m); \
362 MPX_SHRINK(_mm->v, _mm->vl); \
363 if (!MP_LEN(_mm)) \
364 _mm->f &= ~MP_NEG; \
365} while (0)
366
367/* --- @mp_minimize@ --- *
d03ab969 368 *
d3409d5e 369 * Arguments: @mp *m@ = pointer to a multiprecision integer
d03ab969 370 *
371 * Returns: ---
372 *
d3409d5e 373 * Use: Reduces the amount of memory an integer uses. It's best to
374 * do this to numbers which aren't going to change in the
375 * future.
d03ab969 376 */
377
d3409d5e 378extern void mp_minimize(mp */*m*/);
d03ab969 379
d3409d5e 380/*----- Bit scanning ------------------------------------------------------*/
d03ab969 381
d3409d5e 382#ifndef MPSCAN_H
383# include "mpscan.h"
384#endif
385
386/* --- @mp_scan@ --- *
d03ab969 387 *
d3409d5e 388 * Arguments: @mpscan *sc@ = pointer to bitscanner block
389 * @const mp *m@ = pointer to a multiprecision integer
d03ab969 390 *
391 * Returns: ---
392 *
d3409d5e 393 * Use: Initializes a bitscanner on a multiprecision integer.
d03ab969 394 */
395
d3409d5e 396extern void mp_scan(mpscan */*sc*/, const mp */*m*/);
397
398#define MP_SCAN(sc, m) do { \
399 mp *_mm = (m); \
400 mpscan *_sc = (sc); \
401 MPSCAN_INITX(_sc, _mm->v, _mm->vl); \
402} while (0)
403
404/* --- Other bitscanning aliases --- */
405
406#define mp_step mpscan_step
407#define mp_bit mpscan_bit
408
409#define MP_STEP MPSCAN_STEP
410#define MP_BIT MPSCAN_BIT
411
412/*----- Loading and storing -----------------------------------------------*/
d03ab969 413
d3409d5e 414/* --- @mp_octets@ --- *
d03ab969 415 *
d3409d5e 416 * Arguments: @const mp *m@ = a multiprecision integer
d03ab969 417 *
d3409d5e 418 * Returns: The number of octets required to represent @m@.
d03ab969 419 *
d3409d5e 420 * Use: Calculates the external storage required for a multiprecision
421 * integer.
d03ab969 422 */
423
d3409d5e 424extern size_t mp_octets(const mp *m);
d03ab969 425
d3409d5e 426/* --- @mp_loadl@ --- *
d03ab969 427 *
d3409d5e 428 * Arguments: @mp *d@ = destination
429 * @const void *pv@ = pointer to source data
430 * @size_t sz@ = size of the source data
d03ab969 431 *
d3409d5e 432 * Returns: Resulting multiprecision number.
d03ab969 433 *
d3409d5e 434 * Use: Loads a multiprecision number from an array of octets. The
435 * first byte in the array is the least significant. More
436 * formally, if the bytes are %$b_0, b_1, \ldots, b_{n-1}$%
437 * then the result is %$N = \sum_{0 \le i < n} b_i 2^{8i}$%.
d03ab969 438 */
439
d3409d5e 440extern mp *mp_loadl(mp */*d*/, const void */*pv*/, size_t /*sz*/);
d03ab969 441
d3409d5e 442/* --- @mp_storel@ --- *
443 *
444 * Arguments: @const mp *m@ = source
445 * @void *pv@ = pointer to output array
446 * @size_t sz@ = size of the output array
447 *
448 * Returns: ---
449 *
450 * Use: Stores a multiprecision number in an array of octets. The
451 * first byte in the array is the least significant. If the
452 * array is too small to represent the number, high-order bits
453 * are truncated; if the array is too large, high order bytes
454 * are filled with zeros. More formally, if the number is
455 * %$N = \sum{0 \le i} b_i 2^{8i}$% where %$0 \le b_i < 256$%,
456 * then the array is %$b_0, b_1, \ldots, b_{n-1}$%.
457 */
d03ab969 458
d3409d5e 459extern void mp_storel(const mp */*m*/, void */*pv*/, size_t /*sz*/);
d03ab969 460
d3409d5e 461/* --- @mp_loadb@ --- *
d03ab969 462 *
d3409d5e 463 * Arguments: @mp *d@ = destination
464 * @const void *pv@ = pointer to source data
465 * @size_t sz@ = size of the source data
d03ab969 466 *
d3409d5e 467 * Returns: Resulting multiprecision number.
d03ab969 468 *
d3409d5e 469 * Use: Loads a multiprecision number from an array of octets. The
470 * last byte in the array is the least significant. More
471 * formally, if the bytes are %$b_{n-1}, b_{n-2}, \ldots, b_0$%
472 * then the result is %$N = \sum_{0 \le i < n} b_i 2^{8i}$%.
d03ab969 473 */
474
d3409d5e 475extern mp *mp_loadb(mp */*d*/, const void */*pv*/, size_t /*sz*/);
d03ab969 476
d3409d5e 477/* --- @mp_storeb@ --- *
d03ab969 478 *
d3409d5e 479 * Arguments: @const mp *m@ = source
480 * @void *pv@ = pointer to output array
481 * @size_t sz@ = size of the output array
d03ab969 482 *
483 * Returns: ---
484 *
d3409d5e 485 * Use: Stores a multiprecision number in an array of octets. The
486 * last byte in the array is the least significant. If the
487 * array is too small to represent the number, high-order bits
488 * are truncated; if the array is too large, high order bytes
489 * are filled with zeros. More formally, if the number is
490 * %$N = \sum{0 \le i} b_i 2^{8i}$% where %$0 \le b_i < 256$%,
491 * then the array is %$b_{n-1}, b_{n-2}, \ldots, b_0$%.
d03ab969 492 */
493
d3409d5e 494extern void mp_storeb(const mp */*m*/, void */*pv*/, size_t /*sz*/);
d03ab969 495
d3409d5e 496/*----- Simple arithmetic -------------------------------------------------*/
d03ab969 497
d3409d5e 498/* --- @mp_2c@ --- *
d03ab969 499 *
d3409d5e 500 * Arguments: @mp *d@ = destination
501 * @mp *a@ = source
d03ab969 502 *
d3409d5e 503 * Returns: Result, @a@ converted to two's complement notation.
504 */
505
506extern mp *mp_2c(mp */*d*/, mp */*a*/);
507
508/* --- @mp_sm@ --- *
509 *
510 * Arguments: @mp *d@ = destination
511 * @mp *a@ = source
d03ab969 512 *
d3409d5e 513 * Returns: Result, @a@ converted to the native signed-magnitude
514 * notation.
d03ab969 515 */
516
d3409d5e 517extern mp *mp_sm(mp */*d*/, mp */*a*/);
d03ab969 518
d3409d5e 519/* --- @mp_lsl@ --- *
d03ab969 520 *
d3409d5e 521 * Arguments: @mp *d@ = destination
522 * @const mp *a@ = source
523 * @size_t n@ = number of bits to move
d03ab969 524 *
d3409d5e 525 * Returns: Result, @a@ shifted left by @n@.
526 */
527
528extern mp *mp_lsl(mp */*d*/, const mp */*a*/, size_t /*n*/);
529
530/* --- @mp_lsr@ --- *
531 *
532 * Arguments: @mp *d@ = destination
533 * @const mp *a@ = source
534 * @size_t n@ = number of bits to move
d03ab969 535 *
d3409d5e 536 * Returns: Result, @a@ shifted left by @n@.
d03ab969 537 */
538
d3409d5e 539extern mp *mp_lsr(mp */*d*/, const mp */*a*/, size_t /*n*/);
d03ab969 540
d3409d5e 541/* --- @mp_cmp@ --- *
d03ab969 542 *
d3409d5e 543 * Arguments: @const mp *a, *b@ = two numbers
d03ab969 544 *
d3409d5e 545 * Returns: Less than, equal to or greater than zero, according to
546 * whether @a@ is less than, equal to or greater than @b@.
547 */
548
549extern int mp_cmp(const mp */*a*/, const mp */*b*/);
550
551#define MP_CMP(a, op, b) (mp_cmp((a), (b)) op 0)
552
553/* --- @mp_add@ --- *
d03ab969 554 *
d3409d5e 555 * Arguments: @mp *d@ = destination
556 * @const mp *a, *b@ = sources
557 *
558 * Returns: Result, @a@ added to @b@.
d03ab969 559 */
560
d3409d5e 561extern mp *mp_add(mp */*d*/, const mp */*a*/, const mp */*b*/);
d03ab969 562
d3409d5e 563/* --- @mp_sub@ --- *
564 *
565 * Arguments: @mp *d@ = destination
566 * @const mp *a, *b@ = sources
567 *
568 * Returns: Result, @b@ subtracted from @a@.
569 */
d03ab969 570
d3409d5e 571extern mp *mp_sub(mp */*d*/, const mp */*a*/, const mp */*b*/);
572
573/* --- @mp_mul@ --- *
d03ab969 574 *
d3409d5e 575 * Arguments: @mp *d@ = destination
576 * @const mp *a, *b@ = sources
d03ab969 577 *
d3409d5e 578 * Returns: Result, @a@ multiplied by @b@.
579 */
580
581extern mp *mp_mul(mp */*d*/, const mp */*a*/, const mp */*b*/);
582
583/* --- @mp_sqr@ --- *
584 *
585 * Arguments: @mp *d@ = destination
586 * @const mp *a@ = source
587 *
588 * Returns: Result, @a@ squared.
589 */
590
591extern mp *mp_sqr(mp */*d*/, const mp */*a*/);
592
593/* --- @mp_div@ --- *
d03ab969 594 *
d3409d5e 595 * Arguments: @mp **qq, **rr@ = destination, quotient and remainder
596 * @const mp *a, *b@ = sources
597 *
598 * Use: Calculates the quotient and remainder when @a@ is divided by
599 * @b@.
d03ab969 600 */
601
d3409d5e 602extern void mp_div(mp **/*qq*/, mp **/*rr*/,
603 const mp */*a*/, const mp */*b*/);
604
605/*----- More advanced algorithms ------------------------------------------*/
d03ab969 606
d3409d5e 607/* --- @mp_gcd@ --- *
d03ab969 608 *
d3409d5e 609 * Arguments: @mp **gcd, **xx, **yy@ = where to write the results
610 * @mp *a, *b@ = sources (must be nonzero)
d03ab969 611 *
612 * Returns: ---
613 *
d3409d5e 614 * Use: Calculates @gcd(a, b)@, and two numbers @x@ and @y@ such that
615 * @ax + by = gcd(a, b)@. This is useful for computing modular
616 * inverses. Neither @a@ nor @b@ may be zero. Note that,
617 * unlike @mp_div@ for example, it is not possible to specify
618 * explicit destinations -- new MPs are always allocated.
d03ab969 619 */
620
d3409d5e 621extern void mp_gcd(mp **/*gcd*/, mp **/*xx*/, mp **/*yy*/,
622 mp */*a*/, mp */*b*/);
623
624/*----- Test harness support ----------------------------------------------*/
625
626#include <mLib/testrig.h>
627
628#ifndef MPTEXT_H
629# include "mptext.h"
630#endif
631
632extern const test_type type_mp;
d03ab969 633
634/*----- That's all, folks -------------------------------------------------*/
635
636#ifdef __cplusplus
637 }
638#endif
639
640#endif