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