New multiprecision integer arithmetic suite.
[u/mdw/catacomb] / mptext.c
1 /* -*-c-*-
2 *
3 * $Id: mptext.c,v 1.1 1999/11/17 18:02:16 mdw Exp $
4 *
5 * Textual representation of multiprecision numbers
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: mptext.c,v $
33 * Revision 1.1 1999/11/17 18:02:16 mdw
34 * New multiprecision integer arithmetic suite.
35 *
36 */
37
38 /*----- Header files ------------------------------------------------------*/
39
40 #include <ctype.h>
41 #include <stdio.h>
42
43 #include <mLib/darray.h>
44
45 #include "mp.h"
46 #include "mptext.h"
47
48 /*----- Data structures ---------------------------------------------------*/
49
50 #ifndef CHARV
51 DA_DECL(charv, char);
52 # define CHARV
53 #endif
54
55 /*----- Main code ---------------------------------------------------------*/
56
57 /* --- @mp_read@ --- *
58 *
59 * Arguments: @mp *m@ = destination multiprecision number
60 * @int radix@ = base to assume for data (or zero to guess)
61 * @const mptext_ops *ops@ = pointer to operations block
62 * @void *p@ = data for the operations block
63 *
64 * Returns: The integer read, or zero if it didn't work.
65 *
66 * Use: Reads an integer from some source. If the @radix@ is
67 * specified, the number is assumed to be given in that radix,
68 * with the letters `a' (either upper- or lower-case) upwards
69 * standing for digits greater than 9. Otherwise, base 10 is
70 * assumed unless the number starts with `0' (octal), `0x' (hex)
71 * or `nnn_' (base `nnn'). An arbitrary amount of whitespace
72 * before the number is ignored.
73 */
74
75 mp *mp_read(mp *m, int radix, const mptext_ops *ops, void *p)
76 {
77 int r;
78 int ch;
79 unsigned f = 0;
80
81 enum {
82 f_neg = 1u,
83 f_ok = 2u
84 };
85
86 /* --- Initialize the destination number --- */
87
88 MP_MODIFY(m, 16);
89 m->vl = m->v;
90 m->f &= ~MP_UNDEF;
91
92 /* --- Read an initial character --- */
93
94 ch = ops->get(p);
95 while (isspace(ch))
96 ch = ops->get(p);
97
98 /* --- Handle an initial sign --- */
99
100 if (ch == '-') {
101 f |= f_neg;
102 ch = ops->get(p);
103 while (isspace(ch))
104 ch = ops->get(p);
105 }
106
107 /* --- If the radix is zero, look for leading zeros --- */
108
109 if (radix)
110 r = -1;
111 else if (ch != '0') {
112 radix = 10;
113 r = 0;
114 } else {
115 ch = ops->get(p);
116 if (ch == 'x') {
117 ch = ops->get(p);
118 radix = 16;
119 } else {
120 radix = 8;
121 f |= f_ok;
122 }
123 r = -1;
124 }
125
126 /* --- Time to start --- */
127
128 for (;; ch = ops->get(p)) {
129 int x;
130
131 /* --- An underscore indicates a numbered base --- */
132
133 if (ch == '_' && r > 0 && r <= 36) {
134 radix = r;
135 m->vl = m->v;
136 r = -1;
137 f &= ~f_ok;
138 continue;
139 }
140
141 /* --- Check that the character is a digit and in range --- */
142
143 if (!isalnum(ch))
144 break;
145 if (ch >= '0' && ch <= '9')
146 x = ch - '0';
147 else {
148 ch = tolower(ch);
149 if (ch >= 'a' && ch <= 'z') /* ASCII dependent! */
150 x = ch - 'a' + 10;
151 else
152 break;
153 }
154
155 /* --- Sort out what to do with the character --- */
156
157 if (x >= 10 && r >= 0)
158 r = -1;
159 if (x >= radix)
160 break;
161
162 if (r >= 0)
163 r = r * 10 + x;
164
165 /* --- Stick the character on the end of my integer --- */
166
167 MP_ENSURE(m, MP_LEN(m) + 1);
168 MPX_UMULN(m->v, m->vl, m->v, m->vl - 1, radix);
169 MPX_UADDN(m->v, m->vl, x);
170 MP_SHRINK(m);
171 f |= f_ok;
172 }
173
174 ops->unget(ch, p);
175
176 /* --- Bail out if the number was bad --- */
177
178 if (!(f & f_ok)) {
179 MP_DROP(m);
180 return (0);
181 }
182
183 /* --- Set the sign and return --- */
184
185 m->f = 0;
186 if (f & f_neg)
187 m->f |= MP_NEG;
188 return (m);
189 }
190
191 /* --- @mp_write@ --- *
192 *
193 * Arguments: @mp *m@ = pointer to a multi-precision integer
194 * @int radix@ = radix to use when writing the number out
195 * @const mptext_ops *ops@ = pointer to an operations block
196 * @void *p@ = data for the operations block
197 *
198 * Returns: Zero if it worked, nonzero otherwise.
199 *
200 * Use: Writes a large integer in textual form.
201 */
202
203 int mp_write(mp *m, int radix, const mptext_ops *ops, void *p)
204 {
205 charv v = DA_INIT;
206 mpw b = radix;
207 mp bb;
208
209 /* --- Set various things up --- */
210
211 m = MP_COPY(m);
212 mp_build(&bb, &b, &b + 1);
213
214 /* --- If the number is negative, sort that out --- */
215
216 if (m->f & MP_NEG) {
217 if (ops->put("-", 1, p))
218 return (EOF);
219 MP_SPLIT(m);
220 m->f &= ~MP_NEG;
221 }
222
223 /* --- Write digits to a temporary array --- */
224
225 do {
226 mp *q = MP_NEW, *r = MP_NEW;
227 int ch;
228 mpw x;
229
230 mp_div(&q, &r, m, &bb);
231 x = r->v[0];
232 if (x < 10)
233 ch = '0' + x;
234 else
235 ch = 'a' + x - 10;
236 DA_UNSHIFT(&v, ch);
237 MP_DROP(m);
238 MP_DROP(r);
239 m = q;
240 } while (MP_CMP(m, !=, MP_ZERO));
241
242 /* --- Finished that --- */
243
244 MP_DROP(m);
245
246 {
247 int rc = ops->put(DA(&v), DA_LEN(&v), p);
248 DA_DESTROY(&v);
249 return (rc ? EOF : 0);
250 }
251 }
252
253 /*----- Test rig ----------------------------------------------------------*/
254
255 #ifdef TEST_RIG
256
257 #include <mLib/testrig.h>
258
259 static int verify(dstr *v)
260 {
261 int ok = 1;
262 int ib = *(int *)v[0].buf, ob = *(int *)v[2].buf;
263 dstr d = DSTR_INIT;
264 mp *m = mp_readdstr(MP_NEW, &v[1], 0, ib);
265 if (m) {
266 if (!ob) {
267 fprintf(stderr, "*** unexpected successful parse\n"
268 "*** input [%i] = %s\n",
269 ib, v[1].buf);
270 mp_writedstr(m, &d, 10);
271 fprintf(stderr, "*** (value = %s)\n", d.buf);
272 ok = 0;
273 } else {
274 mp_writedstr(m, &d, ob);
275 if (d.len != v[3].len || memcmp(d.buf, v[3].buf, d.len) != 0) {
276 fprintf(stderr, "*** failed read or write\n"
277 "*** input [%i] = %s\n"
278 "*** output [%i] = %s\n"
279 "*** expected [%i] = %s\n",
280 ib, v[1].buf, ob, d.buf, ob, v[3].buf);
281 ok = 0;
282 }
283 }
284 mp_drop(m);
285 } else {
286 if (ob) {
287 fprintf(stderr, "*** unexpected parse failure\n"
288 "*** input [%i] = %s\n"
289 "*** expected [%i] = %s\n",
290 ib, v[1].buf, ob, v[3].buf);
291 ok = 0;
292 }
293 }
294
295 dstr_destroy(&d);
296 return (ok);
297 }
298
299 static test_chunk tests[] = {
300 { "mptext", verify,
301 { &type_int, &type_string, &type_int, &type_string, 0 } },
302 { 0, 0, { 0 } }
303 };
304
305 int main(int argc, char *argv[])
306 {
307 sub_init();
308 test_run(argc, argv, tests, SRCDIR "/tests/mptext");
309 return (0);
310 }
311
312 #endif
313
314 /*----- That's all, folks -------------------------------------------------*/