+void
+IntlTestRBNF::TestSmallValues()
+{
+ UErrorCode status = U_ZERO_ERROR;
+ RuleBasedNumberFormat* formatter
+ = new RuleBasedNumberFormat(URBNF_SPELLOUT, Locale("en_US"), status);
+
+ if (U_FAILURE(status)) {
+ errcheckln(status, "FAIL: could not construct formatter - %s", u_errorName(status));
+ } else {
+ static const char* const testDataDefault[][2] = {
+ { "0.001", "zero point zero zero one" },
+ { "0.0001", "zero point zero zero zero one" },
+ { "0.00001", "zero point zero zero zero zero one" },
+ { "0.000001", "zero point zero zero zero zero zero one" },
+ { "0.0000001", "zero point zero zero zero zero zero zero one" },
+ { "0.00000001", "zero point zero zero zero zero zero zero zero one" },
+ { "0.000000001", "zero point zero zero zero zero zero zero zero zero one" },
+ { "0.0000000001", "zero point zero zero zero zero zero zero zero zero zero one" },
+ { "0.00000000001", "zero point zero zero zero zero zero zero zero zero zero zero one" },
+ { "0.000000000001", "zero point zero zero zero zero zero zero zero zero zero zero zero one" },
+ { "0.0000000000001", "zero point zero zero zero zero zero zero zero zero zero zero zero zero one" },
+ { "0.00000000000001", "zero point zero zero zero zero zero zero zero zero zero zero zero zero zero one" },
+ { "0.000000000000001", "zero point zero zero zero zero zero zero zero zero zero zero zero zero zero zero one" },
+ { "10,000,000.001", "ten million point zero zero one" },
+ { "10,000,000.0001", "ten million point zero zero zero one" },
+ { "10,000,000.00001", "ten million point zero zero zero zero one" },
+ { "10,000,000.000001", "ten million point zero zero zero zero zero one" },
+ { "10,000,000.0000001", "ten million point zero zero zero zero zero zero one" },
+// { "10,000,000.00000001", "ten million point zero zero zero zero zero zero zero one" },
+// { "10,000,000.000000002", "ten million point zero zero zero zero zero zero zero zero two" },
+ { "10,000,000", "ten million" },
+// { "1,234,567,890.0987654", "one billion, two hundred and thirty-four million, five hundred and sixty-seven thousand, eight hundred and ninety point zero nine eight seven six five four" },
+// { "123,456,789.9876543", "one hundred and twenty-three million, four hundred and fifty-six thousand, seven hundred and eighty-nine point nine eight seven six five four three" },
+// { "12,345,678.87654321", "twelve million, three hundred and forty-five thousand, six hundred and seventy-eight point eight seven six five four three two one" },
+ { "1,234,567.7654321", "one million two hundred thirty-four thousand five hundred sixty-seven point seven six five four three two one" },
+ { "123,456.654321", "one hundred twenty-three thousand four hundred fifty-six point six five four three two one" },
+ { "12,345.54321", "twelve thousand three hundred forty-five point five four three two one" },
+ { "1,234.4321", "one thousand two hundred thirty-four point four three two one" },
+ { "123.321", "one hundred twenty-three point three two one" },
+ { "0.0000000011754944", "zero point zero zero zero zero zero zero zero zero one one seven five four nine four four" },
+ { "0.000001175494351", "zero point zero zero zero zero zero one one seven five four nine four three five one" },
+ { NULL, NULL }
+ };
+
+ doTest(formatter, testDataDefault, TRUE);
+
+ delete formatter;
+ }
+}
+
+void
+IntlTestRBNF::TestLocalizations(void)
+{
+ int i;
+ UnicodeString rules("%main:0:no;1:some;100:a lot;1000:tons;\n"
+ "%other:0:nada;1:yah, some;100:plenty;1000:more'n you'll ever need");
+
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError perror;
+ RuleBasedNumberFormat formatter(rules, perror, status);
+ if (U_FAILURE(status)) {
+ errcheckln(status, "FAIL: could not construct formatter - %s", u_errorName(status));
+ } else {
+ {
+ static const char* const testData[][2] = {
+ { "0", "nada" },
+ { "5", "yah, some" },
+ { "423", "plenty" },
+ { "12345", "more'n you'll ever need" },
+ { NULL, NULL }
+ };
+ doTest(&formatter, testData, FALSE);
+ }
+
+ {
+ UnicodeString loc("<<%main, %other>,<en, Main, Other>,<fr, leMain, leOther>,<de, 'das Main', 'etwas anderes'>>");
+ static const char* const testData[][2] = {
+ { "0", "no" },
+ { "5", "some" },
+ { "423", "a lot" },
+ { "12345", "tons" },
+ { NULL, NULL }
+ };
+ RuleBasedNumberFormat formatter0(rules, loc, perror, status);
+ if (U_FAILURE(status)) {
+ errln("failed to build second formatter");
+ } else {
+ doTest(&formatter0, testData, FALSE);
+
+ {
+ // exercise localization info
+ Locale locale0("en__VALLEY@turkey=gobblegobble");
+ Locale locale1("de_DE_FOO");
+ Locale locale2("ja_JP");
+ UnicodeString name = formatter0.getRuleSetName(0);
+ if ( formatter0.getRuleSetDisplayName(0, locale0) == "Main"
+ && formatter0.getRuleSetDisplayName(0, locale1) == "das Main"
+ && formatter0.getRuleSetDisplayName(0, locale2) == "%main"
+ && formatter0.getRuleSetDisplayName(name, locale0) == "Main"
+ && formatter0.getRuleSetDisplayName(name, locale1) == "das Main"
+ && formatter0.getRuleSetDisplayName(name, locale2) == "%main"){
+ logln("getRuleSetDisplayName tested");
+ }else {
+ errln("failed to getRuleSetDisplayName");
+ }
+ }
+
+ for (i = 0; i < formatter0.getNumberOfRuleSetDisplayNameLocales(); ++i) {
+ Locale locale = formatter0.getRuleSetDisplayNameLocale(i, status);
+ if (U_SUCCESS(status)) {
+ for (int j = 0; j < formatter0.getNumberOfRuleSetNames(); ++j) {
+ UnicodeString name = formatter0.getRuleSetName(j);
+ UnicodeString lname = formatter0.getRuleSetDisplayName(j, locale);
+ UnicodeString msg = locale.getName();
+ msg.append(": ");
+ msg.append(name);
+ msg.append(" = ");
+ msg.append(lname);
+ logln(msg);
+ }
+ }
+ }
+ }
+ }
+
+ {
+ static const char* goodLocs[] = {
+ "", // zero-length ok, same as providing no localization data
+ "<<>>", // no public rule sets ok
+ "<<%main>>", // no localizations ok
+ "<<%main,>,<en, Main,>>", // comma before close angle ok
+ "<<%main>,<en, ',<>\" '>>", // quotes everything until next quote
+ "<<%main>,<'en', \"it's ok\">>", // double quotes work too
+ " \n <\n <\n %main\n >\n , \t <\t en\t , \tfoo \t\t > \n\n > \n ", // Pattern_White_Space ok
+ };
+ int32_t goodLocsLen = UPRV_LENGTHOF(goodLocs);
+
+ static const char* badLocs[] = {
+ " ", // non-zero length
+ "<>", // empty array
+ "<", // unclosed outer array
+ "<<", // unclosed inner array
+ "<<,>>", // unexpected comma
+ "<<''>>", // empty string
+ " x<<%main>>", // first non space char not open angle bracket
+ "<%main>", // missing inner array
+ "<<%main %other>>", // elements missing separating commma (spaces must be quoted)
+ "<<%main><en, Main>>", // arrays missing separating comma
+ "<<%main>,<en, main, foo>>", // too many elements in locale data
+ "<<%main>,<en>>", // too few elements in locale data
+ "<<<%main>>>", // unexpected open angle
+ "<<%main<>>>", // unexpected open angle
+ "<<%main, %other>,<en,,>>", // implicit empty strings
+ "<<%main>,<en,''>>", // empty string
+ "<<%main>, < en, '>>", // unterminated quote
+ "<<%main>, < en, \"<>>", // unterminated quote
+ "<<%main\">>", // quote in string
+ "<<%main'>>", // quote in string
+ "<<%main<>>", // open angle in string
+ "<<%main>> x", // extra non-space text at end
+
+ };
+ int32_t badLocsLen = UPRV_LENGTHOF(badLocs);
+
+ for (i = 0; i < goodLocsLen; ++i) {
+ logln("[%d] '%s'", i, goodLocs[i]);
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString loc(goodLocs[i]);
+ RuleBasedNumberFormat fmt(rules, loc, perror, status);
+ if (U_FAILURE(status)) {
+ errln("Failed parse of good localization string: '%s'", goodLocs[i]);
+ }
+ }
+
+ for (i = 0; i < badLocsLen; ++i) {
+ logln("[%d] '%s'", i, badLocs[i]);
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString loc(badLocs[i]);
+ RuleBasedNumberFormat fmt(rules, loc, perror, status);
+ if (U_SUCCESS(status)) {
+ errln("Successful parse of bad localization string: '%s'", badLocs[i]);
+ }
+ }
+ }
+ }
+}
+
+void
+IntlTestRBNF::TestAllLocales()
+{
+ const char* names[] = {
+ " (spellout) ",
+ " (ordinal) "
+ // " (duration) " // This is English only, and it's not really supported in CLDR anymore.
+ };
+ double numbers[] = {45.678, 1, 2, 10, 11, 100, 110, 200, 1000, 1111, -1111};
+
+ int32_t count = 0;
+ const Locale* locales = Locale::getAvailableLocales(count);
+ for (int i = 0; i < count; ++i) {
+ const Locale* loc = &locales[i];
+
+ for (int j = 0; j < 2; ++j) {
+ UErrorCode status = U_ZERO_ERROR;
+ RuleBasedNumberFormat* f = new RuleBasedNumberFormat((URBNFRuleSetTag)j, *loc, status);
+
+ if (status == U_USING_DEFAULT_WARNING || status == U_USING_FALLBACK_WARNING) {
+ // Skip it.
+ delete f;
+ break;
+ }
+ if (U_FAILURE(status)) {
+ errln(UnicodeString(loc->getName()) + names[j]
+ + "ERROR could not instantiate -> " + u_errorName(status));
+ continue;
+ }
+#if !UCONFIG_NO_COLLATION
+ for (unsigned int numidx = 0; numidx < UPRV_LENGTHOF(numbers); numidx++) {
+ double n = numbers[numidx];
+ UnicodeString str;
+ f->format(n, str);
+
+ if (verbose) {
+ logln(UnicodeString(loc->getName()) + names[j]
+ + "success: " + n + " -> " + str);
+ }
+
+ // We do not validate the result in this test case,
+ // because there are cases which do not round trip by design.
+ Formattable num;
+
+ // regular parse
+ status = U_ZERO_ERROR;
+ f->setLenient(FALSE);
+ f->parse(str, num, status);
+ if (U_FAILURE(status)) {
+ errln(UnicodeString(loc->getName()) + names[j]
+ + "ERROR could not parse '" + str + "' -> " + u_errorName(status));
+ }
+ // We only check the spellout. The behavior is undefined for numbers < 1 and fractional numbers.
+ if (j == 0) {
+ if (num.getType() == Formattable::kLong && num.getLong() != n) {
+ errln(UnicodeString(loc->getName()) + names[j]
+ + UnicodeString("ERROR could not roundtrip ") + n
+ + UnicodeString(" -> ") + str + UnicodeString(" -> ") + num.getLong());
+ }
+ else if (num.getType() == Formattable::kDouble && (int64_t)(num.getDouble() * 1000) != (int64_t)(n*1000)) {
+ // The epsilon difference is too high.
+ errln(UnicodeString(loc->getName()) + names[j]
+ + UnicodeString("ERROR could not roundtrip ") + n
+ + UnicodeString(" -> ") + str + UnicodeString(" -> ") + num.getDouble());
+ }
+ }
+ if (!quick && !logKnownIssue("9503") ) {
+ // lenient parse
+ status = U_ZERO_ERROR;
+ f->setLenient(TRUE);
+ f->parse(str, num, status);
+ if (U_FAILURE(status)) {
+ errln(UnicodeString(loc->getName()) + names[j]
+ + "ERROR could not parse(lenient) '" + str + "' -> " + u_errorName(status));
+ }
+ // We only check the spellout. The behavior is undefined for numbers < 1 and fractional numbers.
+ if (j == 0) {
+ if (num.getType() == Formattable::kLong && num.getLong() != n) {
+ errln(UnicodeString(loc->getName()) + names[j]
+ + UnicodeString("ERROR could not roundtrip ") + n
+ + UnicodeString(" -> ") + str + UnicodeString(" -> ") + num.getLong());
+ }
+ else if (num.getType() == Formattable::kDouble && (int64_t)(num.getDouble() * 1000) != (int64_t)(n*1000)) {
+ // The epsilon difference is too high.
+ errln(UnicodeString(loc->getName()) + names[j]
+ + UnicodeString("ERROR could not roundtrip ") + n
+ + UnicodeString(" -> ") + str + UnicodeString(" -> ") + num.getDouble());
+ }
+ }
+ }
+ }
+#endif
+ delete f;
+ }
+ }
+}
+
+void
+IntlTestRBNF::TestMultiplierSubstitution(void) {
+ UnicodeString rules("=#,##0=;1,000,000: <##0.###< million;");
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError parse_error;
+ RuleBasedNumberFormat *rbnf =
+ new RuleBasedNumberFormat(rules, Locale::getUS(), parse_error, status);
+ if (U_SUCCESS(status)) {
+ UnicodeString res;
+ FieldPosition pos;
+ double n = 1234000.0;
+ rbnf->format(n, res, pos);
+ delete rbnf;
+
+ UnicodeString expected(UNICODE_STRING_SIMPLE("1.234 million"));
+ if (expected != res) {
+ UnicodeString msg = "Expected: ";
+ msg.append(expected);
+ msg.append(" but got ");
+ msg.append(res);
+ errln(msg);
+ }
+ }
+}
+
+void
+IntlTestRBNF::TestSetDecimalFormatSymbols() {
+ UErrorCode status = U_ZERO_ERROR;
+
+ RuleBasedNumberFormat rbnf(URBNF_ORDINAL, Locale::getEnglish(), status);
+ if (U_FAILURE(status)) {
+ dataerrln("Unable to create RuleBasedNumberFormat - " + UnicodeString(u_errorName(status)));
+ return;
+ }
+
+ DecimalFormatSymbols dfs(Locale::getEnglish(), status);
+ if (U_FAILURE(status)) {
+ errln("Unable to create DecimalFormatSymbols - " + UnicodeString(u_errorName(status)));
+ return;
+ }
+
+ UnicodeString expected[] = {
+ UnicodeString("1,001st"),
+ UnicodeString("1&001st")
+ };
+
+ double number = 1001;
+
+ UnicodeString result;
+
+ rbnf.format(number, result);
+ if (result != expected[0]) {
+ errln("Format Error - Got: " + result + " Expected: " + expected[0]);
+ }
+
+ result.remove();
+
+ /* Set new symbol for testing */
+ dfs.setSymbol(DecimalFormatSymbols::kGroupingSeparatorSymbol, UnicodeString("&"), TRUE);
+ rbnf.setDecimalFormatSymbols(dfs);
+
+ rbnf.format(number, result);
+ if (result != expected[1]) {
+ errln("Format Error - Got: " + result + " Expected: " + expected[1]);
+ }
+}
+
+void IntlTestRBNF::TestPluralRules() {
+ UErrorCode status = U_ZERO_ERROR;
+ UnicodeString enRules("%digits-ordinal:-x: ->>;0: =#,##0=$(ordinal,one{st}two{nd}few{rd}other{th})$;");
+ UParseError parseError;
+ RuleBasedNumberFormat enFormatter(enRules, Locale::getEnglish(), parseError, status);
+ if (U_FAILURE(status)) {
+ dataerrln("Unable to create RuleBasedNumberFormat - " + UnicodeString(u_errorName(status)));
+ return;
+ }
+ const char* const enTestData[][2] = {
+ { "1", "1st" },
+ { "2", "2nd" },
+ { "3", "3rd" },
+ { "4", "4th" },
+ { "11", "11th" },
+ { "12", "12th" },
+ { "13", "13th" },
+ { "14", "14th" },
+ { "21", "21st" },
+ { "22", "22nd" },
+ { "23", "23rd" },
+ { "24", "24th" },
+ { NULL, NULL }
+ };
+
+ doTest(&enFormatter, enTestData, TRUE);
+
+ // This is trying to model the feminine form, but don't worry about the details too much.
+ // We're trying to test the plural rules.
+ UnicodeString ruRules("%spellout-numbering:"
+ "-x: minus >>;"
+ "x.x: << point >>;"
+ "0: zero;"
+ "1: one;"
+ "2: two;"
+ "3: three;"
+ "4: four;"
+ "5: five;"
+ "6: six;"
+ "7: seven;"
+ "8: eight;"
+ "9: nine;"
+ "10: ten;"
+ "11: eleven;"
+ "12: twelve;"
+ "13: thirteen;"
+ "14: fourteen;"
+ "15: fifteen;"
+ "16: sixteen;"
+ "17: seventeen;"
+ "18: eighteen;"
+ "19: nineteen;"
+ "20: twenty[->>];"
+ "30: thirty[->>];"
+ "40: forty[->>];"
+ "50: fifty[->>];"
+ "60: sixty[->>];"
+ "70: seventy[->>];"
+ "80: eighty[->>];"
+ "90: ninety[->>];"
+ "100: hundred[ >>];"
+ "200: << hundred[ >>];"
+ "300: << hundreds[ >>];"
+ "500: << hundredss[ >>];"
+ "1000: << $(cardinal,one{thousand}few{thousands}other{thousandss})$[ >>];"
+ "1000000: << $(cardinal,one{million}few{millions}other{millionss})$[ >>];");
+ RuleBasedNumberFormat ruFormatter(ruRules, Locale("ru"), parseError, status);
+ const char* const ruTestData[][2] = {
+ { "1", "one" },
+ { "100", "hundred" },
+ { "125", "hundred twenty-five" },
+ { "399", "three hundreds ninety-nine" },
+ { "1,000", "one thousand" },
+ { "1,001", "one thousand one" },
+ { "2,000", "two thousands" },
+ { "2,001", "two thousands one" },
+ { "2,002", "two thousands two" },
+ { "3,333", "three thousands three hundreds thirty-three" },
+ { "5,000", "five thousandss" },
+ { "11,000", "eleven thousandss" },
+ { "21,000", "twenty-one thousand" },
+ { "22,000", "twenty-two thousands" },
+ { "25,001", "twenty-five thousandss one" },
+ { NULL, NULL }
+ };
+
+ if (U_FAILURE(status)) {
+ errln("Unable to create RuleBasedNumberFormat - " + UnicodeString(u_errorName(status)));
+ return;
+ }
+ doTest(&ruFormatter, ruTestData, TRUE);
+
+ // Make sure there are no divide by 0 errors.
+ UnicodeString result;
+ RuleBasedNumberFormat(ruRules, Locale("ru"), parseError, status).format(21000, result);
+ if (result.compare(UNICODE_STRING_SIMPLE("twenty-one thousand")) != 0) {
+ errln("Got " + result + " for 21000");
+ }
+
+}
+
+void IntlTestRBNF::TestInfinityNaN() {
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError parseError;
+ UnicodeString enRules("%default:"
+ "-x: minus >>;"
+ "Inf: infinite;"
+ "NaN: not a number;"
+ "0: =#,##0=;");
+ RuleBasedNumberFormat enFormatter(enRules, Locale::getEnglish(), parseError, status);
+ const char * const enTestData[][2] = {
+ {"1", "1"},
+ {"\\u221E", "infinite"},
+ {"-\\u221E", "minus infinite"},
+ {"NaN", "not a number"},
+ { NULL, NULL }
+ };
+ if (U_FAILURE(status)) {
+ dataerrln("Unable to create RuleBasedNumberFormat - " + UnicodeString(u_errorName(status)));
+ return;
+ }
+
+ doTest(&enFormatter, enTestData, true);
+
+ // Test the default behavior when the rules are undefined.
+ UnicodeString enRules2("%default:"
+ "-x: ->>;"
+ "0: =#,##0=;");
+ RuleBasedNumberFormat enFormatter2(enRules2, Locale::getEnglish(), parseError, status);
+ if (U_FAILURE(status)) {
+ errln("Unable to create RuleBasedNumberFormat - " + UnicodeString(u_errorName(status)));
+ return;
+ }
+ const char * const enDefaultTestData[][2] = {
+ {"1", "1"},
+ {"\\u221E", "\\u221E"},
+ {"-\\u221E", "-\\u221E"},
+ {"NaN", "NaN"},
+ { NULL, NULL }
+ };
+
+ doTest(&enFormatter2, enDefaultTestData, true);
+}
+
+void IntlTestRBNF::TestVariableDecimalPoint() {
+ UErrorCode status = U_ZERO_ERROR;
+ UParseError parseError;
+ UnicodeString enRules("%spellout-numbering:"
+ "-x: minus >>;"
+ "x.x: << point >>;"
+ "x,x: << comma >>;"
+ "0.x: xpoint >>;"
+ "0,x: xcomma >>;"
+ "0: zero;"
+ "1: one;"
+ "2: two;"
+ "3: three;"
+ "4: four;"
+ "5: five;"
+ "6: six;"
+ "7: seven;"
+ "8: eight;"
+ "9: nine;");
+ RuleBasedNumberFormat enFormatter(enRules, Locale::getEnglish(), parseError, status);
+ const char * const enTestPointData[][2] = {
+ {"1.1", "one point one"},
+ {"1.23", "one point two three"},
+ {"0.4", "xpoint four"},
+ { NULL, NULL }
+ };
+ if (U_FAILURE(status)) {
+ dataerrln("Unable to create RuleBasedNumberFormat - " + UnicodeString(u_errorName(status)));
+ return;
+ }
+ doTest(&enFormatter, enTestPointData, true);
+
+ DecimalFormatSymbols decimalFormatSymbols(Locale::getEnglish(), status);
+ decimalFormatSymbols.setSymbol(DecimalFormatSymbols::kDecimalSeparatorSymbol, UNICODE_STRING_SIMPLE(","));
+ enFormatter.setDecimalFormatSymbols(decimalFormatSymbols);
+ const char * const enTestCommaData[][2] = {
+ {"1.1", "one comma one"},
+ {"1.23", "one comma two three"},
+ {"0.4", "xcomma four"},
+ { NULL, NULL }
+ };
+ doTest(&enFormatter, enTestCommaData, true);
+}