| // © 2018 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html |
| |
| #include "unicode/utypes.h" |
| |
| #if !UCONFIG_NO_FORMATTING |
| |
| #include "numfmtst.h" |
| #include "number_decimalquantity.h" |
| #include "putilimp.h" |
| #include "charstr.h" |
| #include <cmath> |
| |
| using icu::number::impl::DecimalQuantity; |
| |
| void NumberFormatDataDrivenTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) { |
| if (exec) { |
| logln("TestSuite NumberFormatDataDrivenTest: "); |
| } |
| TESTCASE_AUTO_BEGIN; |
| TESTCASE_AUTO(TestNumberFormatTestTuple); |
| TESTCASE_AUTO(TestDataDrivenICU4C); |
| TESTCASE_AUTO_END; |
| } |
| |
| static DecimalQuantity& |
| strToDigitList(const UnicodeString& str, DecimalQuantity& digitList, UErrorCode& status) { |
| if (U_FAILURE(status)) { |
| return digitList; |
| } |
| if (str == "NaN") { |
| digitList.setToDouble(uprv_getNaN()); |
| return digitList; |
| } |
| if (str == "-Inf") { |
| digitList.setToDouble(-1 * uprv_getInfinity()); |
| return digitList; |
| } |
| if (str == "Inf") { |
| digitList.setToDouble(uprv_getInfinity()); |
| return digitList; |
| } |
| CharString formatValue; |
| formatValue.appendInvariantChars(str, status); |
| digitList.setToDecNumber({formatValue.data(), formatValue.length()}, status); |
| return digitList; |
| } |
| |
| static UnicodeString& |
| format(const DecimalFormat& fmt, const DecimalQuantity& digitList, UnicodeString& appendTo, |
| UErrorCode& status) { |
| if (U_FAILURE(status)) { |
| return appendTo; |
| } |
| FieldPosition fpos(FieldPosition::DONT_CARE); |
| return fmt.format(digitList, appendTo, fpos, status); |
| } |
| |
| template<class T> |
| static UnicodeString& |
| format(const DecimalFormat& fmt, T value, UnicodeString& appendTo, UErrorCode& status) { |
| if (U_FAILURE(status)) { |
| return appendTo; |
| } |
| FieldPosition fpos(FieldPosition::DONT_CARE); |
| return fmt.format(value, appendTo, fpos, status); |
| } |
| |
| static void adjustDecimalFormat(const NumberFormatTestTuple& tuple, DecimalFormat& fmt, |
| UnicodeString& appendErrorMessage) { |
| if (tuple.minIntegerDigitsFlag) { |
| fmt.setMinimumIntegerDigits(tuple.minIntegerDigits); |
| } |
| if (tuple.maxIntegerDigitsFlag) { |
| fmt.setMaximumIntegerDigits(tuple.maxIntegerDigits); |
| } |
| if (tuple.minFractionDigitsFlag) { |
| fmt.setMinimumFractionDigits(tuple.minFractionDigits); |
| } |
| if (tuple.maxFractionDigitsFlag) { |
| fmt.setMaximumFractionDigits(tuple.maxFractionDigits); |
| } |
| if (tuple.currencyFlag) { |
| UErrorCode status = U_ZERO_ERROR; |
| UnicodeString currency(tuple.currency); |
| const UChar* terminatedCurrency = currency.getTerminatedBuffer(); |
| fmt.setCurrency(terminatedCurrency, status); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error setting currency."); |
| } |
| } |
| if (tuple.minGroupingDigitsFlag) { |
| fmt.setMinimumGroupingDigits(tuple.minGroupingDigits); |
| } |
| if (tuple.useSigDigitsFlag) { |
| fmt.setSignificantDigitsUsed(tuple.useSigDigits != 0); |
| } |
| if (tuple.minSigDigitsFlag) { |
| fmt.setMinimumSignificantDigits(tuple.minSigDigits); |
| } |
| if (tuple.maxSigDigitsFlag) { |
| fmt.setMaximumSignificantDigits(tuple.maxSigDigits); |
| } |
| if (tuple.useGroupingFlag) { |
| fmt.setGroupingUsed(tuple.useGrouping != 0); |
| } |
| if (tuple.multiplierFlag) { |
| fmt.setMultiplier(tuple.multiplier); |
| } |
| if (tuple.roundingIncrementFlag) { |
| fmt.setRoundingIncrement(tuple.roundingIncrement); |
| } |
| if (tuple.formatWidthFlag) { |
| fmt.setFormatWidth(tuple.formatWidth); |
| } |
| if (tuple.padCharacterFlag) { |
| fmt.setPadCharacter(tuple.padCharacter); |
| } |
| if (tuple.useScientificFlag) { |
| fmt.setScientificNotation(tuple.useScientific != 0); |
| } |
| if (tuple.groupingFlag) { |
| fmt.setGroupingSize(tuple.grouping); |
| } |
| if (tuple.grouping2Flag) { |
| fmt.setSecondaryGroupingSize(tuple.grouping2); |
| } |
| if (tuple.roundingModeFlag) { |
| fmt.setRoundingMode(tuple.roundingMode); |
| } |
| if (tuple.currencyUsageFlag) { |
| UErrorCode status = U_ZERO_ERROR; |
| fmt.setCurrencyUsage(tuple.currencyUsage, &status); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("CurrencyUsage: error setting."); |
| } |
| } |
| if (tuple.minimumExponentDigitsFlag) { |
| fmt.setMinimumExponentDigits(tuple.minimumExponentDigits); |
| } |
| if (tuple.exponentSignAlwaysShownFlag) { |
| fmt.setExponentSignAlwaysShown(tuple.exponentSignAlwaysShown != 0); |
| } |
| if (tuple.decimalSeparatorAlwaysShownFlag) { |
| fmt.setDecimalSeparatorAlwaysShown( |
| tuple.decimalSeparatorAlwaysShown != 0); |
| } |
| if (tuple.padPositionFlag) { |
| fmt.setPadPosition(tuple.padPosition); |
| } |
| if (tuple.positivePrefixFlag) { |
| fmt.setPositivePrefix(tuple.positivePrefix); |
| } |
| if (tuple.positiveSuffixFlag) { |
| fmt.setPositiveSuffix(tuple.positiveSuffix); |
| } |
| if (tuple.negativePrefixFlag) { |
| fmt.setNegativePrefix(tuple.negativePrefix); |
| } |
| if (tuple.negativeSuffixFlag) { |
| fmt.setNegativeSuffix(tuple.negativeSuffix); |
| } |
| if (tuple.signAlwaysShownFlag) { |
| fmt.setSignAlwaysShown(tuple.signAlwaysShown != 0); |
| } |
| if (tuple.localizedPatternFlag) { |
| UErrorCode status = U_ZERO_ERROR; |
| fmt.applyLocalizedPattern(tuple.localizedPattern, status); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error setting localized pattern."); |
| } |
| } |
| fmt.setLenient(NFTT_GET_FIELD(tuple, lenient, 1) != 0); |
| if (tuple.parseIntegerOnlyFlag) { |
| fmt.setParseIntegerOnly(tuple.parseIntegerOnly != 0); |
| } |
| if (tuple.decimalPatternMatchRequiredFlag) { |
| fmt.setDecimalPatternMatchRequired( |
| tuple.decimalPatternMatchRequired != 0); |
| } |
| if (tuple.parseNoExponentFlag) { |
| UErrorCode status = U_ZERO_ERROR; |
| fmt.setAttribute( |
| UNUM_PARSE_NO_EXPONENT, tuple.parseNoExponent, status); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error setting parse no exponent flag."); |
| } |
| } |
| if (tuple.parseCaseSensitiveFlag) { |
| fmt.setParseCaseSensitive(tuple.parseCaseSensitive != 0); |
| } |
| } |
| |
| static DecimalFormat* |
| newDecimalFormat(const Locale& locale, const UnicodeString& pattern, UErrorCode& status) { |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| LocalPointer<DecimalFormatSymbols> symbols( |
| new DecimalFormatSymbols(locale, status), status); |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| UParseError perror; |
| LocalPointer<DecimalFormat> result( |
| new DecimalFormat( |
| pattern, symbols.getAlias(), perror, status), status); |
| if (!result.isNull()) { |
| symbols.orphan(); |
| } |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| return result.orphan(); |
| } |
| |
| static DecimalFormat* newDecimalFormat(const NumberFormatTestTuple& tuple, UErrorCode& status) { |
| if (U_FAILURE(status)) { |
| return NULL; |
| } |
| Locale en("en"); |
| return newDecimalFormat(NFTT_GET_FIELD(tuple, locale, en), |
| NFTT_GET_FIELD(tuple, pattern, "0"), |
| status); |
| } |
| |
| UBool NumberFormatDataDrivenTest::isFormatPass(const NumberFormatTestTuple& tuple, |
| UnicodeString& appendErrorMessage, UErrorCode& status) { |
| if (U_FAILURE(status)) { |
| return FALSE; |
| } |
| LocalPointer<DecimalFormat> fmtPtr(newDecimalFormat(tuple, status)); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error creating DecimalFormat."); |
| return FALSE; |
| } |
| adjustDecimalFormat(tuple, *fmtPtr, appendErrorMessage); |
| if (appendErrorMessage.length() > 0) { |
| return FALSE; |
| } |
| DecimalQuantity digitList; |
| strToDigitList(tuple.format, digitList, status); |
| { |
| UnicodeString appendTo; |
| format(*fmtPtr, digitList, appendTo, status); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error formatting."); |
| return FALSE; |
| } |
| if (appendTo != tuple.output) { |
| appendErrorMessage.append( |
| UnicodeString("Expected: ") + tuple.output + ", got: " + appendTo); |
| return FALSE; |
| } |
| } |
| double doubleVal = digitList.toDouble(); |
| DecimalQuantity doubleCheck; |
| doubleCheck.setToDouble(doubleVal); |
| if (digitList == doubleCheck) { // skip cases where the double does not round-trip |
| UnicodeString appendTo; |
| format(*fmtPtr, doubleVal, appendTo, status); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error formatting."); |
| return FALSE; |
| } |
| if (appendTo != tuple.output) { |
| appendErrorMessage.append( |
| UnicodeString("double Expected: ") + tuple.output + ", got: " + appendTo); |
| return FALSE; |
| } |
| } |
| if (!uprv_isNaN(doubleVal) && !uprv_isInfinite(doubleVal) && digitList.fitsInLong()) { |
| int64_t intVal = digitList.toLong(); |
| { |
| UnicodeString appendTo; |
| format(*fmtPtr, intVal, appendTo, status); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error formatting."); |
| return FALSE; |
| } |
| if (appendTo != tuple.output) { |
| appendErrorMessage.append( |
| UnicodeString("int64 Expected: ") + tuple.output + ", got: " + appendTo); |
| return FALSE; |
| } |
| } |
| } |
| return TRUE; |
| } |
| |
| UBool NumberFormatDataDrivenTest::isToPatternPass(const NumberFormatTestTuple& tuple, |
| UnicodeString& appendErrorMessage, UErrorCode& status) { |
| if (U_FAILURE(status)) { |
| return FALSE; |
| } |
| LocalPointer<DecimalFormat> fmtPtr(newDecimalFormat(tuple, status)); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error creating DecimalFormat."); |
| return FALSE; |
| } |
| adjustDecimalFormat(tuple, *fmtPtr, appendErrorMessage); |
| if (appendErrorMessage.length() > 0) { |
| return FALSE; |
| } |
| if (tuple.toPatternFlag) { |
| UnicodeString actual; |
| fmtPtr->toPattern(actual); |
| if (actual != tuple.toPattern) { |
| appendErrorMessage.append( |
| UnicodeString("Expected: ") + tuple.toPattern + ", got: " + actual + ". "); |
| } |
| } |
| if (tuple.toLocalizedPatternFlag) { |
| UnicodeString actual; |
| fmtPtr->toLocalizedPattern(actual); |
| if (actual != tuple.toLocalizedPattern) { |
| appendErrorMessage.append( |
| UnicodeString("Expected: ") + tuple.toLocalizedPattern + ", got: " + actual + ". "); |
| } |
| } |
| return appendErrorMessage.length() == 0; |
| } |
| |
| UBool NumberFormatDataDrivenTest::isParsePass(const NumberFormatTestTuple& tuple, |
| UnicodeString& appendErrorMessage, UErrorCode& status) { |
| if (U_FAILURE(status)) { |
| return FALSE; |
| } |
| LocalPointer<DecimalFormat> fmtPtr(newDecimalFormat(tuple, status)); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error creating DecimalFormat."); |
| return FALSE; |
| } |
| adjustDecimalFormat(tuple, *fmtPtr, appendErrorMessage); |
| if (appendErrorMessage.length() > 0) { |
| return FALSE; |
| } |
| Formattable result; |
| ParsePosition ppos; |
| fmtPtr->parse(tuple.parse, result, ppos); |
| if (ppos.getIndex() == 0) { |
| appendErrorMessage.append("Parse failed; got error index "); |
| appendErrorMessage = appendErrorMessage + ppos.getErrorIndex(); |
| return FALSE; |
| } |
| if (tuple.output == "fail") { |
| appendErrorMessage.append( |
| UnicodeString("Parse succeeded: ") + result.getDouble() + ", but was expected to fail."); |
| return TRUE; // TRUE because failure handling is in the test suite |
| } |
| if (tuple.output == "NaN") { |
| if (!uprv_isNaN(result.getDouble())) { |
| appendErrorMessage.append(UnicodeString("Expected NaN, but got: ") + result.getDouble()); |
| return FALSE; |
| } |
| return TRUE; |
| } else if (tuple.output == "Inf") { |
| if (!uprv_isInfinite(result.getDouble()) || result.getDouble() < 0) { |
| appendErrorMessage.append(UnicodeString("Expected Inf, but got: ") + result.getDouble()); |
| return FALSE; |
| } |
| return TRUE; |
| } else if (tuple.output == "-Inf") { |
| if (!uprv_isInfinite(result.getDouble()) || result.getDouble() > 0) { |
| appendErrorMessage.append(UnicodeString("Expected -Inf, but got: ") + result.getDouble()); |
| return FALSE; |
| } |
| return TRUE; |
| } else if (tuple.output == "-0.0") { |
| if (!std::signbit(result.getDouble()) || result.getDouble() != 0) { |
| appendErrorMessage.append(UnicodeString("Expected -0.0, but got: ") + result.getDouble()); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| // All other cases parse to a DecimalQuantity, not a double. |
| |
| DecimalQuantity expectedQuantity; |
| strToDigitList(tuple.output, expectedQuantity, status); |
| UnicodeString expectedString = expectedQuantity.toScientificString(); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("[Error parsing decnumber] "); |
| // If this happens, assume that tuple.output is exactly the same format as |
| // DecimalQuantity.toScientificString() |
| expectedString = tuple.output; |
| status = U_ZERO_ERROR; |
| } |
| UnicodeString actualString = result.getDecimalQuantity()->toScientificString(); |
| if (expectedString != actualString) { |
| appendErrorMessage.append( |
| UnicodeString("Expected: ") + tuple.output + " (i.e., " + expectedString + "), but got: " + |
| actualString + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")"); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| UBool NumberFormatDataDrivenTest::isParseCurrencyPass(const NumberFormatTestTuple& tuple, |
| UnicodeString& appendErrorMessage, |
| UErrorCode& status) { |
| if (U_FAILURE(status)) { |
| return FALSE; |
| } |
| LocalPointer<DecimalFormat> fmtPtr(newDecimalFormat(tuple, status)); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error creating DecimalFormat."); |
| return FALSE; |
| } |
| adjustDecimalFormat(tuple, *fmtPtr, appendErrorMessage); |
| if (appendErrorMessage.length() > 0) { |
| return FALSE; |
| } |
| ParsePosition ppos; |
| LocalPointer<CurrencyAmount> currAmt( |
| fmtPtr->parseCurrency(tuple.parse, ppos)); |
| if (ppos.getIndex() == 0) { |
| appendErrorMessage.append("Parse failed; got error index "); |
| appendErrorMessage = appendErrorMessage + ppos.getErrorIndex(); |
| return FALSE; |
| } |
| UnicodeString currStr(currAmt->getISOCurrency()); |
| U_ASSERT(currAmt->getNumber().getDecimalQuantity() != nullptr); // no doubles in currency tests |
| UnicodeString resultStr = currAmt->getNumber().getDecimalQuantity()->toScientificString(); |
| if (tuple.output == "fail") { |
| appendErrorMessage.append( |
| UnicodeString("Parse succeeded: ") + resultStr + ", but was expected to fail."); |
| return TRUE; // TRUE because failure handling is in the test suite |
| } |
| |
| DecimalQuantity expectedQuantity; |
| strToDigitList(tuple.output, expectedQuantity, status); |
| UnicodeString expectedString = expectedQuantity.toScientificString(); |
| if (U_FAILURE(status)) { |
| appendErrorMessage.append("Error parsing decnumber"); |
| // If this happens, assume that tuple.output is exactly the same format as |
| // DecimalQuantity.toNumberString() |
| expectedString = tuple.output; |
| status = U_ZERO_ERROR; |
| } |
| if (expectedString != resultStr) { |
| appendErrorMessage.append( |
| UnicodeString("Expected: ") + tuple.output + " (i.e., " + expectedString + "), but got: " + |
| resultStr + " (" + ppos.getIndex() + ":" + ppos.getErrorIndex() + ")"); |
| return FALSE; |
| } |
| |
| if (currStr != tuple.outputCurrency) { |
| appendErrorMessage.append( |
| UnicodeString( |
| "Expected currency: ") + tuple.outputCurrency + ", got: " + currStr + ". "); |
| return FALSE; |
| } |
| return TRUE; |
| } |
| |
| void NumberFormatDataDrivenTest::TestNumberFormatTestTuple() { |
| NumberFormatTestTuple tuple; |
| UErrorCode status = U_ZERO_ERROR; |
| |
| tuple.setField( |
| NumberFormatTestTuple::getFieldByName("locale"), "en", status); |
| tuple.setField( |
| NumberFormatTestTuple::getFieldByName("pattern"), "#,##0.00", status); |
| tuple.setField( |
| NumberFormatTestTuple::getFieldByName("minIntegerDigits"), "-10", status); |
| if (!assertSuccess("", status)) { |
| return; |
| } |
| |
| // only what we set should be set. |
| assertEquals("", "en", tuple.locale.getName()); |
| assertEquals("", "#,##0.00", tuple.pattern); |
| assertEquals("", -10, tuple.minIntegerDigits); |
| assertTrue("", tuple.localeFlag); |
| assertTrue("", tuple.patternFlag); |
| assertTrue("", tuple.minIntegerDigitsFlag); |
| assertFalse("", tuple.formatFlag); |
| |
| UnicodeString appendTo; |
| assertEquals( |
| "", "{locale: en, pattern: #,##0.00, minIntegerDigits: -10}", tuple.toString(appendTo)); |
| |
| tuple.clear(); |
| appendTo.remove(); |
| assertEquals( |
| "", "{}", tuple.toString(appendTo)); |
| tuple.setField( |
| NumberFormatTestTuple::getFieldByName("aBadFieldName"), "someValue", status); |
| if (status != U_ILLEGAL_ARGUMENT_ERROR) { |
| errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); |
| } |
| status = U_ZERO_ERROR; |
| tuple.setField( |
| NumberFormatTestTuple::getFieldByName("minIntegerDigits"), "someBadValue", status); |
| if (status != U_ILLEGAL_ARGUMENT_ERROR) { |
| errln("Expected U_ILLEGAL_ARGUMENT_ERROR"); |
| } |
| } |
| |
| void NumberFormatDataDrivenTest::TestDataDrivenICU4C() { |
| run("numberformattestspecification.txt", TRUE); |
| } |
| |
| #endif // !UCONFIG_NO_FORMATTING |