]>
Commit | Line | Data |
---|---|---|
f3c0d7a5 A |
1 | // © 2016 and later: Unicode, Inc. and others. |
2 | // License & terms of use: http://www.unicode.org/copyright.html | |
374ca955 | 3 | /*********************************************************************** |
b75a7d8f | 4 | * COPYRIGHT: |
51004dcb | 5 | * Copyright (c) 1997-2012, International Business Machines Corporation |
374ca955 A |
6 | * and others. All Rights Reserved. |
7 | ***********************************************************************/ | |
b75a7d8f A |
8 | |
9 | #include "unicode/utypes.h" | |
10 | ||
11 | #if !UCONFIG_NO_FORMATTING | |
12 | ||
13 | #include "unicode/decimfmt.h" | |
14 | #include "tsnmfmt.h" | |
374ca955 | 15 | #include "putilimp.h" |
4388f060 | 16 | #include "cstring.h" |
b75a7d8f | 17 | #include <float.h> |
374ca955 A |
18 | #include <stdlib.h> |
19 | ||
20 | IntlTestNumberFormat::~IntlTestNumberFormat() {} | |
b75a7d8f A |
21 | |
22 | static const char * formattableTypeName(Formattable::Type t) | |
23 | { | |
24 | switch(t) { | |
25 | case Formattable::kDate: return "kDate"; | |
26 | case Formattable::kDouble: return "kDouble"; | |
27 | case Formattable::kLong: return "kLong"; | |
28 | case Formattable::kString: return "kString"; | |
29 | case Formattable::kArray: return "kArray"; | |
374ca955 | 30 | case Formattable::kInt64: return "kInt64"; |
b75a7d8f A |
31 | default: return "??unknown??"; |
32 | } | |
33 | } | |
34 | ||
35 | /** | |
36 | * This test does round-trip testing (format -> parse -> format -> parse -> etc.) of | |
37 | * NumberFormat. | |
38 | */ | |
39 | void IntlTestNumberFormat::runIndexedTest( int32_t index, UBool exec, const char* &name, char* /*par*/ ) | |
40 | { | |
41 | ||
42 | if (exec) logln((UnicodeString)"TestSuite NumberFormat"); | |
43 | switch (index) { | |
44 | case 0: name = "createInstance"; | |
45 | if (exec) | |
46 | { | |
47 | logln(name); | |
48 | fStatus = U_ZERO_ERROR; | |
49 | fFormat = NumberFormat::createInstance(fStatus); | |
50 | testFormat(/*par*/); | |
51 | } | |
52 | break; | |
53 | ||
54 | case 1: name = "DefaultLocale"; | |
55 | if (exec) testLocale(/*par, */Locale::getDefault(), name); | |
56 | break; | |
57 | ||
58 | case 2: name = "testAvailableLocales"; | |
59 | if (exec) { | |
60 | logln(name); | |
61 | testAvailableLocales(/*par*/); | |
62 | } | |
63 | break; | |
64 | ||
65 | case 3: name = "monsterTest"; | |
66 | if (exec) { | |
67 | logln(name); | |
68 | monsterTest(/*par*/); | |
69 | } | |
70 | break; | |
71 | ||
72 | default: name = ""; break; | |
73 | } | |
74 | } | |
75 | ||
76 | void | |
77 | IntlTestNumberFormat::testLocale(/* char* par, */const Locale& locale, const UnicodeString& localeName) | |
78 | { | |
79 | const char* name; | |
80 | ||
81 | fLocale = locale; | |
82 | name = "Number test"; | |
83 | logln((UnicodeString)name + " (" + localeName + ")"); | |
84 | fStatus = U_ZERO_ERROR; | |
85 | fFormat = NumberFormat::createInstance(locale, fStatus); | |
86 | testFormat(/* par */); | |
87 | ||
88 | name = "Currency test"; | |
89 | logln((UnicodeString)name + " (" + localeName + ")"); | |
90 | fStatus = U_ZERO_ERROR; | |
91 | fFormat = NumberFormat::createCurrencyInstance(locale, fStatus); | |
92 | testFormat(/* par */); | |
93 | ||
94 | name = "Percent test"; | |
95 | logln((UnicodeString)name + " (" + localeName + ")"); | |
96 | fStatus = U_ZERO_ERROR; | |
97 | fFormat = NumberFormat::createPercentInstance(locale, fStatus); | |
98 | testFormat(/* par */); | |
729e4ab9 | 99 | |
4388f060 A |
100 | if (uprv_strcmp(locale.getName(), "en_US_POSIX") != 0) { |
101 | name = "Scientific test"; | |
102 | logln((UnicodeString)name + " (" + localeName + ")"); | |
103 | fStatus = U_ZERO_ERROR; | |
104 | fFormat = NumberFormat::createScientificInstance(locale, fStatus); | |
105 | testFormat(/* par */); | |
106 | } | |
b75a7d8f A |
107 | } |
108 | ||
109 | double IntlTestNumberFormat::randDouble() | |
110 | { | |
111 | // Assume 8-bit (or larger) rand values. Also assume | |
112 | // that the system rand() function is very poor, which it always is. | |
113 | // Call srand(currentTime) in intltest to make it truly random. | |
114 | double d; | |
115 | uint32_t i; | |
116 | char* poke = (char*)&d; | |
117 | do { | |
118 | for (i=0; i < sizeof(double); ++i) | |
119 | { | |
120 | poke[i] = (char)(rand() & 0xFF); | |
121 | } | |
122 | } while (uprv_isNaN(d) || uprv_isInfinite(d) | |
123 | || !((-DBL_MAX < d && d < DBL_MAX) || (d < -DBL_MIN && DBL_MIN < d))); | |
124 | ||
125 | return d; | |
126 | } | |
127 | ||
128 | /* | |
129 | * Return a random uint32_t | |
130 | **/ | |
131 | uint32_t IntlTestNumberFormat::randLong() | |
132 | { | |
133 | // Assume 8-bit (or larger) rand values. Also assume | |
134 | // that the system rand() function is very poor, which it always is. | |
135 | // Call srand(currentTime) in intltest to make it truly random. | |
136 | uint32_t d; | |
137 | uint32_t i; | |
138 | char* poke = (char*)&d; | |
139 | for (i=0; i < sizeof(uint32_t); ++i) | |
140 | { | |
141 | poke[i] = (char)(rand() & 0xFF); | |
142 | } | |
143 | return d; | |
144 | } | |
145 | ||
146 | ||
374ca955 A |
147 | /* Make sure that we don't get something too large and multiply into infinity. |
148 | @param smallerThanMax the requested maximum value smaller than DBL_MAX */ | |
b75a7d8f A |
149 | double IntlTestNumberFormat::getSafeDouble(double smallerThanMax) { |
150 | double it; | |
374ca955 A |
151 | double high = (DBL_MAX/smallerThanMax)/10.0; |
152 | double low = -high; | |
b75a7d8f A |
153 | do { |
154 | it = randDouble(); | |
374ca955 | 155 | } while (low > it || it > high); |
b75a7d8f A |
156 | return it; |
157 | } | |
158 | ||
159 | void | |
160 | IntlTestNumberFormat::testFormat(/* char* par */) | |
161 | { | |
162 | if (U_FAILURE(fStatus)) | |
163 | { | |
729e4ab9 | 164 | dataerrln((UnicodeString)"**** FAIL: createXxxInstance failed. - " + u_errorName(fStatus)); |
b75a7d8f A |
165 | if (fFormat != 0) |
166 | errln("**** FAIL: Non-null format returned by createXxxInstance upon failure."); | |
167 | delete fFormat; | |
168 | fFormat = 0; | |
169 | return; | |
170 | } | |
171 | ||
172 | if (fFormat == 0) | |
173 | { | |
174 | errln((UnicodeString)"**** FAIL: Null format returned by createXxxInstance."); | |
175 | return; | |
176 | } | |
177 | ||
178 | UnicodeString str; | |
179 | ||
180 | // Assume it's a DecimalFormat and get some info | |
181 | DecimalFormat *s = (DecimalFormat*)fFormat; | |
182 | logln((UnicodeString)" Pattern " + s->toPattern(str)); | |
183 | ||
4388f060 | 184 | #if U_PF_OS390 <= U_PLATFORM && U_PLATFORM <= U_PF_OS400 |
b75a7d8f A |
185 | tryIt(-2.02147304840132e-68); |
186 | tryIt(3.88057859588817e-68); // Test rounding when only some digits are shown because exponent is close to -maxfrac | |
187 | tryIt(-2.64651110485945e+65); // Overflows to +INF when shown as a percent | |
188 | tryIt(9.29526819488338e+64); // Ok -- used to fail? | |
189 | #else | |
190 | tryIt(-2.02147304840132e-100); | |
191 | tryIt(3.88057859588817e-096); // Test rounding when only some digits are shown because exponent is close to -maxfrac | |
192 | tryIt(-2.64651110485945e+306); // Overflows to +INF when shown as a percent | |
193 | tryIt(9.29526819488338e+250); // Ok -- used to fail? | |
194 | #endif | |
195 | ||
196 | // These PASS now, with the sprintf/atof based format-parse. | |
197 | ||
198 | // These fail due to round-off | |
199 | // The least significant digit drops by one during each format-parse cycle. | |
200 | // Both numbers DON'T have a round-off problem when multiplied by 100! (shown as %) | |
4388f060 | 201 | #if U_PLATFORM == U_PF_OS390 |
b75a7d8f A |
202 | tryIt(-9.18228054496402e+64); |
203 | tryIt(-9.69413034454191e+64); | |
204 | #else | |
205 | tryIt(-9.18228054496402e+255); | |
206 | tryIt(-9.69413034454191e+273); | |
207 | #endif | |
208 | ||
4388f060 | 209 | #if U_PLATFORM != U_PF_OS390 |
b75a7d8f A |
210 | tryIt(1.234e-200); |
211 | tryIt(-2.3e-168); | |
212 | ||
213 | tryIt(uprv_getNaN()); | |
214 | tryIt(uprv_getInfinity()); | |
215 | tryIt(-uprv_getInfinity()); | |
216 | #endif | |
217 | ||
218 | tryIt((int32_t)251887531); | |
219 | tryIt(5e-20 / 9); | |
220 | tryIt(5e20 / 9); | |
221 | tryIt(1.234e-50); | |
222 | tryIt(9.99999999999996); | |
223 | tryIt(9.999999999999996); | |
729e4ab9 | 224 | |
4388f060 A |
225 | tryIt(5.06e-27); |
226 | ||
b75a7d8f A |
227 | tryIt((int32_t)INT32_MIN); |
228 | tryIt((int32_t)INT32_MAX); | |
229 | tryIt((double)INT32_MIN); | |
230 | tryIt((double)INT32_MAX); | |
231 | tryIt((double)INT32_MIN - 1.0); | |
232 | tryIt((double)INT32_MAX + 1.0); | |
233 | ||
234 | tryIt(5.0 / 9.0 * 1e-20); | |
235 | tryIt(4.0 / 9.0 * 1e-20); | |
236 | tryIt(5.0 / 9.0 * 1e+20); | |
237 | tryIt(4.0 / 9.0 * 1e+20); | |
238 | ||
239 | tryIt(2147483647.); | |
240 | tryIt((int32_t)0); | |
241 | tryIt(0.0); | |
242 | tryIt((int32_t)1); | |
243 | tryIt((int32_t)10); | |
244 | tryIt((int32_t)100); | |
245 | tryIt((int32_t)-1); | |
246 | tryIt((int32_t)-10); | |
247 | tryIt((int32_t)-100); | |
248 | tryIt((int32_t)-1913860352); | |
249 | ||
250 | for (int32_t z=0; z<10; ++z) | |
251 | { | |
252 | double d = randFraction() * 2e10 - 1e10; | |
253 | tryIt(d); | |
254 | } | |
255 | ||
256 | double it = getSafeDouble(100000.0); | |
257 | ||
258 | tryIt(0.0); | |
259 | tryIt(it); | |
260 | tryIt((int32_t)0); | |
261 | tryIt(uprv_floor(it)); | |
262 | tryIt((int32_t)randLong()); | |
263 | ||
264 | // try again | |
265 | it = getSafeDouble(100.0); | |
266 | tryIt(it); | |
267 | tryIt(uprv_floor(it)); | |
268 | tryIt((int32_t)randLong()); | |
269 | ||
270 | // try again with very large numbers | |
271 | it = getSafeDouble(100000000000.0); | |
272 | tryIt(it); | |
273 | ||
274 | // try again with very large numbers | |
275 | // and without going outside of the int32_t range | |
276 | it = randFraction() * INT32_MAX; | |
277 | tryIt(it); | |
278 | tryIt((int32_t)uprv_floor(it)); | |
279 | ||
280 | delete fFormat; | |
281 | } | |
282 | ||
283 | void | |
284 | IntlTestNumberFormat::tryIt(double aNumber) | |
285 | { | |
286 | const int32_t DEPTH = 10; | |
287 | Formattable number[DEPTH]; | |
288 | UnicodeString string[DEPTH]; | |
289 | ||
290 | int32_t numberMatch = 0; | |
291 | int32_t stringMatch = 0; | |
292 | UnicodeString errMsg; | |
293 | int32_t i; | |
294 | for (i=0; i<DEPTH; ++i) | |
295 | { | |
296 | errMsg.truncate(0); // if non-empty, we failed this iteration | |
297 | UErrorCode status = U_ZERO_ERROR; | |
298 | string[i] = "(n/a)"; // "format was never done" value | |
299 | if (i == 0) { | |
300 | number[i].setDouble(aNumber); | |
301 | } else { | |
302 | fFormat->parse(string[i-1], number[i], status); | |
303 | if (U_FAILURE(status)) { | |
304 | number[i].setDouble(1234.5); // "parse failed" value | |
305 | errMsg = "**** FAIL: Parse of " + prettify(string[i-1]) + " failed."; | |
306 | --i; // don't show empty last line: "1234.5 F> (n/a) P>" | |
307 | break; | |
308 | } | |
309 | } | |
310 | // Convert from long to double | |
311 | if (number[i].getType() == Formattable::kLong) | |
312 | number[i].setDouble(number[i].getLong()); | |
374ca955 A |
313 | else if (number[i].getType() == Formattable::kInt64) |
314 | number[i].setDouble((double)number[i].getInt64()); | |
b75a7d8f A |
315 | else if (number[i].getType() != Formattable::kDouble) |
316 | { | |
317 | errMsg = ("**** FAIL: Parse of " + prettify(string[i-1]) | |
318 | + " returned non-numeric Formattable, type " + UnicodeString(formattableTypeName(number[i].getType())) | |
319 | + ", Locale=" + UnicodeString(fLocale.getName()) | |
320 | + ", longValue=" + number[i].getLong()); | |
321 | break; | |
322 | } | |
323 | string[i].truncate(0); | |
324 | fFormat->format(number[i].getDouble(), string[i]); | |
325 | if (i > 0) | |
326 | { | |
327 | if (numberMatch == 0 && number[i] == number[i-1]) | |
328 | numberMatch = i; | |
329 | else if (numberMatch > 0 && number[i] != number[i-1]) | |
330 | { | |
331 | errMsg = ("**** FAIL: Numeric mismatch after match."); | |
332 | break; | |
333 | } | |
334 | if (stringMatch == 0 && string[i] == string[i-1]) | |
335 | stringMatch = i; | |
336 | else if (stringMatch > 0 && string[i] != string[i-1]) | |
337 | { | |
338 | errMsg = ("**** FAIL: String mismatch after match."); | |
339 | break; | |
340 | } | |
341 | } | |
342 | if (numberMatch > 0 && stringMatch > 0) | |
343 | break; | |
344 | } | |
345 | if (i == DEPTH) | |
346 | --i; | |
347 | ||
348 | if (stringMatch > 2 || numberMatch > 2) | |
349 | { | |
350 | errMsg = ("**** FAIL: No string and/or number match within 2 iterations."); | |
351 | } | |
352 | ||
353 | if (errMsg.length() != 0) | |
354 | { | |
355 | for (int32_t k=0; k<=i; ++k) | |
356 | { | |
357 | logln((UnicodeString)"" + k + ": " + number[k].getDouble() + " F> " + | |
358 | prettify(string[k]) + " P> "); | |
359 | } | |
360 | errln(errMsg); | |
361 | } | |
362 | } | |
363 | ||
364 | void | |
365 | IntlTestNumberFormat::tryIt(int32_t aNumber) | |
366 | { | |
367 | Formattable number(aNumber); | |
368 | UnicodeString stringNum; | |
369 | UErrorCode status = U_ZERO_ERROR; | |
370 | ||
371 | fFormat->format(number, stringNum, status); | |
372 | if (U_FAILURE(status)) | |
373 | { | |
51004dcb | 374 | errln(UnicodeString("**** FAIL: Formatting ") + aNumber); |
b75a7d8f A |
375 | return; |
376 | } | |
377 | fFormat->parse(stringNum, number, status); | |
378 | if (U_FAILURE(status)) | |
379 | { | |
380 | errln("**** FAIL: Parse of " + prettify(stringNum) + " failed."); | |
381 | return; | |
382 | } | |
383 | if (number.getType() != Formattable::kLong) | |
384 | { | |
385 | errln("**** FAIL: Parse of " + prettify(stringNum) | |
386 | + " returned non-long Formattable, type " + UnicodeString(formattableTypeName(number.getType())) | |
387 | + ", Locale=" + UnicodeString(fLocale.getName()) | |
388 | + ", doubleValue=" + number.getDouble() | |
389 | + ", longValue=" + number.getLong() | |
390 | + ", origValue=" + aNumber | |
391 | ); | |
392 | } | |
393 | if (number.getLong() != aNumber) { | |
394 | errln("**** FAIL: Parse of " + prettify(stringNum) + " failed. Got:" + number.getLong() | |
395 | + " Expected:" + aNumber); | |
396 | } | |
397 | } | |
398 | ||
399 | void IntlTestNumberFormat::testAvailableLocales(/* char* par */) | |
400 | { | |
401 | int32_t count = 0; | |
402 | const Locale* locales = NumberFormat::getAvailableLocales(count); | |
403 | logln((UnicodeString)"" + count + " available locales"); | |
404 | if (locales && count) | |
405 | { | |
406 | UnicodeString name; | |
407 | UnicodeString all; | |
408 | for (int32_t i=0; i<count; ++i) | |
409 | { | |
410 | if (i!=0) | |
411 | all += ", "; | |
412 | all += locales[i].getName(); | |
413 | } | |
414 | logln(all); | |
415 | } | |
416 | else | |
729e4ab9 | 417 | dataerrln((UnicodeString)"**** FAIL: Zero available locales or null array pointer"); |
b75a7d8f A |
418 | } |
419 | ||
420 | void IntlTestNumberFormat::monsterTest(/* char* par */) | |
421 | { | |
422 | const char *SEP = "============================================================\n"; | |
423 | int32_t count; | |
424 | const Locale* allLocales = NumberFormat::getAvailableLocales(count); | |
425 | Locale* locales = (Locale*)allLocales; | |
426 | Locale quickLocales[6]; | |
427 | if (allLocales && count) | |
428 | { | |
429 | if (quick && count > 6) { | |
430 | logln("quick test: testing just 6 locales!"); | |
431 | count = 6; | |
432 | locales = quickLocales; | |
433 | locales[0] = allLocales[0]; | |
434 | locales[1] = allLocales[1]; | |
435 | locales[2] = allLocales[2]; | |
436 | // In a quick test, make sure we test locales that use | |
437 | // currency prefix, currency suffix, and choice currency | |
438 | // logic. Otherwise bugs in these areas can slip through. | |
439 | locales[3] = Locale("ar", "AE", ""); | |
440 | locales[4] = Locale("cs", "CZ", ""); | |
441 | locales[5] = Locale("en", "IN", ""); | |
442 | } | |
443 | for (int32_t i=0; i<count; ++i) | |
444 | { | |
445 | UnicodeString name(locales[i].getName(), ""); | |
446 | logln(SEP); | |
447 | testLocale(/* par, */locales[i], name); | |
448 | } | |
449 | } | |
450 | ||
451 | logln(SEP); | |
452 | } | |
453 | ||
454 | #endif /* #if !UCONFIG_NO_FORMATTING */ |