1 /* This is prints roman numerals.
2 * Inspired by http://www.differentpla.net/node/view/58
3 * Requires GMP
4 */
5
6 /* we only need output here, so turn off other IO functions */
7 #define EX_UTILS_NO_USE_INPUT 1
8 #define EX_UTILS_NO_USE_OPEN 1
9 #include "ex_utils.h"
10
11 #include <limits.h>
12 #include <gmp.h>
13
14 /* do we want to test that it works? */
15 #define EX_ROMAN_USE_TST 0
16
17 #define ROMAN_MAKE(num, out) \
18 {num, out, sizeof(out) - 1} \
19
20
roman_conv(Vstr_base * s1,size_t pos,const mpz_t num)21 static size_t roman_conv(Vstr_base *s1, size_t pos, const mpz_t num)
22 {
23 struct Roman_conv
24 {
25 const unsigned int num;
26 const char *str;
27 const size_t len;
28 };
29
30 static struct Roman_conv conv[] =
31 {
32 ROMAN_MAKE(1000, "M"),
33 ROMAN_MAKE(900, "CM"),
34 ROMAN_MAKE(500, "D"),
35 ROMAN_MAKE(400, "CD"),
36 ROMAN_MAKE(100, "C"),
37 ROMAN_MAKE(90, "XC"),
38 ROMAN_MAKE(50, "L"),
39 ROMAN_MAKE(40, "XL"),
40 ROMAN_MAKE(10, "X"),
41 ROMAN_MAKE(9, "IX"),
42 ROMAN_MAKE(5, "V"),
43 ROMAN_MAKE(4, "IV"),
44 ROMAN_MAKE(1, "I"),
45 };
46 const struct Roman_conv *scan = conv;
47 unsigned int alen = sizeof(conv) / sizeof(conv[0]);
48 size_t ret = 0;
49 mpz_t tmp;
50
51 mpz_init_set(tmp, num);
52 mpz_abs(tmp, tmp);
53
54 while (alen)
55 {
56 while (mpz_cmp_ui(tmp, scan->num) >= 0)
57 {
58 mpz_sub_ui(tmp, tmp, scan->num);
59
60 if (s1 && !vstr_add_buf(s1, pos, scan->str, scan->len))
61 return (0);
62
63 pos += scan->len;
64 ret += scan->len;
65 }
66
67 --alen;
68 ++scan;
69 }
70
71
72 return (ret);
73 }
74
75 /* This is the custom formatter. */
ex__usr_roman_cb(Vstr_base * base,size_t pos,Vstr_fmt_spec * spec,const mpz_t val)76 static int ex__usr_roman_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec,
77 const mpz_t val)
78 {
79 int flags = VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NUM; /* it's a number */
80 size_t len = 0;
81
82 if (mpz_sgn(val) == -1)
83 flags |= VSTR_FLAG_SC_FMT_CB_BEG_OBJ_NEG;
84
85 len = roman_conv(NULL, 0, val);
86
87 /* Not handled atm. */
88 spec->fmt_quote = 0;
89
90 if (!vstr_sc_fmt_cb_beg(base, &pos, spec, &len, flags))
91 return (FALSE);
92
93 if (!roman_conv(base, pos, val))
94 return (FALSE);
95
96 if (!vstr_sc_fmt_cb_end(base, pos, spec, len))
97 return (FALSE);
98
99 return (TRUE);
100 }
101
102 /* Extra, do binary output... */
ex_usr_roman_cb(Vstr_base * base,size_t pos,Vstr_fmt_spec * spec)103 static int ex_usr_roman_cb(Vstr_base *base, size_t pos, Vstr_fmt_spec *spec)
104 {
105 void *mpz = VSTR_FMT_CB_ARG_PTR(spec, 0);
106
107 return (ex__usr_roman_cb(base, pos, spec, mpz));
108 }
109
main(int argc,char * argv[])110 int main(int argc, char *argv[])
111 {
112 Vstr_base *s1 = ex_init(NULL);
113 mpz_t bignum;
114
115 if (argc < 2)
116 errx(EXIT_FAILURE, "No count specified");
117
118 vstr_cntl_conf(s1->conf, VSTR_CNTL_CONF_SET_FMT_CHAR_ESC, '$');
119 if (!vstr_sc_fmt_add_vstr(s1->conf, "{vstr:%p%zu%zu%u}") ||
120 !vstr_fmt_add(s1->conf, "<roman:%p>", ex_usr_roman_cb,
121 VSTR_TYPE_FMT_PTR_VOID, VSTR_TYPE_FMT_END))
122 errno = ENOMEM, err(EXIT_FAILURE, "Custom formatters");
123
124 #if EX_ROMAN_USE_TST
125 {
126 struct Roman_tst_conv
127 {
128 const char *i;
129 const char *o;
130 }
131 tsts[] =
132 {
133 {"0", ""},
134 {"1", "I"},
135 {"2", "II"},
136 {"3", "III"},
137 {"4", "IV"},
138 {"5", "V"},
139 {"6", "VI"},
140 {"7", "VII"},
141 {"8", "VIII"},
142 {"9", "IX"},
143 {"10", "X"},
144 {"11", "XI"},
145 {"12", "XII"},
146 {"13", "XIII"},
147 {"14", "XIV"},
148 {"15", "XV"},
149 {"16", "XVI"},
150 {"17", "XVII"},
151 {"18", "XVIII"},
152 {"19", "XIX"},
153 {"20", "XX"},
154 {"21", "XXI"},
155 {"29", "XXIX"},
156 {"30", "XXX"},
157 {"34", "XXXIV"},
158 {"40", "XL"},
159 {"50", "L"},
160 {"60", "LX"},
161 {"70", "LXX"},
162 {"80", "LXXX"},
163 {"90", "XC"},
164 {"100", "C"},
165 {"190", "CXC"},
166 {"200", "CC"},
167 {"300", "CCC"},
168 {"400", "CD"},
169 {"500", "D"},
170 {"600", "DC"},
171 {"700", "DCC"},
172 {"800", "DCCC"},
173 {"900", "CM"},
174 {"1000", "M"},
175
176 {"1900", "MCM"},
177 {"1975", "MCMLXXV"},
178 {"1989", "MCMLXXXIX"},
179 {"1999", "MCMXCIX"},
180 {"2000", "MM"},
181 {"2001", "MMI"},
182 #define M10() "MMMMMMMMMM"
183 #define M100() M10() M10() M10() M10() M10() M10() M10() M10() M10() M10()
184 #define M1_000() M100()M100()M100()M100()M100()M100()M100()M100()M100()M100()
185 #define M10_000() M1_000()M1_000()M1_000()M1_000()M1_000() \
186 M1_000()M1_000()M1_000()M1_000()M1_000()
187 #define M100_000() M10_000()M10_000()M10_000()M10_000()M10_000() \
188 M10_000()M10_000()M10_000()M10_000()M10_000()
189 #define M1_000_000() M100_000()M100_000()M100_000()M100_000()M100_000() \
190 M100_000()M100_000()M100_000()M100_000()M100_000()
191 #define M10_000_000() M1_000_000()M1_000_000()M1_000_000()M1_000_000()M1_000_000() \
192 M1_000_000()M1_000_000()M1_000_000()M1_000_000()M1_000_000()
193 {"10004", M10() "IV"},
194 {"12345", M10() "MMCCCXLV"},
195 {"10000000053", M10_000_000() "LIII"},
196 };
197 unsigned int alen = sizeof(tsts) / sizeof(tsts[0]);
198 struct Roman_tst_conv *scan = tsts;
199
200
201 while (alen)
202 {
203 mpz_init_set_str(bignum, scan->i, 0);
204
205 vstr_del(s1, 1, s1->len);
206 vstr_add_fmt(s1, s1->len, "$<roman:%p>", (void *)bignum);
207
208 if (!vstr_cmp_cstr_eq(s1, 1, s1->len, scan->o))
209 errx(EXIT_FAILURE, "Tst failed(%s): %s",
210 scan->o, vstr_export_cstr_ptr(s1, 1, s1->len));
211
212 --alen;
213 ++scan;
214 }
215 }
216 #endif
217
218 mpz_init_set_str(bignum, argv[1], 0);
219
220 vstr_del(s1, 1, s1->len);
221 vstr_add_fmt(s1, s1->len, " Input: %s\n", argv[1]);
222 vstr_add_fmt(s1, s1->len, " Roman: $<roman:%p>\n", (void *)bignum);
223
224 if (s1->conf->malloc_bad)
225 errno = ENOMEM, err(EXIT_FAILURE, "Add string data");
226
227 io_put_all(s1, STDOUT_FILENO);
228
229 exit (ex_exit(s1, NULL));
230 }
231