• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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