Line data Source code
1 : // Copyright (c) 2023 Made to Order Software Corp. All Rights Reserved
2 : //
3 : // https://snapwebsites.org/project/versiontheca
4 : // contact@m2osw.com
5 : //
6 : // This program is free software: you can redistribute it and/or modify
7 : // it under the terms of the GNU General Public License as published by
8 : // the Free Software Foundation, either version 3 of the License, or
9 : // (at your option) any later version.
10 : //
11 : // This program is distributed in the hope that it will be useful,
12 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 : // GNU General Public License for more details.
15 : //
16 : // You should have received a copy of the GNU General Public License
17 : // along with this program. If not, see <https://www.gnu.org/licenses/>.
18 :
19 : // tested file
20 : //
21 : #include "versiontheca/roman.h"
22 :
23 :
24 : // self
25 : //
26 : #include "catch_main.h"
27 :
28 :
29 :
30 : // versiontheca
31 : //
32 : #include "versiontheca/exception.h"
33 : #include "versiontheca/versiontheca.h"
34 :
35 :
36 : // libutf8
37 : //
38 : #include "libutf8/libutf8.h"
39 :
40 :
41 : // C++
42 : //
43 : #include <cstring>
44 : #include <iomanip>
45 : #include <stdexcept>
46 :
47 :
48 :
49 :
50 : namespace
51 : {
52 :
53 :
54 :
55 4013 : versiontheca::versiontheca::pointer_t create(char const * version, char const * verify = nullptr)
56 : {
57 8026 : versiontheca::roman::pointer_t t(std::make_shared<versiontheca::roman>());
58 4013 : versiontheca::versiontheca::pointer_t v(std::make_shared<versiontheca::versiontheca>(t, version));
59 4013 : if(verify == nullptr)
60 : {
61 4001 : verify = version;
62 : }
63 4013 : CATCH_REQUIRE(v->get_version() == verify);
64 8026 : return v;
65 : }
66 :
67 :
68 11997 : std::string value_to_roman(int v)
69 : {
70 11997 : std::string result;
71 :
72 47999 : for(; v >= 1'000; v -= 1'000)
73 : {
74 18001 : result += 'M';
75 : }
76 :
77 11997 : if(v >= 900)
78 : {
79 1175 : result += "CM";
80 1175 : v -= 900;
81 : }
82 : else
83 : {
84 10822 : if(v >= 500)
85 : {
86 4751 : result += "D";
87 4751 : v -= 500;
88 : }
89 6071 : else if(v >= 400) // v E [400 .. 500)
90 : {
91 1192 : result += "CD";
92 1192 : v -= 400;
93 : }
94 :
95 39780 : for(; v >= 100; v -= 100)
96 : {
97 14479 : result += 'C';
98 : }
99 : }
100 :
101 11997 : if(v >= 90)
102 : {
103 1230 : result += "XC";
104 1230 : v -= 90;
105 : }
106 : else
107 : {
108 10767 : if(v >= 50)
109 : {
110 4844 : result += 'L';
111 4844 : v -= 50;
112 : }
113 5923 : else if(v >= 40)
114 : {
115 1178 : result += "XL";
116 1178 : v -= 40;
117 : }
118 :
119 39493 : for(; v >= 10; v -= 10)
120 : {
121 14363 : result += 'X';
122 : }
123 : }
124 :
125 11997 : if(v == 9)
126 : {
127 1212 : result += "IX";
128 : }
129 : else
130 : {
131 10785 : if(v >= 5)
132 : {
133 4819 : result += 'V';
134 4819 : v -= 5;
135 : }
136 5966 : else if(v == 4)
137 : {
138 1177 : result += "IV";
139 1177 : v = 0;
140 : }
141 :
142 39735 : for(; v > 0; --v)
143 : {
144 14475 : result += 'I';
145 : }
146 : }
147 :
148 11997 : return result;
149 : }
150 :
151 :
152 :
153 : }
154 : // no name namespace
155 :
156 :
157 3 : CATCH_TEST_CASE("roman_numerals", "[roman][valid]")
158 : {
159 2 : CATCH_START_SECTION("roman_numerals: verify roman number conversions")
160 : {
161 4000 : for(int i(1); i <= 3999; ++i)
162 : {
163 7998 : std::string const roman_number(value_to_roman(i));
164 3999 : CATCH_REQUIRE(versiontheca::from_roman_number(roman_number) == static_cast<versiontheca::part_integer_t>(i));
165 3999 : CATCH_REQUIRE(versiontheca::to_roman_number(i) == roman_number);
166 : }
167 : }
168 : CATCH_END_SECTION()
169 1 : }
170 :
171 :
172 6 : CATCH_TEST_CASE("roman_versions", "[roman][valid]")
173 : {
174 8 : CATCH_START_SECTION("roman_versions: verify test checker for version 1.0 and i.0 and I.0")
175 : {
176 1 : create("1.0");
177 1 : create("i.0", "I.0");
178 1 : create("I.0");
179 : }
180 : CATCH_END_SECTION()
181 :
182 8 : CATCH_START_SECTION("roman_versions: verify numbers from 1 to 3999")
183 : {
184 4000 : for(int major_version(1); major_version <= 3999; ++major_version)
185 : {
186 : // use a random number for the minor
187 : //
188 3999 : int const minor_version(rand() % 3999 + 1);
189 7998 : std::string const major_number(value_to_roman(major_version));
190 7998 : std::string const minor_number(value_to_roman(minor_version));
191 7998 : std::string version(major_number + '.' + minor_number);
192 7998 : versiontheca::versiontheca::pointer_t v(create(version.c_str()));
193 3999 : CATCH_REQUIRE(v->get_major() == static_cast<versiontheca::part_integer_t>(major_version));
194 3999 : CATCH_REQUIRE(v->get_minor() == static_cast<versiontheca::part_integer_t>(minor_version));
195 : }
196 : }
197 : CATCH_END_SECTION()
198 :
199 8 : CATCH_START_SECTION("roman_versions: funny roman numerals")
200 : {
201 : // the numerals on the left are _possible_ but should not be used
202 : //
203 1 : create("I.IL", "I.XLIX");
204 1 : create("I.IC", "I.XCIX");
205 1 : create("I.vc", "I.XCV");
206 1 : create("I.ID", "I.CDXCIX");
207 1 : create("i.vd", "I.CDXCV");
208 1 : create("I.IM", "I.CMXCIX");
209 1 : create("IIII.A", "IV.A");
210 :
211 : // the following lot is really just circumstancial
212 : //
213 1 : create("I.LC", "I.L");
214 1 : create("i.llci", "I.I");
215 : }
216 : CATCH_END_SECTION()
217 :
218 8 : CATCH_START_SECTION("roman_versions: including other things")
219 : {
220 2 : versiontheca::versiontheca::pointer_t a(create("3.L.rc5", "3.L.rc5"));
221 2 : versiontheca::versiontheca::pointer_t b(create("3.XI.rc6", "3.XI.rc6"));
222 :
223 1 : CATCH_REQUIRE(*a > *b); // 50 > 11
224 : }
225 : CATCH_END_SECTION()
226 4 : }
227 :
228 :
229 5 : CATCH_TEST_CASE("roman_invalid", "[roman][invalid]")
230 : {
231 6 : CATCH_START_SECTION("roman_invalid: verify test checker for version 1.0 and i.0 and I.0")
232 : {
233 2 : versiontheca::roman::pointer_t t(std::make_shared<versiontheca::roman>());
234 2 : versiontheca::versiontheca::pointer_t v(std::make_shared<versiontheca::versiontheca>(t, ""));
235 1 : CATCH_REQUIRE(v->get_last_error().empty());
236 1 : CATCH_REQUIRE(v->get_version().empty());
237 1 : CATCH_REQUIRE(v->get_last_error() == "no parts to output.");
238 1 : CATCH_REQUIRE(v->get_last_error().empty());
239 : }
240 : CATCH_END_SECTION()
241 :
242 6 : CATCH_START_SECTION("roman_invalid: two periods one after the other is not valid")
243 : {
244 2 : versiontheca::roman::pointer_t t(std::make_shared<versiontheca::roman>());
245 2 : versiontheca::versiontheca::pointer_t v(std::make_shared<versiontheca::versiontheca>(t, ""));
246 1 : CATCH_REQUIRE_FALSE(v->set_version("1..2"));
247 1 : CATCH_REQUIRE(v->get_last_error() == "a version value cannot be an empty string.");
248 1 : CATCH_REQUIRE(v->get_last_error().empty());
249 : }
250 : CATCH_END_SECTION()
251 :
252 6 : CATCH_START_SECTION("roman_invalid: invalid roman numbers")
253 : {
254 1 : CATCH_REQUIRE(versiontheca::to_roman_number(0).empty());
255 1002 : for(int n(4000); n <= 5000; ++n)
256 : {
257 1001 : CATCH_REQUIRE(versiontheca::to_roman_number(n).empty());
258 : }
259 :
260 1 : CATCH_REQUIRE(versiontheca::from_roman_number("") == 0);
261 : }
262 : CATCH_END_SECTION()
263 9 : }
264 :
265 : // vim: ts=4 sw=4 et
|