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