1 // © 2018 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
3
4 #include "unicode/utypes.h"
5
6 #if !UCONFIG_NO_FORMATTING
7
8 #include "numfmtst.h"
9 #include "number_decimalquantity.h"
10 #include "putilimp.h"
11 #include "charstr.h"
12 #include <cmath>
13
14 using icu::number::impl::DecimalQuantity;
15
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)16 void NumberFormatDataDrivenTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
17 if (exec) {
18 logln("TestSuite NumberFormatDataDrivenTest: ");
19 }
20 TESTCASE_AUTO_BEGIN;
21 TESTCASE_AUTO(TestNumberFormatTestTuple);
22 TESTCASE_AUTO(TestDataDrivenICU4C);
23 TESTCASE_AUTO_END;
24 }
25
26 static DecimalQuantity&
strToDigitList(const UnicodeString & str,DecimalQuantity & digitList,UErrorCode & status)27 strToDigitList(const UnicodeString& str, DecimalQuantity& digitList, UErrorCode& status) {
28 if (U_FAILURE(status)) {
29 return digitList;
30 }
31 if (str == "NaN") {
32 digitList.setToDouble(uprv_getNaN());
33 return digitList;
34 }
35 if (str == "-Inf") {
36 digitList.setToDouble(-1 * uprv_getInfinity());
37 return digitList;
38 }
39 if (str == "Inf") {
40 digitList.setToDouble(uprv_getInfinity());
41 return digitList;
42 }
43 CharString formatValue;
44 formatValue.appendInvariantChars(str, status);
45 digitList.setToDecNumber({formatValue.data(), formatValue.length()}, status);
46 return digitList;
47 }
48
49 static UnicodeString&
format(const DecimalFormat & fmt,const DecimalQuantity & digitList,UnicodeString & appendTo,UErrorCode & status)50 format(const DecimalFormat& fmt, const DecimalQuantity& digitList, UnicodeString& appendTo,
51 UErrorCode& status) {
52 if (U_FAILURE(status)) {
53 return appendTo;
54 }
55 FieldPosition fpos(FieldPosition::DONT_CARE);
56 return fmt.format(digitList, appendTo, fpos, status);
57 }
58
59 template<class T>
60 static UnicodeString&
format(const DecimalFormat & fmt,T value,UnicodeString & appendTo,UErrorCode & status)61 format(const DecimalFormat& fmt, T value, UnicodeString& appendTo, UErrorCode& status) {
62 if (U_FAILURE(status)) {
63 return appendTo;
64 }
65 FieldPosition fpos(FieldPosition::DONT_CARE);
66 return fmt.format(value, appendTo, fpos, status);
67 }
68
adjustDecimalFormat(const NumberFormatTestTuple & tuple,DecimalFormat & fmt,UnicodeString & appendErrorMessage)69 static void adjustDecimalFormat(const NumberFormatTestTuple& tuple, DecimalFormat& fmt,
70 UnicodeString& appendErrorMessage) {
71 if (tuple.minIntegerDigitsFlag) {
72 fmt.setMinimumIntegerDigits(tuple.minIntegerDigits);
73 }
74 if (tuple.maxIntegerDigitsFlag) {
75 fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits);
76 }
77 if (tuple.minFractionDigitsFlag) {
78 fmt.setMinimumFractionDigits(tuple.minFractionDigits);
79 }
80 if (tuple.maxFractionDigitsFlag) {
81 fmt.setMaximumFractionDigits(tuple.maxFractionDigits);
82 }
83 if (tuple.currencyFlag) {
84 UErrorCode status = U_ZERO_ERROR;
85 UnicodeString currency(tuple.currency);
86 const UChar* terminatedCurrency = currency.getTerminatedBuffer();
87 fmt.setCurrency(terminatedCurrency, status);
88 if (U_FAILURE(status)) {
89 appendErrorMessage.append("Error setting currency.");
90 }
91 }
92 if (tuple.minGroupingDigitsFlag) {
93 fmt.setMinimumGroupingDigits(tuple.minGroupingDigits);
94 }
95 if (tuple.useSigDigitsFlag) {
96 fmt.setSignificantDigitsUsed(tuple.useSigDigits != 0);
97 }
98 if (tuple.minSigDigitsFlag) {
99 fmt.setMinimumSignificantDigits(tuple.minSigDigits);
100 }
101 if (tuple.maxSigDigitsFlag) {
102 fmt.setMaximumSignificantDigits(tuple.maxSigDigits);
103 }
104 if (tuple.useGroupingFlag) {
105 fmt.setGroupingUsed(tuple.useGrouping != 0);
106 }
107 if (tuple.multiplierFlag) {
108 fmt.setMultiplier(tuple.multiplier);
109 }
110 if (tuple.roundingIncrementFlag) {
111 fmt.setRoundingIncrement(tuple.roundingIncrement);
112 }
113 if (tuple.formatWidthFlag) {
114 fmt.setFormatWidth(tuple.formatWidth);
115 }
116 if (tuple.padCharacterFlag) {
117 fmt.setPadCharacter(tuple.padCharacter);
118 }
119 if (tuple.useScientificFlag) {
120 fmt.setScientificNotation(tuple.useScientific != 0);
121 }
122 if (tuple.groupingFlag) {
123 fmt.setGroupingSize(tuple.grouping);
124 }
125 if (tuple.grouping2Flag) {
126 fmt.setSecondaryGroupingSize(tuple.grouping2);
127 }
128 if (tuple.roundingModeFlag) {
129 fmt.setRoundingMode(tuple.roundingMode);
130 }
131 if (tuple.currencyUsageFlag) {
132 UErrorCode status = U_ZERO_ERROR;
133 fmt.setCurrencyUsage(tuple.currencyUsage, &status);
134 if (U_FAILURE(status)) {
135 appendErrorMessage.append("CurrencyUsage: error setting.");
136 }
137 }
138 if (tuple.minimumExponentDigitsFlag) {
139 fmt.setMinimumExponentDigits(tuple.minimumExponentDigits);
140 }
141 if (tuple.exponentSignAlwaysShownFlag) {
142 fmt.setExponentSignAlwaysShown(tuple.exponentSignAlwaysShown != 0);
143 }
144 if (tuple.decimalSeparatorAlwaysShownFlag) {
145 fmt.setDecimalSeparatorAlwaysShown(
146 tuple.decimalSeparatorAlwaysShown != 0);
147 }
148 if (tuple.padPositionFlag) {
149 fmt.setPadPosition(tuple.padPosition);
150 }
151 if (tuple.positivePrefixFlag) {
152 fmt.setPositivePrefix(tuple.positivePrefix);
153 }
154 if (tuple.positiveSuffixFlag) {
155 fmt.setPositiveSuffix(tuple.positiveSuffix);
156 }
157 if (tuple.negativePrefixFlag) {
158 fmt.setNegativePrefix(tuple.negativePrefix);
159 }
160 if (tuple.negativeSuffixFlag) {
161 fmt.setNegativeSuffix(tuple.negativeSuffix);
162 }
163 if (tuple.signAlwaysShownFlag) {
164 fmt.setSignAlwaysShown(tuple.signAlwaysShown != 0);
165 }
166 if (tuple.localizedPatternFlag) {
167 UErrorCode status = U_ZERO_ERROR;
168 fmt.applyLocalizedPattern(tuple.localizedPattern, status);
169 if (U_FAILURE(status)) {
170 appendErrorMessage.append("Error setting localized pattern.");
171 }
172 }
173 fmt.setLenient(NFTT_GET_FIELD(tuple, lenient, 1) != 0);
174 if (tuple.parseIntegerOnlyFlag) {
175 fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0);
176 }
177 if (tuple.decimalPatternMatchRequiredFlag) {
178 fmt.setDecimalPatternMatchRequired(
179 tuple.decimalPatternMatchRequired != 0);
180 }
181 if (tuple.parseNoExponentFlag) {
182 UErrorCode status = U_ZERO_ERROR;
183 fmt.setAttribute(
184 UNUM_PARSE_NO_EXPONENT, tuple.parseNoExponent, status);
185 if (U_FAILURE(status)) {
186 appendErrorMessage.append("Error setting parse no exponent flag.");
187 }
188 }
189 if (tuple.parseCaseSensitiveFlag) {
190 fmt.setParseCaseSensitive(tuple.parseCaseSensitive != 0);
191 }
192 }
193
194 static DecimalFormat*
newDecimalFormat(const Locale & locale,const UnicodeString & pattern,UErrorCode & status)195 newDecimalFormat(const Locale& locale, const UnicodeString& pattern, UErrorCode& status) {
196 if (U_FAILURE(status)) {
197 return NULL;
198 }
199 LocalPointer<DecimalFormatSymbols> symbols(
200 new DecimalFormatSymbols(locale, status), status);
201 if (U_FAILURE(status)) {
202 return NULL;
203 }
204 UParseError perror;
205 LocalPointer<DecimalFormat> result(
206 new DecimalFormat(
207 pattern, symbols.getAlias(), perror, status), status);
208 if (!result.isNull()) {
209 symbols.orphan();
210 }
211 if (U_FAILURE(status)) {
212 return NULL;
213 }
214 return result.orphan();
215 }
216
newDecimalFormat(const NumberFormatTestTuple & tuple,UErrorCode & status)217 static DecimalFormat* newDecimalFormat(const NumberFormatTestTuple& tuple, UErrorCode& status) {
218 if (U_FAILURE(status)) {
219 return NULL;
220 }
221 Locale en("en");
222 return newDecimalFormat(NFTT_GET_FIELD(tuple, locale, en),
223 NFTT_GET_FIELD(tuple, pattern, "0"),
224 status);
225 }
226
isFormatPass(const NumberFormatTestTuple & tuple,UnicodeString & appendErrorMessage,UErrorCode & status)227 UBool NumberFormatDataDrivenTest::isFormatPass(const NumberFormatTestTuple& tuple,
228 UnicodeString& appendErrorMessage, UErrorCode& status) {
229 if (U_FAILURE(status)) {
230 return false;
231 }
232 LocalPointer<DecimalFormat> fmtPtr(newDecimalFormat(tuple, status));
233 if (U_FAILURE(status)) {
234 appendErrorMessage.append("Error creating DecimalFormat.");
235 return false;
236 }
237 adjustDecimalFormat(tuple, *fmtPtr, appendErrorMessage);
238 if (appendErrorMessage.length() > 0) {
239 return false;
240 }
241 DecimalQuantity digitList;
242 strToDigitList(tuple.format, digitList, status);
243 {
244 UnicodeString appendTo;
245 format(*fmtPtr, digitList, appendTo, status);
246 if (U_FAILURE(status)) {
247 appendErrorMessage.append("Error formatting.");
248 return false;
249 }
250 if (appendTo != tuple.output) {
251 appendErrorMessage.append(
252 UnicodeString("Expected: ") + tuple.output + ", got: " + appendTo);
253 return false;
254 }
255 }
256 double doubleVal = digitList.toDouble();
257 DecimalQuantity doubleCheck;
258 doubleCheck.setToDouble(doubleVal);
259 if (digitList == doubleCheck) { // skip cases where the double does not round-trip
260 UnicodeString appendTo;
261 format(*fmtPtr, doubleVal, appendTo, status);
262 if (U_FAILURE(status)) {
263 appendErrorMessage.append("Error formatting.");
264 return false;
265 }
266 if (appendTo != tuple.output) {
267 appendErrorMessage.append(
268 UnicodeString("double Expected: ") + tuple.output + ", got: " + appendTo);
269 return false;
270 }
271 }
272 if (!uprv_isNaN(doubleVal) && !uprv_isInfinite(doubleVal) && digitList.fitsInLong()) {
273 int64_t intVal = digitList.toLong();
274 {
275 UnicodeString appendTo;
276 format(*fmtPtr, intVal, appendTo, status);
277 if (U_FAILURE(status)) {
278 appendErrorMessage.append("Error formatting.");
279 return false;
280 }
281 if (appendTo != tuple.output) {
282 appendErrorMessage.append(
283 UnicodeString("int64 Expected: ") + tuple.output + ", got: " + appendTo);
284 return false;
285 }
286 }
287 }
288 return true;
289 }
290
isToPatternPass(const NumberFormatTestTuple & tuple,UnicodeString & appendErrorMessage,UErrorCode & status)291 UBool NumberFormatDataDrivenTest::isToPatternPass(const NumberFormatTestTuple& tuple,
292 UnicodeString& appendErrorMessage, UErrorCode& status) {
293 if (U_FAILURE(status)) {
294 return false;
295 }
296 LocalPointer<DecimalFormat> fmtPtr(newDecimalFormat(tuple, status));
297 if (U_FAILURE(status)) {
298 appendErrorMessage.append("Error creating DecimalFormat.");
299 return false;
300 }
301 adjustDecimalFormat(tuple, *fmtPtr, appendErrorMessage);
302 if (appendErrorMessage.length() > 0) {
303 return false;
304 }
305 if (tuple.toPatternFlag) {
306 UnicodeString actual;
307 fmtPtr->toPattern(actual);
308 if (actual != tuple.toPattern) {
309 appendErrorMessage.append(
310 UnicodeString("Expected: ") + tuple.toPattern + ", got: " + actual + ". ");
311 }
312 }
313 if (tuple.toLocalizedPatternFlag) {
314 UnicodeString actual;
315 fmtPtr->toLocalizedPattern(actual);
316 if (actual != tuple.toLocalizedPattern) {
317 appendErrorMessage.append(
318 UnicodeString("Expected: ") + tuple.toLocalizedPattern + ", got: " + actual + ". ");
319 }
320 }
321 return appendErrorMessage.length() == 0;
322 }
323
isParsePass(const NumberFormatTestTuple & tuple,UnicodeString & appendErrorMessage,UErrorCode & status)324 UBool NumberFormatDataDrivenTest::isParsePass(const NumberFormatTestTuple& tuple,
325 UnicodeString& appendErrorMessage, UErrorCode& status) {
326 if (U_FAILURE(status)) {
327 return false;
328 }
329 LocalPointer<DecimalFormat> fmtPtr(newDecimalFormat(tuple, status));
330 if (U_FAILURE(status)) {
331 appendErrorMessage.append("Error creating DecimalFormat.");
332 return false;
333 }
334 adjustDecimalFormat(tuple, *fmtPtr, appendErrorMessage);
335 if (appendErrorMessage.length() > 0) {
336 return false;
337 }
338 Formattable result;
339 ParsePosition ppos;
340 fmtPtr->parse(tuple.parse, result, ppos);
341 if (ppos.getIndex() == 0) {
342 appendErrorMessage.append("Parse failed; got error index ");
343 appendErrorMessage = appendErrorMessage + ppos.getErrorIndex();
344 return false;
345 }
346 if (tuple.output == "fail") {
347 appendErrorMessage.append(
348 UnicodeString("Parse succeeded: ") + result.getDouble() + ", but was expected to fail.");
349 return true; // true because failure handling is in the test suite
350 }
351 if (tuple.output == "NaN") {
352 if (!uprv_isNaN(result.getDouble())) {
353 appendErrorMessage.append(UnicodeString("Expected NaN, but got: ") + result.getDouble());
354 return false;
355 }
356 return true;
357 } else if (tuple.output == "Inf") {
358 if (!uprv_isInfinite(result.getDouble()) || result.getDouble() < 0) {
359 appendErrorMessage.append(UnicodeString("Expected Inf, but got: ") + result.getDouble());
360 return false;
361 }
362 return true;
363 } else if (tuple.output == "-Inf") {
364 if (!uprv_isInfinite(result.getDouble()) || result.getDouble() > 0) {
365 appendErrorMessage.append(UnicodeString("Expected -Inf, but got: ") + result.getDouble());
366 return false;
367 }
368 return true;
369 } else if (tuple.output == "-0.0") {
370 if (!std::signbit(result.getDouble()) || result.getDouble() != 0) {
371 appendErrorMessage.append(UnicodeString("Expected -0.0, but got: ") + result.getDouble());
372 return false;
373 }
374 return true;
375 }
376 // All other cases parse to a DecimalQuantity, not a double.
377
378 DecimalQuantity expectedQuantity;
379 strToDigitList(tuple.output, expectedQuantity, status);
380 UnicodeString expectedString = expectedQuantity.toScientificString();
381 if (U_FAILURE(status)) {
382 appendErrorMessage.append("[Error parsing decnumber] ");
383 // If this happens, assume that tuple.output is exactly the same format as
384 // DecimalQuantity.toScientificString()
385 expectedString = tuple.output;
386 status = U_ZERO_ERROR;
387 }
388 UnicodeString actualString = result.getDecimalQuantity()->toScientificString();
389 if (expectedString != actualString) {
390 appendErrorMessage.append(
391 UnicodeString("Expected: ") + tuple.output + " (i.e., " + expectedString + "), but got: " +
392 actualString + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")");
393 return false;
394 }
395
396 return true;
397 }
398
isParseCurrencyPass(const NumberFormatTestTuple & tuple,UnicodeString & appendErrorMessage,UErrorCode & status)399 UBool NumberFormatDataDrivenTest::isParseCurrencyPass(const NumberFormatTestTuple& tuple,
400 UnicodeString& appendErrorMessage,
401 UErrorCode& status) {
402 if (U_FAILURE(status)) {
403 return false;
404 }
405 LocalPointer<DecimalFormat> fmtPtr(newDecimalFormat(tuple, status));
406 if (U_FAILURE(status)) {
407 appendErrorMessage.append("Error creating DecimalFormat.");
408 return false;
409 }
410 adjustDecimalFormat(tuple, *fmtPtr, appendErrorMessage);
411 if (appendErrorMessage.length() > 0) {
412 return false;
413 }
414 ParsePosition ppos;
415 LocalPointer<CurrencyAmount> currAmt(
416 fmtPtr->parseCurrency(tuple.parse, ppos));
417 if (ppos.getIndex() == 0) {
418 appendErrorMessage.append("Parse failed; got error index ");
419 appendErrorMessage = appendErrorMessage + ppos.getErrorIndex();
420 return false;
421 }
422 UnicodeString currStr(currAmt->getISOCurrency());
423 U_ASSERT(currAmt->getNumber().getDecimalQuantity() != nullptr); // no doubles in currency tests
424 UnicodeString resultStr = currAmt->getNumber().getDecimalQuantity()->toScientificString();
425 if (tuple.output == "fail") {
426 appendErrorMessage.append(
427 UnicodeString("Parse succeeded: ") + resultStr + ", but was expected to fail.");
428 return true; // true because failure handling is in the test suite
429 }
430
431 DecimalQuantity expectedQuantity;
432 strToDigitList(tuple.output, expectedQuantity, status);
433 UnicodeString expectedString = expectedQuantity.toScientificString();
434 if (U_FAILURE(status)) {
435 appendErrorMessage.append("Error parsing decnumber");
436 // If this happens, assume that tuple.output is exactly the same format as
437 // DecimalQuantity.toNumberString()
438 expectedString = tuple.output;
439 status = U_ZERO_ERROR;
440 }
441 if (expectedString != resultStr) {
442 appendErrorMessage.append(
443 UnicodeString("Expected: ") + tuple.output + " (i.e., " + expectedString + "), but got: " +
444 resultStr + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")");
445 return false;
446 }
447
448 if (currStr != tuple.outputCurrency) {
449 appendErrorMessage.append(
450 UnicodeString(
451 "Expected currency: ") + tuple.outputCurrency + ", got: " + currStr + ". ");
452 return false;
453 }
454 return true;
455 }
456
TestNumberFormatTestTuple()457 void NumberFormatDataDrivenTest::TestNumberFormatTestTuple() {
458 NumberFormatTestTuple tuple;
459 UErrorCode status = U_ZERO_ERROR;
460
461 tuple.setField(
462 NumberFormatTestTuple::getFieldByName("locale"), "en", status);
463 tuple.setField(
464 NumberFormatTestTuple::getFieldByName("pattern"), "#,##0.00", status);
465 tuple.setField(
466 NumberFormatTestTuple::getFieldByName("minIntegerDigits"), "-10", status);
467 if (!assertSuccess("", status)) {
468 return;
469 }
470
471 // only what we set should be set.
472 assertEquals("", "en", tuple.locale.getName());
473 assertEquals("", "#,##0.00", tuple.pattern);
474 assertEquals("", -10, tuple.minIntegerDigits);
475 assertTrue("", tuple.localeFlag);
476 assertTrue("", tuple.patternFlag);
477 assertTrue("", tuple.minIntegerDigitsFlag);
478 assertFalse("", tuple.formatFlag);
479
480 UnicodeString appendTo;
481 assertEquals(
482 "", "{locale: en, pattern: #,##0.00, minIntegerDigits: -10}", tuple.toString(appendTo));
483
484 tuple.clear();
485 appendTo.remove();
486 assertEquals(
487 "", "{}", tuple.toString(appendTo));
488 tuple.setField(
489 NumberFormatTestTuple::getFieldByName("aBadFieldName"), "someValue", status);
490 if (status != U_ILLEGAL_ARGUMENT_ERROR) {
491 errln("Expected U_ILLEGAL_ARGUMENT_ERROR");
492 }
493 status = U_ZERO_ERROR;
494 tuple.setField(
495 NumberFormatTestTuple::getFieldByName("minIntegerDigits"), "someBadValue", status);
496 if (status != U_ILLEGAL_ARGUMENT_ERROR) {
497 errln("Expected U_ILLEGAL_ARGUMENT_ERROR");
498 }
499 }
500
TestDataDrivenICU4C()501 void NumberFormatDataDrivenTest::TestDataDrivenICU4C() {
502 run("numberformattestspecification.txt", true);
503 }
504
505 #endif // !UCONFIG_NO_FORMATTING
506