X-Git-Url: https://git.distorted.org.uk/u/mdw/catacomb/blobdiff_plain/42684bdb717221e41ad97cfefed2242e0553f835..c65df27983057ec76ed0e72bb370f9a5ae7dad28:/mpx.c diff --git a/mpx.c b/mpx.c index e63750f..e122760 100644 --- a/mpx.c +++ b/mpx.c @@ -1,6 +1,6 @@ /* -*-c-*- * - * $Id: mpx.c,v 1.6 1999/11/20 22:43:44 mdw Exp $ + * $Id: mpx.c,v 1.20 2004/04/08 01:36:15 mdw Exp $ * * Low-level multiprecision arithmetic * @@ -27,31 +27,6 @@ * MA 02111-1307, USA. */ -/*----- Revision history --------------------------------------------------* - * - * $Log: mpx.c,v $ - * Revision 1.6 1999/11/20 22:43:44 mdw - * Integrate testing for MPX routines. - * - * Revision 1.5 1999/11/20 22:23:27 mdw - * Add function versions of some low-level macros with wider use. - * - * Revision 1.4 1999/11/17 18:04:09 mdw - * Add two's-complement functionality. Improve mpx_udiv a little by - * performing the multiplication of the divisor by q with the subtraction - * from r. - * - * Revision 1.3 1999/11/13 01:57:31 mdw - * Remove stray debugging code. - * - * Revision 1.2 1999/11/13 01:50:59 mdw - * Multiprecision routines finished and tested. - * - * Revision 1.1 1999/09/03 08:41:12 mdw - * Initial import. - * - */ - /*----- Header files ------------------------------------------------------*/ #include @@ -63,6 +38,7 @@ #include "mptypes.h" #include "mpx.h" +#include "bitops.h" /*----- Loading and storing -----------------------------------------------*/ @@ -218,6 +194,186 @@ void mpx_loadb(mpw *v, mpw *vl, const void *pp, size_t sz) MPX_ZERO(v, vl); } +/* --- @mpx_storel2cn@ --- * + * + * Arguments: @const mpw *v, *vl@ = base and limit of source vector + * @void *pp@ = pointer to octet array + * @size_t sz@ = size of octet array + * + * Returns: --- + * + * Use: Stores a negative MP in an octet array, least significant + * octet first, as two's complement. High-end octets are + * silently discarded if there isn't enough space for them. + * This obviously makes the output bad. + */ + +void mpx_storel2cn(const mpw *v, const mpw *vl, void *pp, size_t sz) +{ + unsigned c = 1; + unsigned b = 0; + mpw n, w = 0; + octet *p = pp, *q = p + sz; + unsigned bits = 0; + + while (p < q) { + if (bits < 8) { + if (v >= vl) { + b = w; + break; + } + n = *v++; + b = w | n << bits; + w = n >> (8 - bits); + bits += MPW_BITS - 8; + } else { + b = w; + w >>= 8; + bits -= 8; + } + b = U8(~b + c); + c = c && !b; + *p++ = b; + } + while (p < q) { + b = U8(~b + c); + c = c && !b; + *p++ = b; + b = 0; + } +} + +/* --- @mpx_loadl2cn@ --- * + * + * Arguments: @mpw *v, *vl@ = base and limit of destination vector + * @const void *pp@ = pointer to octet array + * @size_t sz@ = size of octet array + * + * Returns: --- + * + * Use: Loads a negative MP in an octet array, least significant + * octet first, as two's complement. High-end octets are + * ignored if there isn't enough space for them. This probably + * means you made the wrong choice coming here. + */ + +void mpx_loadl2cn(mpw *v, mpw *vl, const void *pp, size_t sz) +{ + unsigned n; + unsigned c = 1; + mpw w = 0; + const octet *p = pp, *q = p + sz; + unsigned bits = 0; + + if (v >= vl) + return; + while (p < q) { + n = U8(~(*p++) + c); + c = c && !n; + w |= n << bits; + bits += 8; + if (bits >= MPW_BITS) { + *v++ = MPW(w); + w = n >> (MPW_BITS - bits + 8); + bits -= MPW_BITS; + if (v >= vl) + return; + } + } + *v++ = w; + MPX_ZERO(v, vl); +} + +/* --- @mpx_storeb2cn@ --- * + * + * Arguments: @const mpw *v, *vl@ = base and limit of source vector + * @void *pp@ = pointer to octet array + * @size_t sz@ = size of octet array + * + * Returns: --- + * + * Use: Stores a negative MP in an octet array, most significant + * octet first, as two's complement. High-end octets are + * silently discarded if there isn't enough space for them, + * which probably isn't what you meant. + */ + +void mpx_storeb2cn(const mpw *v, const mpw *vl, void *pp, size_t sz) +{ + mpw n, w = 0; + unsigned b = 0; + unsigned c = 1; + octet *p = pp, *q = p + sz; + unsigned bits = 0; + + while (q > p) { + if (bits < 8) { + if (v >= vl) { + b = w; + break; + } + n = *v++; + b = w | n << bits; + w = n >> (8 - bits); + bits += MPW_BITS - 8; + } else { + b = w; + w >>= 8; + bits -= 8; + } + b = U8(~b + c); + c = c && !b; + *--q = b; + } + while (q > p) { + b = ~b + c; + c = c && !(b & 0xff); + *--q = b; + b = 0; + } +} + +/* --- @mpx_loadb2cn@ --- * + * + * Arguments: @mpw *v, *vl@ = base and limit of destination vector + * @const void *pp@ = pointer to octet array + * @size_t sz@ = size of octet array + * + * Returns: --- + * + * Use: Loads a negative MP in an octet array, most significant octet + * first as two's complement. High-end octets are ignored if + * there isn't enough space for them. This probably means you + * chose this function wrongly. + */ + +void mpx_loadb2cn(mpw *v, mpw *vl, const void *pp, size_t sz) +{ + unsigned n; + unsigned c = 1; + mpw w = 0; + const octet *p = pp, *q = p + sz; + unsigned bits = 0; + + if (v >= vl) + return; + while (q > p) { + n = U8(~(*--q) + c); + c = c && !n; + w |= n << bits; + bits += 8; + if (bits >= MPW_BITS) { + *v++ = MPW(w); + w = n >> (MPW_BITS - bits + 8); + bits -= MPW_BITS; + if (v >= vl) + return; + } + } + *v++ = w; + MPX_ZERO(v, vl); +} + /*----- Logical shifting --------------------------------------------------*/ /* --- @mpx_lsl@ --- * @@ -268,8 +424,12 @@ void mpx_lsl(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, size_t n) /* --- Handle a shift by a multiple of the word size --- */ if (nb == 0) { - MPX_COPY(dv + nw, dvl, av, avl); - memset(dv, 0, MPWS(nw)); + if (nw >= dvl - dv) + MPX_ZERO(dv, dvl); + else { + MPX_COPY(dv + nw, dvl, av, avl); + memset(dv, 0, MPWS(nw)); + } } /* --- And finally the difficult case --- * @@ -313,6 +473,104 @@ void mpx_lsl(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, size_t n) done:; } +/* --- @mpx_lslc@ --- * + * + * Arguments: @mpw *dv, *dvl@ = destination vector base and limit + * @const mpw *av, *avl@ = source vector base and limit + * @size_t n@ = number of bit positions to shift by + * + * Returns: --- + * + * Use: Performs a logical shift left operation on an integer, only + * it fills in the bits with ones instead of zeroes. + */ + +void mpx_lslc(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, size_t n) +{ + size_t nw; + unsigned nb; + + /* --- Trivial special case --- */ + + if (n == 0) + MPX_COPY(dv, dvl, av, avl); + + /* --- Single bit shifting --- */ + + else if (n == 1) { + mpw w = 1; + while (av < avl) { + mpw t; + if (dv >= dvl) + goto done; + t = *av++; + *dv++ = MPW((t << 1) | w); + w = t >> (MPW_BITS - 1); + } + if (dv >= dvl) + goto done; + *dv++ = MPW(w); + MPX_ZERO(dv, dvl); + goto done; + } + + /* --- Break out word and bit shifts for more sophisticated work --- */ + + nw = n / MPW_BITS; + nb = n % MPW_BITS; + + /* --- Handle a shift by a multiple of the word size --- */ + + if (nb == 0) { + if (nw >= dvl - dv) + MPX_ONE(dv, dvl); + else { + MPX_COPY(dv + nw, dvl, av, avl); + MPX_ONE(dv, dv + nw); + } + } + + /* --- And finally the difficult case --- * + * + * This is a little convoluted, because I have to start from the end and + * work backwards to avoid overwriting the source, if they're both the same + * block of memory. + */ + + else { + mpw w; + size_t nr = MPW_BITS - nb; + size_t dvn = dvl - dv; + size_t avn = avl - av; + + if (dvn <= nw) { + MPX_ONE(dv, dvl); + goto done; + } + + if (dvn > avn + nw) { + size_t off = avn + nw + 1; + MPX_ZERO(dv + off, dvl); + dvl = dv + off; + w = 0; + } else { + avl = av + dvn - nw; + w = *--avl << nb; + } + + while (avl > av) { + mpw t = *--avl; + *--dvl = (t >> nr) | w; + w = t << nb; + } + + *--dvl = (MPW_MAX >> nr) | w; + MPX_ONE(dv, dvl); + } + +done:; +} + /* --- @mpx_lsr@ --- * * * Arguments: @mpw *dv, *dvl@ = destination vector base and limit @@ -337,7 +595,7 @@ void mpx_lsr(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, size_t n) /* --- Single bit shifting --- */ else if (n == 1) { - mpw w = *av++ >> 1; + mpw w = av < avl ? *av++ >> 1 : 0; while (av < avl) { mpw t; if (dv >= dvl) @@ -360,8 +618,12 @@ void mpx_lsr(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, size_t n) /* --- Handle a shift by a multiple of the word size --- */ - if (nb == 0) - MPX_COPY(dv, dvl, av + nw, avl); + if (nb == 0) { + if (nw >= avl - av) + MPX_ZERO(dv, dvl); + else + MPX_COPY(dv, dvl, av + nw, avl); + } /* --- And finally the difficult case --- */ @@ -370,7 +632,7 @@ void mpx_lsr(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, size_t n) size_t nr = MPW_BITS - nb; av += nw; - w = *av++; + w = av < avl ? *av++ : 0; while (av < avl) { mpw t; if (dv >= dvl) @@ -388,6 +650,48 @@ void mpx_lsr(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, size_t n) done:; } +/*----- Bitwise operations ------------------------------------------------*/ + +/* --- @mpx_bitop@ --- * + * + * Arguments: @mpw *dv, *dvl@ = destination vector + * @const mpw *av, *avl@ = first source vector + * @const mpw *bv, *bvl@ = second source vector + * + * Returns: --- + * + * Use; Provides the dyadic boolean functions. + */ + +#define MPX_BITBINOP(string) \ + \ +void mpx_bit##string(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, \ + const mpw *bv, const mpw *bvl) \ +{ \ + MPX_SHRINK(av, avl); \ + MPX_SHRINK(bv, bvl); \ + \ + while (dv < dvl) { \ + mpw a, b; \ + a = (av < avl) ? *av++ : 0; \ + b = (bv < bvl) ? *bv++ : 0; \ + *dv++ = B##string(a, b); \ + } \ +} + +MPX_DOBIN(MPX_BITBINOP) + +void mpx_not(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl) +{ + MPX_SHRINK(av, avl); + + while (dv < dvl) { + mpw a; + a = (av < avl) ? *av++ : 0; + *dv++ = ~a; + } +} + /*----- Unsigned arithmetic -----------------------------------------------*/ /* --- @mpx_2c@ --- * @@ -414,6 +718,29 @@ void mpx_2c(mpw *dv, mpw *dvl, const mpw *v, const mpw *vl) MPX_UADDN(dv, dvl, 1); } +/* --- @mpx_ueq@ --- * + * + * Arguments: @const mpw *av, *avl@ = first argument vector base and limit + * @const mpw *bv, *bvl@ = second argument vector base and limit + * + * Returns: Nonzero if the two vectors are equal. + * + * Use: Performs an unsigned integer test for equality. + */ + +int mpx_ueq(const mpw *av, const mpw *avl, const mpw *bv, const mpw *bvl) +{ + MPX_SHRINK(av, avl); + MPX_SHRINK(bv, bvl); + if (avl - av != bvl - bv) + return (0); + while (av < avl) { + if (*av++ != *bv++) + return (0); + } + return (1); +} + /* --- @mpx_ucmp@ --- * * * Arguments: @const mpw *av, *avl@ = first argument vector base and limit @@ -444,7 +771,7 @@ int mpx_ucmp(const mpw *av, const mpw *avl, const mpw *bv, const mpw *bvl) } return (0); } - + /* --- @mpx_uadd@ --- * * * Arguments: @mpw *dv, *dvl@ = destination vector base and limit @@ -495,6 +822,30 @@ void mpx_uadd(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, void mpx_uaddn(mpw *dv, mpw *dvl, mpw n) { MPX_UADDN(dv, dvl, n); } +/* --- @mpx_uaddnlsl@ --- * + * + * Arguments: @mpw *dv, *dvl@ = destination and first argument vector + * @mpw a@ = second argument + * @unsigned o@ = offset in bits + * + * Returns: --- + * + * Use: Computes %$d + 2^o a$%. If the result overflows then + * high-order bits are discarded, as usual. We must have + * @0 < o < MPW_BITS@. + */ + +void mpx_uaddnlsl(mpw *dv, mpw *dvl, mpw a, unsigned o) +{ + mpd x = (mpd)a << o; + + while (x && dv < dvl) { + x += *dv; + *dv++ = MPW(x); + x >>= MPW_BITS; + } +} + /* --- @mpx_usub@ --- * * * Arguments: @mpw *dv, *dvl@ = destination vector base and limit @@ -549,6 +900,33 @@ void mpx_usub(mpw *dv, mpw *dvl, const mpw *av, const mpw *avl, void mpx_usubn(mpw *dv, mpw *dvl, mpw n) { MPX_USUBN(dv, dvl, n); } +/* --- @mpx_uaddnlsl@ --- * + * + * Arguments: @mpw *dv, *dvl@ = destination and first argument vector + * @mpw a@ = second argument + * @unsigned o@ = offset in bits + * + * Returns: --- + * + * Use: Computes %$d + 2^o a$%. If the result overflows then + * high-order bits are discarded, as usual. We must have + * @0 < o < MPW_BITS@. + */ + +void mpx_usubnlsl(mpw *dv, mpw *dvl, mpw a, unsigned o) +{ + mpw b = a >> (MPW_BITS - o); + a <<= o; + + if (dv < dvl) { + mpd x = (mpd)*dv - (mpd)a; + *dv++ = MPW(x); + if (x >> MPW_BITS) + b++; + MPX_USUBN(dv, dvl, b); + } +} + /* --- @mpx_umul@ --- * * * Arguments: @mpw *dv, *dvl@ = destination vector base and limit @@ -743,7 +1121,7 @@ void mpx_udiv(mpw *qv, mpw *qvl, mpw *rv, mpw *rvl, d = dvl[-1]; for (b = MPW_BITS / 2; b; b >>= 1) { - if (d < (MPW_MAX >> b)) { + if (d <= (MPW_MAX >> b)) { d <<= b; norm += b; } @@ -821,21 +1199,19 @@ void mpx_udiv(mpw *qv, mpw *qvl, mpw *rv, mpw *rvl, { mpd yh = (mpd)d * q; - mpd yl = (mpd)dd * q; + mpd yy = (mpd)dd * q; + mpw yl; - if (yl > MPW_MAX) { - yh += yl >> MPW_BITS; - yl &= MPW_MAX; - } + if (yy > MPW_MAX) + yh += yy >> MPW_BITS; + yl = MPW(yy); while (yh > rh || (yh == rh && yl > rrr)) { q--; yh -= d; - if (yl < dd) { - yh++; - yl += MPW_MAX; - } - yl -= dd; + if (yl < dd) + yh--; + yl = MPW(yl - dd); } } @@ -900,6 +1276,35 @@ void mpx_udiv(mpw *qv, mpw *qvl, mpw *rv, mpw *rvl, mpx_lsr(rv, rvl, rv, rvl, norm); } +/* --- @mpx_udivn@ --- * + * + * Arguments: @mpw *qv, *qvl@ = storage for the quotient (may overlap + * dividend) + * @const mpw *rv, *rvl@ = dividend + * @mpw d@ = single-precision divisor + * + * Returns: Remainder after divison. + * + * Use: Performs a single-precision division operation. + */ + +mpw mpx_udivn(mpw *qv, mpw *qvl, const mpw *rv, const mpw *rvl, mpw d) +{ + size_t i; + size_t ql = qvl - qv; + mpd r = 0; + + i = rvl - rv; + while (i > 0) { + i--; + r = (r << MPW_BITS) | rv[i]; + if (i < ql) + qv[i] = r / d; + r %= d; + } + return (MPW(r)); +} + /*----- Test rig ----------------------------------------------------------*/ #ifdef TEST_RIG @@ -1021,6 +1426,84 @@ static int loadstore(dstr *v) return (ok); } +static int twocl(dstr *v) +{ + dstr d = DSTR_INIT; + mpw *m, *ml; + size_t sz; + int ok = 1; + + sz = v[0].len; if (v[1].len > sz) sz = v[1].len; + dstr_ensure(&d, sz); + + sz = MPW_RQ(sz); + m = xmalloc(MPWS(sz)); + ml = m + sz; + + mpx_loadl(m, ml, v[0].buf, v[0].len); + mpx_storel2cn(m, ml, d.buf, v[1].len); + if (memcmp(d.buf, v[1].buf, v[1].len)) { + dumpbits("\n*** storel2cn failed", d.buf, v[1].len); + ok = 0; + } + + mpx_loadl2cn(m, ml, v[1].buf, v[1].len); + mpx_storel(m, ml, d.buf, v[0].len); + if (memcmp(d.buf, v[0].buf, v[0].len)) { + dumpbits("\n*** loadl2cn failed", d.buf, v[0].len); + ok = 0; + } + + if (!ok) { + dumpbits("pos", v[0].buf, v[0].len); + dumpbits("neg", v[1].buf, v[1].len); + } + + free(m); + dstr_destroy(&d); + + return (ok); +} + +static int twocb(dstr *v) +{ + dstr d = DSTR_INIT; + mpw *m, *ml; + size_t sz; + int ok = 1; + + sz = v[0].len; if (v[1].len > sz) sz = v[1].len; + dstr_ensure(&d, sz); + + sz = MPW_RQ(sz); + m = xmalloc(MPWS(sz)); + ml = m + sz; + + mpx_loadb(m, ml, v[0].buf, v[0].len); + mpx_storeb2cn(m, ml, d.buf, v[1].len); + if (memcmp(d.buf, v[1].buf, v[1].len)) { + dumpbits("\n*** storeb2cn failed", d.buf, v[1].len); + ok = 0; + } + + mpx_loadb2cn(m, ml, v[1].buf, v[1].len); + mpx_storeb(m, ml, d.buf, v[0].len); + if (memcmp(d.buf, v[0].buf, v[0].len)) { + dumpbits("\n*** loadb2cn failed", d.buf, v[0].len); + ok = 0; + } + + if (!ok) { + dumpbits("pos", v[0].buf, v[0].len); + dumpbits("neg", v[1].buf, v[1].len); + } + + free(m); + dstr_destroy(&d); + + return (ok); +} + static int lsl(dstr *v) { mpw *a, *al; @@ -1034,7 +1517,7 @@ static int lsl(dstr *v) ALLOC(d, dl, al - a + (n + MPW_BITS - 1) / MPW_BITS); mpx_lsl(d, dl, a, al, n); - if (MPX_UCMP(d, dl, !=, c, cl)) { + if (!mpx_ueq(d, dl, c, cl)) { fprintf(stderr, "\n*** lsl(%i) failed\n", n); dumpmp(" a", a, al); dumpmp("expected", c, cl); @@ -1046,6 +1529,31 @@ static int lsl(dstr *v) return (ok); } +static int lslc(dstr *v) +{ + mpw *a, *al; + int n = *(int *)v[1].buf; + mpw *c, *cl; + mpw *d, *dl; + int ok = 1; + + LOAD(a, al, &v[0]); + LOAD(c, cl, &v[2]); + ALLOC(d, dl, al - a + (n + MPW_BITS - 1) / MPW_BITS); + + mpx_lslc(d, dl, a, al, n); + if (!mpx_ueq(d, dl, c, cl)) { + fprintf(stderr, "\n*** lslc(%i) failed\n", n); + dumpmp(" a", a, al); + dumpmp("expected", c, cl); + dumpmp(" result", d, dl); + ok = 0; + } + + free(a); free(c); free(d); + return (ok); +} + static int lsr(dstr *v) { mpw *a, *al; @@ -1059,7 +1567,7 @@ static int lsr(dstr *v) ALLOC(d, dl, al - a + (n + MPW_BITS - 1) / MPW_BITS + 1); mpx_lsr(d, dl, a, al, n); - if (MPX_UCMP(d, dl, !=, c, cl)) { + if (!mpx_ueq(d, dl, c, cl)) { fprintf(stderr, "\n*** lsr(%i) failed\n", n); dumpmp(" a", a, al); dumpmp("expected", c, cl); @@ -1085,7 +1593,7 @@ static int uadd(dstr *v) ALLOC(d, dl, MAX(al - a, bl - b) + 1); mpx_uadd(d, dl, a, al, b, bl); - if (MPX_UCMP(d, dl, !=, c, cl)) { + if (!mpx_ueq(d, dl, c, cl)) { fprintf(stderr, "\n*** uadd failed\n"); dumpmp(" a", a, al); dumpmp(" b", b, bl); @@ -1112,7 +1620,7 @@ static int usub(dstr *v) ALLOC(d, dl, al - a); mpx_usub(d, dl, a, al, b, bl); - if (MPX_UCMP(d, dl, !=, c, cl)) { + if (!mpx_ueq(d, dl, c, cl)) { fprintf(stderr, "\n*** usub failed\n"); dumpmp(" a", a, al); dumpmp(" b", b, bl); @@ -1139,7 +1647,7 @@ static int umul(dstr *v) ALLOC(d, dl, (al - a) + (bl - b)); mpx_umul(d, dl, a, al, b, bl); - if (MPX_UCMP(d, dl, !=, c, cl)) { + if (!mpx_ueq(d, dl, c, cl)) { fprintf(stderr, "\n*** umul failed\n"); dumpmp(" a", a, al); dumpmp(" b", b, bl); @@ -1164,7 +1672,7 @@ static int usqr(dstr *v) ALLOC(d, dl, 2 * (al - a)); mpx_usqr(d, dl, a, al); - if (MPX_UCMP(d, dl, !=, c, cl)) { + if (!mpx_ueq(d, dl, c, cl)) { fprintf(stderr, "\n*** usqr failed\n"); dumpmp(" a", a, al); dumpmp("expected", c, cl); @@ -1194,8 +1702,8 @@ static int udiv(dstr *v) ALLOC(s, sl, (bl - b) + 1); mpx_udiv(qq, qql, a, al, b, bl, s, sl); - if (MPX_UCMP(qq, qql, !=, q, ql) || - MPX_UCMP(a, al, !=, r, rl)) { + if (!mpx_ueq(qq, qql, q, ql) || + !mpx_ueq(a, al, r, rl)) { fprintf(stderr, "\n*** udiv failed\n"); dumpmp(" divisor", b, bl); dumpmp("expect r", r, rl); @@ -1211,7 +1719,10 @@ static int udiv(dstr *v) static test_chunk defs[] = { { "load-store", loadstore, { &type_hex, 0 } }, + { "2cl", twocl, { &type_hex, &type_hex, } }, + { "2cb", twocb, { &type_hex, &type_hex, } }, { "lsl", lsl, { &type_hex, &type_int, &type_hex, 0 } }, + { "lslc", lslc, { &type_hex, &type_int, &type_hex, 0 } }, { "lsr", lsr, { &type_hex, &type_int, &type_hex, 0 } }, { "uadd", uadd, { &type_hex, &type_hex, &type_hex, 0 } }, { "usub", usub, { &type_hex, &type_hex, &type_hex, 0 } }, @@ -1227,7 +1738,6 @@ int main(int argc, char *argv[]) return (0); } - #endif /*----- That's all, folks -------------------------------------------------*/