1 // © 2018 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 #include "unicode/utypes.h"
6 #if !UCONFIG_NO_FORMATTING
9 #include "number_decimalquantity.h"
14 using icu::number::impl::DecimalQuantity
;
16 void NumberFormatDataDrivenTest::runIndexedTest(int32_t index
, UBool exec
, const char*& name
, char*) {
18 logln("TestSuite NumberFormatDataDrivenTest: ");
21 TESTCASE_AUTO(TestNumberFormatTestTuple
);
22 TESTCASE_AUTO(TestDataDrivenICU4C
);
26 static DecimalQuantity
&
27 strToDigitList(const UnicodeString
& str
, DecimalQuantity
& digitList
, UErrorCode
& status
) {
28 if (U_FAILURE(status
)) {
32 digitList
.setToDouble(uprv_getNaN());
36 digitList
.setToDouble(-1 * uprv_getInfinity());
40 digitList
.setToDouble(uprv_getInfinity());
43 CharString formatValue
;
44 formatValue
.appendInvariantChars(str
, status
);
45 digitList
.setToDecNumber({formatValue
.data(), formatValue
.length()}, status
);
50 format(const DecimalFormat
& fmt
, const DecimalQuantity
& digitList
, UnicodeString
& appendTo
,
52 if (U_FAILURE(status
)) {
55 FieldPosition
fpos(FieldPosition::DONT_CARE
);
56 return fmt
.format(digitList
, appendTo
, fpos
, status
);
61 format(const DecimalFormat
& fmt
, T value
, UnicodeString
& appendTo
, UErrorCode
& status
) {
62 if (U_FAILURE(status
)) {
65 FieldPosition
fpos(FieldPosition::DONT_CARE
);
66 return fmt
.format(value
, appendTo
, fpos
, status
);
69 static void adjustDecimalFormat(const NumberFormatTestTuple
& tuple
, DecimalFormat
& fmt
,
70 UnicodeString
& appendErrorMessage
) {
71 if (tuple
.minIntegerDigitsFlag
) {
72 fmt
.setMinimumIntegerDigits(tuple
.minIntegerDigits
);
74 if (tuple
.maxIntegerDigitsFlag
) {
75 fmt
.setMaximumIntegerDigits(tuple
.maxIntegerDigits
);
77 if (tuple
.minFractionDigitsFlag
) {
78 fmt
.setMinimumFractionDigits(tuple
.minFractionDigits
);
80 if (tuple
.maxFractionDigitsFlag
) {
81 fmt
.setMaximumFractionDigits(tuple
.maxFractionDigits
);
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.");
92 if (tuple
.minGroupingDigitsFlag
) {
93 fmt
.setMinimumGroupingDigits(tuple
.minGroupingDigits
);
95 if (tuple
.useSigDigitsFlag
) {
96 fmt
.setSignificantDigitsUsed(tuple
.useSigDigits
!= 0);
98 if (tuple
.minSigDigitsFlag
) {
99 fmt
.setMinimumSignificantDigits(tuple
.minSigDigits
);
101 if (tuple
.maxSigDigitsFlag
) {
102 fmt
.setMaximumSignificantDigits(tuple
.maxSigDigits
);
104 if (tuple
.useGroupingFlag
) {
105 fmt
.setGroupingUsed(tuple
.useGrouping
!= 0);
107 if (tuple
.multiplierFlag
) {
108 fmt
.setMultiplier(tuple
.multiplier
);
110 if (tuple
.roundingIncrementFlag
) {
111 fmt
.setRoundingIncrement(tuple
.roundingIncrement
);
113 if (tuple
.formatWidthFlag
) {
114 fmt
.setFormatWidth(tuple
.formatWidth
);
116 if (tuple
.padCharacterFlag
) {
117 fmt
.setPadCharacter(tuple
.padCharacter
);
119 if (tuple
.useScientificFlag
) {
120 fmt
.setScientificNotation(tuple
.useScientific
!= 0);
122 if (tuple
.groupingFlag
) {
123 fmt
.setGroupingSize(tuple
.grouping
);
125 if (tuple
.grouping2Flag
) {
126 fmt
.setSecondaryGroupingSize(tuple
.grouping2
);
128 if (tuple
.roundingModeFlag
) {
129 fmt
.setRoundingMode(tuple
.roundingMode
);
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.");
138 if (tuple
.minimumExponentDigitsFlag
) {
139 fmt
.setMinimumExponentDigits(tuple
.minimumExponentDigits
);
141 if (tuple
.exponentSignAlwaysShownFlag
) {
142 fmt
.setExponentSignAlwaysShown(tuple
.exponentSignAlwaysShown
!= 0);
144 if (tuple
.decimalSeparatorAlwaysShownFlag
) {
145 fmt
.setDecimalSeparatorAlwaysShown(
146 tuple
.decimalSeparatorAlwaysShown
!= 0);
148 if (tuple
.padPositionFlag
) {
149 fmt
.setPadPosition(tuple
.padPosition
);
151 if (tuple
.positivePrefixFlag
) {
152 fmt
.setPositivePrefix(tuple
.positivePrefix
);
154 if (tuple
.positiveSuffixFlag
) {
155 fmt
.setPositiveSuffix(tuple
.positiveSuffix
);
157 if (tuple
.negativePrefixFlag
) {
158 fmt
.setNegativePrefix(tuple
.negativePrefix
);
160 if (tuple
.negativeSuffixFlag
) {
161 fmt
.setNegativeSuffix(tuple
.negativeSuffix
);
163 if (tuple
.signAlwaysShownFlag
) {
164 fmt
.setSignAlwaysShown(tuple
.signAlwaysShown
!= 0);
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.");
173 fmt
.setLenient(NFTT_GET_FIELD(tuple
, lenient
, 1) != 0);
174 if (tuple
.parseIntegerOnlyFlag
) {
175 fmt
.setParseIntegerOnly(tuple
.parseIntegerOnly
!= 0);
177 if (tuple
.decimalPatternMatchRequiredFlag
) {
178 fmt
.setDecimalPatternMatchRequired(
179 tuple
.decimalPatternMatchRequired
!= 0);
181 if (tuple
.parseNoExponentFlag
) {
182 UErrorCode status
= U_ZERO_ERROR
;
184 UNUM_PARSE_NO_EXPONENT
, tuple
.parseNoExponent
, status
);
185 if (U_FAILURE(status
)) {
186 appendErrorMessage
.append("Error setting parse no exponent flag.");
189 if (tuple
.parseCaseSensitiveFlag
) {
190 fmt
.setParseCaseSensitive(tuple
.parseCaseSensitive
!= 0);
194 static DecimalFormat
*
195 newDecimalFormat(const Locale
& locale
, const UnicodeString
& pattern
, UErrorCode
& status
) {
196 if (U_FAILURE(status
)) {
199 LocalPointer
<DecimalFormatSymbols
> symbols(
200 new DecimalFormatSymbols(locale
, status
), status
);
201 if (U_FAILURE(status
)) {
205 LocalPointer
<DecimalFormat
> result(
207 pattern
, symbols
.getAlias(), perror
, status
), status
);
208 if (!result
.isNull()) {
211 if (U_FAILURE(status
)) {
214 return result
.orphan();
217 static DecimalFormat
* newDecimalFormat(const NumberFormatTestTuple
& tuple
, UErrorCode
& status
) {
218 if (U_FAILURE(status
)) {
222 return newDecimalFormat(NFTT_GET_FIELD(tuple
, locale
, en
),
223 NFTT_GET_FIELD(tuple
, pattern
, "0"),
227 UBool
NumberFormatDataDrivenTest::isFormatPass(const NumberFormatTestTuple
& tuple
,
228 UnicodeString
& appendErrorMessage
, UErrorCode
& status
) {
229 if (U_FAILURE(status
)) {
232 LocalPointer
<DecimalFormat
> fmtPtr(newDecimalFormat(tuple
, status
));
233 if (U_FAILURE(status
)) {
234 appendErrorMessage
.append("Error creating DecimalFormat.");
237 adjustDecimalFormat(tuple
, *fmtPtr
, appendErrorMessage
);
238 if (appendErrorMessage
.length() > 0) {
241 DecimalQuantity digitList
;
242 strToDigitList(tuple
.format
, digitList
, status
);
244 UnicodeString appendTo
;
245 format(*fmtPtr
, digitList
, appendTo
, status
);
246 if (U_FAILURE(status
)) {
247 appendErrorMessage
.append("Error formatting.");
250 if (appendTo
!= tuple
.output
) {
251 appendErrorMessage
.append(
252 UnicodeString("Expected: ") + tuple
.output
+ ", got: " + appendTo
);
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.");
266 if (appendTo
!= tuple
.output
) {
267 appendErrorMessage
.append(
268 UnicodeString("double Expected: ") + tuple
.output
+ ", got: " + appendTo
);
272 if (!uprv_isNaN(doubleVal
) && !uprv_isInfinite(doubleVal
) && digitList
.fitsInLong()) {
273 int64_t intVal
= digitList
.toLong();
275 UnicodeString appendTo
;
276 format(*fmtPtr
, intVal
, appendTo
, status
);
277 if (U_FAILURE(status
)) {
278 appendErrorMessage
.append("Error formatting.");
281 if (appendTo
!= tuple
.output
) {
282 appendErrorMessage
.append(
283 UnicodeString("int64 Expected: ") + tuple
.output
+ ", got: " + appendTo
);
291 UBool
NumberFormatDataDrivenTest::isToPatternPass(const NumberFormatTestTuple
& tuple
,
292 UnicodeString
& appendErrorMessage
, UErrorCode
& status
) {
293 if (U_FAILURE(status
)) {
296 LocalPointer
<DecimalFormat
> fmtPtr(newDecimalFormat(tuple
, status
));
297 if (U_FAILURE(status
)) {
298 appendErrorMessage
.append("Error creating DecimalFormat.");
301 adjustDecimalFormat(tuple
, *fmtPtr
, appendErrorMessage
);
302 if (appendErrorMessage
.length() > 0) {
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
+ ". ");
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
+ ". ");
321 return appendErrorMessage
.length() == 0;
324 UBool
NumberFormatDataDrivenTest::isParsePass(const NumberFormatTestTuple
& tuple
,
325 UnicodeString
& appendErrorMessage
, UErrorCode
& status
) {
326 if (U_FAILURE(status
)) {
329 LocalPointer
<DecimalFormat
> fmtPtr(newDecimalFormat(tuple
, status
));
330 if (U_FAILURE(status
)) {
331 appendErrorMessage
.append("Error creating DecimalFormat.");
334 adjustDecimalFormat(tuple
, *fmtPtr
, appendErrorMessage
);
335 if (appendErrorMessage
.length() > 0) {
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();
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
351 if (tuple
.output
== "NaN") {
352 if (!uprv_isNaN(result
.getDouble())) {
353 appendErrorMessage
.append(UnicodeString("Expected NaN, but got: ") + result
.getDouble());
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());
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());
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());
376 // All other cases parse to a DecimalQuantity, not a double.
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
;
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() + ")");
399 UBool
NumberFormatDataDrivenTest::isParseCurrencyPass(const NumberFormatTestTuple
& tuple
,
400 UnicodeString
& appendErrorMessage
,
401 UErrorCode
& status
) {
402 if (U_FAILURE(status
)) {
405 LocalPointer
<DecimalFormat
> fmtPtr(newDecimalFormat(tuple
, status
));
406 if (U_FAILURE(status
)) {
407 appendErrorMessage
.append("Error creating DecimalFormat.");
410 adjustDecimalFormat(tuple
, *fmtPtr
, appendErrorMessage
);
411 if (appendErrorMessage
.length() > 0) {
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();
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
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
;
441 if (expectedString
!= resultStr
) {
442 appendErrorMessage
.append(
443 UnicodeString("Expected: ") + tuple
.output
+ " (i.e., " + expectedString
+ "), but got: " +
444 resultStr
+ " (" + ppos
.getIndex() + ":" + ppos
.getErrorIndex() + ")");
448 if (currStr
!= tuple
.outputCurrency
) {
449 appendErrorMessage
.append(
451 "Expected currency: ") + tuple
.outputCurrency
+ ", got: " + currStr
+ ". ");
457 void NumberFormatDataDrivenTest::TestNumberFormatTestTuple() {
458 NumberFormatTestTuple tuple
;
459 UErrorCode status
= U_ZERO_ERROR
;
462 NumberFormatTestTuple::getFieldByName("locale"), "en", status
);
464 NumberFormatTestTuple::getFieldByName("pattern"), "#,##0.00", status
);
466 NumberFormatTestTuple::getFieldByName("minIntegerDigits"), "-10", status
);
467 if (!assertSuccess("", status
)) {
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
);
480 UnicodeString appendTo
;
482 "", "{locale: en, pattern: #,##0.00, minIntegerDigits: -10}", tuple
.toString(appendTo
));
487 "", "{}", tuple
.toString(appendTo
));
489 NumberFormatTestTuple::getFieldByName("aBadFieldName"), "someValue", status
);
490 if (status
!= U_ILLEGAL_ARGUMENT_ERROR
) {
491 errln("Expected U_ILLEGAL_ARGUMENT_ERROR");
493 status
= U_ZERO_ERROR
;
495 NumberFormatTestTuple::getFieldByName("minIntegerDigits"), "someBadValue", status
);
496 if (status
!= U_ILLEGAL_ARGUMENT_ERROR
) {
497 errln("Expected U_ILLEGAL_ARGUMENT_ERROR");
501 void NumberFormatDataDrivenTest::TestDataDrivenICU4C() {
502 run("numberformattestspecification.txt", TRUE
);
505 #endif // !UCONFIG_NO_FORMATTING