1 /* Simple libgmp command, see...
2
3 http://www.gnu.org/manual/gmp/html_node/Converting-Integers.html
4 http://www.gnu.org/manual/gmp/html_node/Integer-Comparisons.html
5 http://www.gnu.org/manual/gmp/html_node/Miscellaneous-Integer-Functions.html
6 http://www.gnu.org/manual/gmp/html_node/Integer-Arithmetic.html
7
8 http://www.gnu.org/manual/gmp/html_node/Formatted-Output-Strings.html
9
10
11 ...it prints the factorial of the first argument, you can check the output
12 against...
13
14 http://www.newdream.net/~sage/old/numbers/fact.htm
15
16 ...or a POSIX "bc" via. ...
17
18 define f (x) {
19 if (x <= 1) return (1);
20 return (f(x-1) * x);
21 }
22
23 */
24
25 /* we only need output here, so turn off other IO functions */
26 #define EX_UTILS_NO_USE_INPUT 1
27 #define EX_UTILS_NO_USE_OPEN 1
28 #include "ex_utils.h" /* helper functions */
29
30 #include <limits.h>
31 #include <gmp.h>
32 #include <locale.h>
33
34 /* if this is enabled we go through the factorials twice, which means we do
35 * almost twice as much work ... the output is more readable for small values
36 * though */
37 #define EX_GMP_FACT_USE_FIELDWIDTH 1
38
39 /* if this is enabled we add the malloc()d string as a reference,
40 * which saves doing an extra copy. */
41 #define EX_GMP_FACT_USE_REFS 1
42
43 /* This is the custom formatter.
44 * Note that this deals with grouping unlike the gmp_*printf() calls */
ex__usr_mpz_cb(Vstr_base * base,size_t pos,Vstr_fmt_spec * spec,const mpz_t val)45 static int ex__usr_mpz_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec,
46 /* gmp args, need to be in paramter list */
47 const mpz_t val)
48 {
49 int flags = VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NUM; /* it's a number */
50 size_t len = 0;
51 int ret = FALSE;
52 char ui_buf[sizeof(unsigned long) * CHAR_BIT];
53 char *buf = NULL;
54 char *out_buf = ui_buf;
55
56 if (mpz_sgn(val) == -1) /* it's a negative number */
57 flags |= VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NEG;
58
59 if (mpz_fits_ulong_p(val)) /* it's a simple number */
60 len = vstr_sc_conv_num10_ulong(ui_buf, sizeof(ui_buf), mpz_get_ui(val));
61 else /* bignum, so get libgmp to export it as a string */
62 {
63 len = mpz_sizeinbase(val, 10); /* doesn't include minus sign */
64 out_buf = buf = mpz_get_str(NULL, 10, val); /* dies on malloc error */
65
66 if (mpz_sgn(val) == -1) ++out_buf; /* skip the minus sign */
67 if (!out_buf[len - 1]) --len; /* see documentation for mpz_sizeinbase() */
68 }
69
70 ASSERT(strlen(out_buf) == len);
71
72 /* this deals with things like having the the zero flag (Ie. %0d), or the
73 * plus flag (Ie. %+d) or right shifted field widths */
74 if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &len, flags))
75 goto mem_fail;
76
77 if (spec->fmt_quote) /* add number including grouping */
78 ret = vstr_sc_add_grpnum_buf(base, pos, out_buf, len);
79 else if (!EX_GMP_FACT_USE_REFS || !buf) /* just add the number */
80 ret = vstr_add_buf(base, pos, out_buf, len);
81 else
82 { /* create a reference to avoid copying data,
83 * assumes mp_set_memory_functions() hasn't been called */
84 Vstr_ref *ref = vstr_ref_make_ptr(buf, vstr_ref_cb_free_ptr_ref);
85
86 if (!ref)
87 goto mem_fail;
88
89 ret = vstr_add_ref(base, pos, ref, out_buf - buf, len);
90
91 buf = NULL; /* memory is free'd when the reference is used up */
92
93 /* remove our reference, if !ret then this will free buf */
94 vstr_ref_del(ref);
95 }
96
97 /* this deals with left shifted field widths */
98 if (!ret || !vstr_sc_fmt_cb_end(base, pos, spec, len))
99 goto mem_fail;
100
101 free(buf);
102
103 return (TRUE);
104
105 mem_fail:
106 free(buf);
107 return (FALSE);
108 }
109
110 /* we need to jump though an extra function due to the way GMP defines the
111 * mpz_t type */
ex_usr_mpz_cb(Vstr_base * base,size_t pos,Vstr_fmt_spec * spec)112 static int ex_usr_mpz_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec)
113 {
114 void *mpz = VSTR_FMT_CB_ARG_PTR(spec, 0);
115
116 return (ex__usr_mpz_cb(base, pos, spec, mpz));
117 }
118
119 /* The code to calculate the factorial... */
ex_gmp_fact(mpz_t bignum_ret,mpz_t bignum_cnt,mpz_t bignum_for,int out,Vstr_base * s1,int ret_max_sz,int cnt_max_sz)120 static void ex_gmp_fact(mpz_t bignum_ret, mpz_t bignum_cnt, mpz_t bignum_for,
121 int out, Vstr_base *s1, int ret_max_sz, int cnt_max_sz)
122 {
123 while (mpz_cmp(bignum_cnt, bignum_for) <= 0)
124 {
125 int w_state = IO_OK;
126
127 mpz_mul(bignum_ret, bignum_ret, bignum_cnt);
128
129 if (out)
130 { /* output the current values */
131 vstr_add_fmt(s1, s1->len, "$'*<MPZ:%*p>%s %c $'*<MPZ:%*p>\n",
132 cnt_max_sz, (void *)bignum_cnt, "!", '=',
133 ret_max_sz, (void *)bignum_ret);
134
135 if (s1->conf->malloc_bad)
136 errno = ENOMEM, err(EXIT_FAILURE, "Add string data");
137
138 w_state = io_put(s1, STDOUT_FILENO);
139
140 if ((w_state == IO_BLOCK) && (s1->len > EX_MAX_W_DATA_INCORE))
141 io_block(-1, STDOUT_FILENO);
142 }
143
144 mpz_add_ui(bignum_cnt, bignum_cnt, 1);
145 }
146 }
147
main(int argc,char * argv[])148 int main(int argc, char *argv[])
149 {
150 Vstr_base *s1 = ex_init(NULL);
151 mpz_t bignum_ret;
152 mpz_t bignum_for;
153 mpz_t bignum_cnt;
154 int cnt_max_sz = 1;
155 int ret_max_sz = 1;
156 const char *loc_num_name = NULL;
157
158 if (argc < 2)
159 errx(EXIT_FAILURE, "No count specified");
160
161 /* setup the custom format specifier for GMP ... see above
162 */
163 vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$');
164 vstr_fmt_add(s1->conf, "<MPZ:%p>", ex_usr_mpz_cb,
165 VSTR_TYPE_FMT_PTR_VOID, VSTR_TYPE_FMT_END);
166 /* second version so we can give a field width */
167 vstr_fmt_add(s1->conf, "<MPZ:%*p>", ex_usr_mpz_cb,
168 VSTR_TYPE_FMT_PTR_VOID, VSTR_TYPE_FMT_END);
169
170 /* get the numeric locale name... */
171 setlocale(LC_ALL, "");
172 loc_num_name = setlocale(LC_NUMERIC, NULL);
173
174 /* change grouping, from locale, to make numbers more readable */
175 if (!vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_LOC_CSTR_AUTO_NAME_NUMERIC,
176 loc_num_name))
177 errx(EXIT_FAILURE, "Couldn't change numeric locale info");
178
179 mpz_init_set_str(bignum_for, argv[1], 0);
180 mpz_init_set_str(bignum_ret, "1", 0);
181 mpz_init_set_str(bignum_cnt, "1", 0);
182
183 if (EX_GMP_FACT_USE_FIELDWIDTH)
184 { /* find out the max length of the for values... */
185
186 /* value of the count... */
187 vstr_add_fmt(s1, s1->len, "$'<MPZ:%p>", (void *)bignum_for);
188 if (s1->conf->malloc_bad) /* this checks a bunch of things above */
189 errno = ENOMEM, err(EXIT_FAILURE, "Add string data");
190
191 cnt_max_sz = s1->len; vstr_del(s1, 1, s1->len);
192
193 /* work out the result */
194 if (mpz_fits_ulong_p(bignum_for))
195 mpz_fac_ui(bignum_ret, mpz_get_ui(bignum_for));
196 else
197 ex_gmp_fact(bignum_ret, bignum_cnt, bignum_for, FALSE, NULL, 0, 0);
198
199 /* value of the result... */
200 if (!vstr_add_fmt(s1, s1->len, "$'<MPZ:%p>", (void *)bignum_ret))
201 errno = ENOMEM, err(EXIT_FAILURE, "Add string data");
202
203 ret_max_sz = s1->len; vstr_del(s1, 1, s1->len);
204
205 /* reinit, so we can print everything... */
206 mpz_init_set_str(bignum_ret, "1", 0);
207 mpz_init_set_str(bignum_cnt, "1", 0);
208 }
209
210 /* do the output... */
211 if (mpz_sgn(bignum_for) >= 0) /* special case 0! */
212 if (!vstr_add_fmt(s1, s1->len, "%*u%s %c %*u\n\n",
213 cnt_max_sz, 0, "!", '=', ret_max_sz, 1))
214 errno = ENOMEM, err(EXIT_FAILURE, "Add string data");
215
216 ex_gmp_fact(bignum_ret, bignum_cnt, bignum_for,
217 TRUE, s1, ret_max_sz, cnt_max_sz);
218
219 io_put_all(s1, STDOUT_FILENO);
220
221 exit (ex_exit(s1, NULL));
222 }
223