1 /* This is prints US words for a number.
2    http://www.jimloy.com/math/billion.htm
3  */
4 
5 /* we only need output here, so turn off other IO functions */
6 #define EX_UTILS_NO_USE_INPUT 1
7 #define EX_UTILS_NO_USE_OPEN 1
8 #include "ex_utils.h"
9 
10 #include <limits.h>
11 #include <gmp.h>
12 
13 /* do we want to test that it works? */
14 #define EX_WORDS_USE_TST 1
15 
16 #define VAL0_3()    "000"
17 #define VAL0_6()    VAL0_3()VAL0_3()
18 #define VAL0_9()    VAL0_3()VAL0_3()VAL0_3()
19 #define VAL0_10()   "0" VAL0_9()
20 #define VAL0_12()   VAL0_3()VAL0_3()VAL0_3()VAL0_3()
21 #define VAL0_15()   VAL0_3()VAL0_3()VAL0_3()VAL0_3()VAL0_3()
22 #define VAL0_30()   VAL0_15()VAL0_15()
23 #define VAL0_50()   VAL0_10()VAL0_10()VAL0_10()VAL0_10()VAL0_10()
24 #define VAL0_60()   VAL0_30()VAL0_30()
25 #define VAL0_100()  VAL0_50()VAL0_50()
26 #define VAL0_300()  VAL0_100()VAL0_100()VAL0_100()
27 
28 #define WORDS_MAKE(num, out)                        \
29     {num, " " out, sizeof(out), {{0, 0, NULL}}}     \
30 
31 #define WORDS_ADD_WORD()                        \
32     if (s1 && !vstr_add_ptr(s1, pos, scan->str, scan->len))     \
33       return (0);                                               \
34                                                                 \
35     pos += scan->len;                                           \
36     ret += scan->len
37 
38 /* can't be bothered doing the english version ...
39  * we often just use the american version anyway. */
words_conv(Vstr_base * s1,size_t pos,const mpz_t num,int cap,int del)40 static size_t words_conv(Vstr_base *s1, size_t pos, const mpz_t num,
41                          int cap, int del)
42 {
43   size_t orig_pos = pos;
44   struct Words_conv
45   {
46    const char *num;
47    const char *str;
48    const size_t len;
49    mpz_t bignum;
50   };
51   static struct Words_conv conv_m[] =
52     {
53 #if 0
54      WORDS_MAKE("1" VAL0_300() VAL0_3(),           "centillion"),
55      WORDS_MAKE("1" VAL0_60() VAL0_3(),            "vigintillion"),
56      WORDS_MAKE("1" VAL0_60(),                     "novemdecillion"),
57      WORDS_MAKE("1" VAL0_30() VAL0_15() VAL0_12(), "octodecillion"),
58      WORDS_MAKE("1" VAL0_30() VAL0_15() VAL0_9(),  "septendecillion"),
59      WORDS_MAKE("1" VAL0_30() VAL0_15() VAL0_6(),  "sexdecillion"),
60      WORDS_MAKE("1" VAL0_30() VAL0_15() VAL0_3(),  "quindecillion"),
61      WORDS_MAKE("1" VAL0_30() VAL0_15(),           "quattuordecillion"),
62      WORDS_MAKE("1" VAL0_30() VAL0_12(),           "tredecillion"),
63      WORDS_MAKE("1" VAL0_30() VAL0_9(),            "duodecillion"),
64      WORDS_MAKE("1" VAL0_30() VAL0_6(),            "undecillion"),
65      WORDS_MAKE("1" VAL0_30() VAL0_3(),            "decillion"),
66      WORDS_MAKE("1" VAL0_30(),                     "nonillion"),
67      WORDS_MAKE("1" VAL0_15() VAL0_12(),           "octillion"),
68      WORDS_MAKE("1" VAL0_15() VAL0_9(),            "septillion"),
69      WORDS_MAKE("1" VAL0_15() VAL0_6(),            "sextillion"),
70 #endif
71      WORDS_MAKE("1" VAL0_15() VAL0_3(),            "quintillion"),
72      WORDS_MAKE("1" VAL0_15(),                     "quadrillion"),
73      WORDS_MAKE("1" VAL0_12(),                     "trillion"),
74      WORDS_MAKE("1" VAL0_9(),                      "billion"),
75      WORDS_MAKE("1" VAL0_6(),                      "million"),
76      WORDS_MAKE("1" VAL0_3(),                      "thousand"),
77      WORDS_MAKE("100",                             "hundred"),
78     };
79   static struct Words_conv conv_and =
80      WORDS_MAKE("&",                               "and");
81   static struct Words_conv conv_a[] =
82     {
83      WORDS_MAKE("90",                              "ninety"),
84      WORDS_MAKE("80",                              "eighty"),
85      WORDS_MAKE("70",                              "seventy"),
86      WORDS_MAKE("60",                              "sixty"),
87      WORDS_MAKE("50",                              "fifty"),
88      WORDS_MAKE("40",                              "forty"),
89      WORDS_MAKE("30",                              "thirty"),
90      WORDS_MAKE("20",                              "twenty"),
91      WORDS_MAKE("19",                              "nineteen"),
92      WORDS_MAKE("18",                              "eighteen"),
93      WORDS_MAKE("17",                              "seventeen"),
94      WORDS_MAKE("16",                              "sixteen"),
95      WORDS_MAKE("15",                              "fifteen"),
96      WORDS_MAKE("14",                              "fourteen"),
97      WORDS_MAKE("13",                              "thirteen"),
98      WORDS_MAKE("12",                              "twelve"),
99      WORDS_MAKE("11",                              "eleven"),
100      WORDS_MAKE("10",                              "ten"),
101      WORDS_MAKE("9",                               "nine"),
102      WORDS_MAKE("8",                               "eight"),
103      WORDS_MAKE("7",                               "seven"),
104      WORDS_MAKE("6",                               "six"),
105      WORDS_MAKE("5",                               "five"),
106      WORDS_MAKE("4",                               "four"),
107      WORDS_MAKE("3",                               "three"),
108      WORDS_MAKE("2",                               "two"),
109      WORDS_MAKE("1",                               "one"),
110     };
111   static struct Words_conv conv_zero =
112      WORDS_MAKE("0",                               "zero");
113   static struct Words_conv conv_minus =
114      WORDS_MAKE("-",                               "minus");
115   static int done = FALSE;
116   struct Words_conv *scan = conv_m;
117   unsigned int alen = sizeof(conv_m) / sizeof(conv_m[0]);
118   size_t ret = 0;
119   mpz_t tmp;
120 
121   if (!done)
122   {
123     while (alen)
124     {
125       mpz_init_set_str(scan->bignum, scan->num, 10);
126 
127       --alen;
128       ++scan;
129     }
130     scan = conv_a;
131     alen = sizeof(conv_a) / sizeof(conv_a[0]);
132     while (alen)
133     {
134       mpz_init_set_str(scan->bignum, scan->num, 10);
135 
136       --alen;
137       ++scan;
138     }
139 
140     scan = conv_m;
141     alen = sizeof(conv_m) / sizeof(conv_m[0]);
142 
143     done = TRUE;
144   }
145 
146   mpz_init_set(tmp, num);
147   mpz_abs(tmp, tmp);
148 
149   if (mpz_cmp_ui(num, 0) == 0)
150   {
151     scan = &conv_zero;
152     WORDS_ADD_WORD();
153   }
154   else
155   {
156     while (alen)
157     {
158       mpz_t quo;
159 
160       mpz_init(quo);
161 
162       while (mpz_cmp(tmp, scan->bignum) >= 0)
163       {
164         size_t front = 0;
165 
166         mpz_tdiv_qr(quo, tmp, tmp, scan->bignum);
167 
168         if (!(front = words_conv(s1, pos, quo, FALSE, FALSE)))
169           return (0);
170         ret += front;
171         pos += front;
172 
173         WORDS_ADD_WORD();
174       }
175 
176       --alen;
177       ++scan;
178     }
179 
180     if (ret && (mpz_cmp_ui(tmp, 0) != 0))
181     {
182       scan = &conv_and;
183       WORDS_ADD_WORD();
184     }
185 
186     scan = conv_a;
187     alen = sizeof(conv_a) / sizeof(conv_a[0]);
188     while (alen)
189     {
190       while (mpz_cmp(tmp, scan->bignum) >= 0)
191       {
192         mpz_mod(tmp, tmp, scan->bignum);
193         WORDS_ADD_WORD();
194       }
195 
196       --alen;
197       ++scan;
198     }
199   }
200 
201   ASSERT(ret >= 2);
202 
203   if (mpz_sgn(num) == -1)
204   {
205     scan = &conv_minus;
206     pos  = orig_pos;
207     WORDS_ADD_WORD();
208   }
209 
210   ASSERT(ret >= 2);
211 
212   ++orig_pos;
213   if (del)
214   { /* get rid of space */
215     if (s1) vstr_del(s1, orig_pos, 1);
216     --ret;
217   }
218 
219   if (cap && s1 && !vstr_conv_uppercase(s1, orig_pos, 1)) /* capitalize */
220     return (0);
221 
222   return (ret);
223 }
224 
225 /* This is the custom formatter. */
ex__usr_words_cb(Vstr_base * base,size_t pos,Vstr_fmt_spec * spec,const mpz_t val)226 static int ex__usr_words_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec,
227                             const mpz_t val)
228 {
229   int flags = VSTR_FLAG_SC_FMT_CB_BEG_OBJ_ATOM;
230   size_t len = 0;
231 
232   len = words_conv(NULL, 0, val, TRUE, TRUE);
233 
234   /* Not handled atm. */
235   spec->fmt_quote = 0;
236 
237   if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &len, flags))
238     return (FALSE);
239 
240   if (!words_conv(base, pos, val, TRUE, TRUE))
241     return (FALSE);
242 
243   if (!vstr_sc_fmt_cb_end(base, pos, spec, len))
244     return (FALSE);
245 
246   return (TRUE);
247 }
248 
249 /* Extra, do binary output... */
ex_usr_words_cb(Vstr_base * base,size_t pos,Vstr_fmt_spec * spec)250 static int ex_usr_words_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec)
251 {
252   void *mpz = VSTR_FMT_CB_ARG_PTR(spec, 0);
253 
254   return (ex__usr_words_cb(base, pos, spec, mpz));
255 }
256 
main(int argc,char * argv[])257 int main(int argc, char *argv[])
258 {
259   Vstr_base *s1 = ex_init(NULL);
260   mpz_t bignum;
261 
262   if (argc < 2)
263     errx(EXIT_FAILURE, "No count specified");
264 
265   vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$');
266   if (!vstr_sc_fmt_add_vstr(s1->conf, "{vstr:%p%zu%zu%u}") ||
267       !vstr_fmt_add(s1->conf, "<words:%p>", ex_usr_words_cb,
268                     VSTR_TYPE_FMT_PTR_VOID, VSTR_TYPE_FMT_END))
269     errno = ENOMEM, err(EXIT_FAILURE, "Custom formatters");
270 
271 #if EX_WORDS_USE_TST
272   {
273     struct Words_tst_conv
274     {
275      const char *i;
276      const char *o;
277     }
278     tsts[] =
279       {
280        {"0",   "Zero"},
281        {"1",   "One"},
282        {"2",   "Two"},
283        {"3",   "Three"},
284        {"4",   "Four"},
285        {"5",   "Five"},
286        {"6",   "Six"},
287        {"7",   "Seven"},
288        {"8",   "Eight"},
289        {"9",   "Nine"},
290        {"10",  "Ten"},
291        {"11",  "Eleven"},
292        {"12",  "Twelve"},
293        {"13",  "Thirteen"},
294        {"14",  "Fourteen"},
295        {"15",  "Fifteen"},
296        {"16",  "Sixteen"},
297        {"17",  "Seventeen"},
298        {"18",  "Eighteen"},
299        {"19",  "Nineteen"},
300        {"20",  "Twenty"},
301        {"21",  "Twenty one"},
302        {"29",  "Twenty nine"},
303        {"30",  "Thirty"},
304        {"34",  "Thirty four"},
305        {"40",  "Forty"},
306        {"50",  "Fifty"},
307        {"60",  "Sixty"},
308        {"70",  "Seventy"},
309        {"80",  "Eighty"},
310        {"90",  "Ninety"},
311        {"100", "One hundred"},
312        {"190", "One hundred and ninety"},
313        {"200", "Two hundred"},
314        {"1000", "One thousand"},
315        {"2000", "Two thousand"},
316        {"3210", "Three thousand two hundred and ten"},
317 
318        {"9876543210", "Nine billion eight hundred and seventy six million five hundred and forty three thousand two hundred and ten"},
319        {"9876543210" VAL0_9(), "Nine quintillion eight hundred and seventy six quadrillion five hundred and forty three trillion two hundred and ten billion"},
320        {"-3210", "Minus three thousand two hundred and ten"},
321       };
322     unsigned int alen = sizeof(tsts) / sizeof(tsts[0]);
323     struct Words_tst_conv *scan = tsts;
324 
325 
326     while (alen)
327     {
328       mpz_init_set_str(bignum, scan->i, 0);
329 
330       vstr_del(s1, 1, s1->len);
331       vstr_add_fmt(s1, s1->len, "$<words:%p>", (void *)bignum);
332 
333       if (!vstr_cmp_cstr_eq(s1, 1, s1->len, scan->o))
334         errx(EXIT_FAILURE, "Tst failed(%s): %s",
335              scan->o, vstr_export_cstr_ptr(s1, 1, s1->len));
336 
337       --alen;
338       ++scan;
339     }
340   }
341 #endif
342 
343   mpz_init_set_str(bignum, argv[1], 0);
344 
345   vstr_del(s1, 1, s1->len);
346   vstr_add_fmt(s1, s1->len, " Input: %s\n", argv[1]);
347   vstr_add_fmt(s1, s1->len, " Words: $<words:%p>\n", (void *)bignum);
348 
349   if (s1->conf->malloc_bad)
350     errno = ENOMEM, err(EXIT_FAILURE, "Add string data");
351 
352   io_put_all(s1, STDOUT_FILENO);
353 
354   exit (ex_exit(s1, NULL));
355 }
356