1 // © 2017 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 <cmath>
9 #include <cstdarg>
10 #include <memory>
11
12 #include "unicode/displayoptions.h"
13 #include "unicode/numberformatter.h"
14 #include "unicode/testlog.h"
15 #include "unicode/unum.h"
16 #include "unicode/utypes.h"
17
18 #include "charstr.h"
19 #include "number_asformat.h"
20 #include "number_microprops.h"
21 #include "number_types.h"
22 #include "number_utils.h"
23 #include "number_utypes.h"
24 #include "numbertest.h"
25
26 using number::impl::UFormattedNumberData;
27
28 // Horrible workaround for the lack of a status code in the constructor...
29 // (Also affects numbertest_range.cpp)
30 UErrorCode globalNumberFormatterApiTestStatus = U_ZERO_ERROR;
31
NumberFormatterApiTest()32 NumberFormatterApiTest::NumberFormatterApiTest()
33 : NumberFormatterApiTest(globalNumberFormatterApiTestStatus) {
34 }
35
NumberFormatterApiTest(UErrorCode & status)36 NumberFormatterApiTest::NumberFormatterApiTest(UErrorCode& status)
37 : USD(u"USD", status),
38 GBP(u"GBP", status),
39 CZK(u"CZK", status),
40 CAD(u"CAD", status),
41 ESP(u"ESP", status),
42 PTE(u"PTE", status),
43 RON(u"RON", status),
44 TWD(u"TWD", status),
45 TRY(u"TRY", status),
46 CNY(u"CNY", status),
47 FRENCH_SYMBOLS(Locale::getFrench(), status),
48 SWISS_SYMBOLS(Locale("de-CH"), status),
49 MYANMAR_SYMBOLS(Locale("my"), status) {
50
51 // Check for error on the first MeasureUnit in case there is no data
52 LocalPointer<MeasureUnit> unit(MeasureUnit::createMeter(status));
53 if (U_FAILURE(status)) {
54 dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status));
55 return;
56 }
57 METER = *unit;
58
59 METER_PER_SECOND = *LocalPointer<MeasureUnit>(MeasureUnit::createMeterPerSecond(status));
60 DAY = *LocalPointer<MeasureUnit>(MeasureUnit::createDay(status));
61 SQUARE_METER = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMeter(status));
62 FAHRENHEIT = *LocalPointer<MeasureUnit>(MeasureUnit::createFahrenheit(status));
63 SECOND = *LocalPointer<MeasureUnit>(MeasureUnit::createSecond(status));
64 POUND = *LocalPointer<MeasureUnit>(MeasureUnit::createPound(status));
65 POUND_FORCE = *LocalPointer<MeasureUnit>(MeasureUnit::createPoundForce(status));
66 SQUARE_MILE = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareMile(status));
67 SQUARE_INCH = *LocalPointer<MeasureUnit>(MeasureUnit::createSquareInch(status));
68 JOULE = *LocalPointer<MeasureUnit>(MeasureUnit::createJoule(status));
69 FURLONG = *LocalPointer<MeasureUnit>(MeasureUnit::createFurlong(status));
70 KELVIN = *LocalPointer<MeasureUnit>(MeasureUnit::createKelvin(status));
71
72 MATHSANB = *LocalPointer<NumberingSystem>(NumberingSystem::createInstanceByName("mathsanb", status));
73 LATN = *LocalPointer<NumberingSystem>(NumberingSystem::createInstanceByName("latn", status));
74 }
75
runIndexedTest(int32_t index,UBool exec,const char * & name,char *)76 void NumberFormatterApiTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
77 if (exec) {
78 logln("TestSuite NumberFormatterApiTest: ");
79 }
80 TESTCASE_AUTO_BEGIN;
81 TESTCASE_AUTO(notationSimple);
82 TESTCASE_AUTO(notationScientific);
83 TESTCASE_AUTO(notationCompact);
84 TESTCASE_AUTO(unitMeasure);
85 TESTCASE_AUTO(unitCompoundMeasure);
86 TESTCASE_AUTO(unitArbitraryMeasureUnits);
87 TESTCASE_AUTO(unitSkeletons);
88 TESTCASE_AUTO(unitUsage);
89 TESTCASE_AUTO(unitUsageErrorCodes);
90 TESTCASE_AUTO(unitUsageSkeletons);
91 TESTCASE_AUTO(unitCurrency);
92 TESTCASE_AUTO(unitInflections);
93 TESTCASE_AUTO(unitNounClass);
94 TESTCASE_AUTO(unitNotConvertible);
95 TESTCASE_AUTO(unitPercent);
96 TESTCASE_AUTO(unitLocaleTags);
97 if (!quick) {
98 // Slow test: run in exhaustive mode only
99 TESTCASE_AUTO(percentParity);
100 }
101 TESTCASE_AUTO(roundingFraction);
102 TESTCASE_AUTO(roundingFigures);
103 TESTCASE_AUTO(roundingFractionFigures);
104 TESTCASE_AUTO(roundingOther);
105 TESTCASE_AUTO(roundingIncrementRegressionTest);
106 TESTCASE_AUTO(roundingPriorityCoverageTest);
107 TESTCASE_AUTO(grouping);
108 TESTCASE_AUTO(padding);
109 TESTCASE_AUTO(integerWidth);
110 TESTCASE_AUTO(symbols);
111 // TODO: Add this method if currency symbols override support is added.
112 //TESTCASE_AUTO(symbolsOverride);
113 TESTCASE_AUTO(sign);
114 TESTCASE_AUTO(signNearZero);
115 TESTCASE_AUTO(signCoverage);
116 TESTCASE_AUTO(decimal);
117 TESTCASE_AUTO(scale);
118 TESTCASE_AUTO(locale);
119 TESTCASE_AUTO(skeletonUserGuideExamples);
120 TESTCASE_AUTO(formatTypes);
121 TESTCASE_AUTO(fieldPositionLogic);
122 TESTCASE_AUTO(fieldPositionCoverage);
123 TESTCASE_AUTO(toFormat);
124 TESTCASE_AUTO(errors);
125 if (!quick) {
126 // Slow test: run in exhaustive mode only
127 // (somewhat slow to check all permutations of settings)
128 TESTCASE_AUTO(validRanges);
129 }
130 TESTCASE_AUTO(copyMove);
131 TESTCASE_AUTO(localPointerCAPI);
132 TESTCASE_AUTO(toObject);
133 TESTCASE_AUTO(toDecimalNumber);
134 TESTCASE_AUTO(microPropsInternals);
135 TESTCASE_AUTO(formatUnitsAliases);
136 TESTCASE_AUTO(testIssue22378);
137 TESTCASE_AUTO_END;
138 }
139
notationSimple()140 void NumberFormatterApiTest::notationSimple() {
141 assertFormatDescending(
142 u"Basic",
143 u"",
144 u"",
145 NumberFormatter::with(),
146 Locale::getEnglish(),
147 u"87,650",
148 u"8,765",
149 u"876.5",
150 u"87.65",
151 u"8.765",
152 u"0.8765",
153 u"0.08765",
154 u"0.008765",
155 u"0");
156
157 assertFormatDescendingBig(
158 u"Big Simple",
159 u"notation-simple",
160 u"",
161 NumberFormatter::with().notation(Notation::simple()),
162 Locale::getEnglish(),
163 u"87,650,000",
164 u"8,765,000",
165 u"876,500",
166 u"87,650",
167 u"8,765",
168 u"876.5",
169 u"87.65",
170 u"8.765",
171 u"0");
172
173 assertFormatSingle(
174 u"Basic with Negative Sign",
175 u"",
176 u"",
177 NumberFormatter::with(),
178 Locale::getEnglish(),
179 -9876543.21,
180 u"-9,876,543.21");
181 }
182
183
notationScientific()184 void NumberFormatterApiTest::notationScientific() {
185 assertFormatDescending(
186 u"Scientific",
187 u"scientific",
188 u"E0",
189 NumberFormatter::with().notation(Notation::scientific()),
190 Locale::getEnglish(),
191 u"8.765E4",
192 u"8.765E3",
193 u"8.765E2",
194 u"8.765E1",
195 u"8.765E0",
196 u"8.765E-1",
197 u"8.765E-2",
198 u"8.765E-3",
199 u"0E0");
200
201 assertFormatDescending(
202 u"Engineering",
203 u"engineering",
204 u"EE0",
205 NumberFormatter::with().notation(Notation::engineering()),
206 Locale::getEnglish(),
207 u"87.65E3",
208 u"8.765E3",
209 u"876.5E0",
210 u"87.65E0",
211 u"8.765E0",
212 u"876.5E-3",
213 u"87.65E-3",
214 u"8.765E-3",
215 u"0E0");
216
217 assertFormatDescending(
218 u"Scientific sign always shown",
219 u"scientific/sign-always",
220 u"E+!0",
221 NumberFormatter::with().notation(
222 Notation::scientific().withExponentSignDisplay(UNumberSignDisplay::UNUM_SIGN_ALWAYS)),
223 Locale::getEnglish(),
224 u"8.765E+4",
225 u"8.765E+3",
226 u"8.765E+2",
227 u"8.765E+1",
228 u"8.765E+0",
229 u"8.765E-1",
230 u"8.765E-2",
231 u"8.765E-3",
232 u"0E+0");
233
234 assertFormatDescending(
235 u"Scientific min exponent digits",
236 u"scientific/*ee",
237 u"E00",
238 NumberFormatter::with().notation(Notation::scientific().withMinExponentDigits(2)),
239 Locale::getEnglish(),
240 u"8.765E04",
241 u"8.765E03",
242 u"8.765E02",
243 u"8.765E01",
244 u"8.765E00",
245 u"8.765E-01",
246 u"8.765E-02",
247 u"8.765E-03",
248 u"0E00");
249
250 assertFormatSingle(
251 u"Scientific Negative",
252 u"scientific",
253 u"E0",
254 NumberFormatter::with().notation(Notation::scientific()),
255 Locale::getEnglish(),
256 -1000000,
257 u"-1E6");
258
259 assertFormatSingle(
260 u"Scientific Infinity",
261 u"scientific",
262 u"E0",
263 NumberFormatter::with().notation(Notation::scientific()),
264 Locale::getEnglish(),
265 -uprv_getInfinity(),
266 u"-∞");
267
268 assertFormatSingle(
269 u"Scientific NaN",
270 u"scientific",
271 u"E0",
272 NumberFormatter::with().notation(Notation::scientific()),
273 Locale::getEnglish(),
274 uprv_getNaN(),
275 u"NaN");
276 }
277
notationCompact()278 void NumberFormatterApiTest::notationCompact() {
279 assertFormatDescending(
280 u"Compact Short",
281 u"compact-short",
282 u"K",
283 NumberFormatter::with().notation(Notation::compactShort()),
284 Locale::getEnglish(),
285 u"88K",
286 u"8.8K",
287 u"876",
288 u"88",
289 u"8.8",
290 u"0.88",
291 u"0.088",
292 u"0.0088",
293 u"0");
294
295 assertFormatDescending(
296 u"Compact Long",
297 u"compact-long",
298 u"KK",
299 NumberFormatter::with().notation(Notation::compactLong()),
300 Locale::getEnglish(),
301 u"88 thousand",
302 u"8.8 thousand",
303 u"876",
304 u"88",
305 u"8.8",
306 u"0.88",
307 u"0.088",
308 u"0.0088",
309 u"0");
310
311 assertFormatDescending(
312 u"Compact Short Currency",
313 u"compact-short currency/USD",
314 u"K currency/USD",
315 NumberFormatter::with().notation(Notation::compactShort()).unit(USD),
316 Locale::getEnglish(),
317 u"$88K",
318 u"$8.8K",
319 u"$876",
320 u"$88",
321 u"$8.8",
322 u"$0.88",
323 u"$0.088",
324 u"$0.0088",
325 u"$0");
326
327 assertFormatDescending(
328 u"Compact Short with ISO Currency",
329 u"compact-short currency/USD unit-width-iso-code",
330 u"K currency/USD unit-width-iso-code",
331 NumberFormatter::with().notation(Notation::compactShort())
332 .unit(USD)
333 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE),
334 Locale::getEnglish(),
335 u"USD 88K",
336 u"USD 8.8K",
337 u"USD 876",
338 u"USD 88",
339 u"USD 8.8",
340 u"USD 0.88",
341 u"USD 0.088",
342 u"USD 0.0088",
343 u"USD 0");
344
345 assertFormatDescending(
346 u"Compact Short with Long Name Currency",
347 u"compact-short currency/USD unit-width-full-name",
348 u"K currency/USD unit-width-full-name",
349 NumberFormatter::with().notation(Notation::compactShort())
350 .unit(USD)
351 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
352 Locale::getEnglish(),
353 u"88K US dollars",
354 u"8.8K US dollars",
355 u"876 US dollars",
356 u"88 US dollars",
357 u"8.8 US dollars",
358 u"0.88 US dollars",
359 u"0.088 US dollars",
360 u"0.0088 US dollars",
361 u"0 US dollars");
362
363 // Note: Most locales don't have compact long currency, so this currently falls back to short.
364 // This test case should be fixed when proper compact long currency patterns are added.
365 assertFormatDescending(
366 u"Compact Long Currency",
367 u"compact-long currency/USD",
368 u"KK currency/USD",
369 NumberFormatter::with().notation(Notation::compactLong()).unit(USD),
370 Locale::getEnglish(),
371 u"$88K", // should be something like "$88 thousand"
372 u"$8.8K",
373 u"$876",
374 u"$88",
375 u"$8.8",
376 u"$0.88",
377 u"$0.088",
378 u"$0.0088",
379 u"$0");
380
381 // Note: Most locales don't have compact long currency, so this currently falls back to short.
382 // This test case should be fixed when proper compact long currency patterns are added.
383 assertFormatDescending(
384 u"Compact Long with ISO Currency",
385 u"compact-long currency/USD unit-width-iso-code",
386 u"KK currency/USD unit-width-iso-code",
387 NumberFormatter::with().notation(Notation::compactLong())
388 .unit(USD)
389 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE),
390 Locale::getEnglish(),
391 u"USD 88K", // should be something like "USD 88 thousand"
392 u"USD 8.8K",
393 u"USD 876",
394 u"USD 88",
395 u"USD 8.8",
396 u"USD 0.88",
397 u"USD 0.088",
398 u"USD 0.0088",
399 u"USD 0");
400
401 // TODO: This behavior could be improved and should be revisited.
402 assertFormatDescending(
403 u"Compact Long with Long Name Currency",
404 u"compact-long currency/USD unit-width-full-name",
405 u"KK currency/USD unit-width-full-name",
406 NumberFormatter::with().notation(Notation::compactLong())
407 .unit(USD)
408 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
409 Locale::getEnglish(),
410 u"88 thousand US dollars",
411 u"8.8 thousand US dollars",
412 u"876 US dollars",
413 u"88 US dollars",
414 u"8.8 US dollars",
415 u"0.88 US dollars",
416 u"0.088 US dollars",
417 u"0.0088 US dollars",
418 u"0 US dollars");
419
420 assertFormatSingle(
421 u"Compact Plural One",
422 u"compact-long",
423 u"KK",
424 NumberFormatter::with().notation(Notation::compactLong()),
425 Locale::createFromName("es"),
426 1000000,
427 u"1 millón");
428
429 assertFormatSingle(
430 u"Compact Plural Other",
431 u"compact-long",
432 u"KK",
433 NumberFormatter::with().notation(Notation::compactLong()),
434 Locale::createFromName("es"),
435 2000000,
436 u"2 millones");
437
438 assertFormatSingle(
439 u"Compact with Negative Sign",
440 u"compact-short",
441 u"K",
442 NumberFormatter::with().notation(Notation::compactShort()),
443 Locale::getEnglish(),
444 -9876543.21,
445 u"-9.9M");
446
447 assertFormatSingle(
448 u"Compact Rounding",
449 u"compact-short",
450 u"K",
451 NumberFormatter::with().notation(Notation::compactShort()),
452 Locale::getEnglish(),
453 990000,
454 u"990K");
455
456 assertFormatSingle(
457 u"Compact Rounding",
458 u"compact-short",
459 u"K",
460 NumberFormatter::with().notation(Notation::compactShort()),
461 Locale::getEnglish(),
462 999000,
463 u"999K");
464
465 assertFormatSingle(
466 u"Compact Rounding",
467 u"compact-short",
468 u"K",
469 NumberFormatter::with().notation(Notation::compactShort()),
470 Locale::getEnglish(),
471 999900,
472 u"1M");
473
474 assertFormatSingle(
475 u"Compact Rounding",
476 u"compact-short",
477 u"K",
478 NumberFormatter::with().notation(Notation::compactShort()),
479 Locale::getEnglish(),
480 9900000,
481 u"9.9M");
482
483 assertFormatSingle(
484 u"Compact Rounding",
485 u"compact-short",
486 u"K",
487 NumberFormatter::with().notation(Notation::compactShort()),
488 Locale::getEnglish(),
489 9990000,
490 u"10M");
491
492 assertFormatSingle(
493 u"Compact in zh-Hant-HK",
494 u"compact-short",
495 u"K",
496 NumberFormatter::with().notation(Notation::compactShort()),
497 Locale("zh-Hant-HK"),
498 1e7,
499 u"10M");
500
501 assertFormatSingle(
502 u"Compact in zh-Hant",
503 u"compact-short",
504 u"K",
505 NumberFormatter::with().notation(Notation::compactShort()),
506 Locale("zh-Hant"),
507 1e7,
508 u"1000\u842C");
509
510 assertFormatSingle(
511 u"Compact with plural form =1 (ICU-21258)",
512 u"compact-long",
513 u"KK",
514 NumberFormatter::with().notation(Notation::compactLong()),
515 Locale("fr-FR"),
516 1e3,
517 u"mille");
518
519 assertFormatSingle(
520 u"Compact Infinity",
521 u"compact-short",
522 u"K",
523 NumberFormatter::with().notation(Notation::compactShort()),
524 Locale::getEnglish(),
525 -uprv_getInfinity(),
526 u"-∞");
527
528 assertFormatSingle(
529 u"Compact NaN",
530 u"compact-short",
531 u"K",
532 NumberFormatter::with().notation(Notation::compactShort()),
533 Locale::getEnglish(),
534 uprv_getNaN(),
535 u"NaN");
536
537 // NOTE: There is no API for compact custom data in C++
538 // and thus no "Compact Somali No Figure" test
539 }
540
unitMeasure()541 void NumberFormatterApiTest::unitMeasure() {
542 IcuTestErrorCode status(*this, "unitMeasure()");
543
544 assertFormatDescending(
545 u"Meters Short and unit() method",
546 u"measure-unit/length-meter",
547 u"unit/meter",
548 NumberFormatter::with().unit(MeasureUnit::getMeter()),
549 Locale::getEnglish(),
550 u"87,650 m",
551 u"8,765 m",
552 u"876.5 m",
553 u"87.65 m",
554 u"8.765 m",
555 u"0.8765 m",
556 u"0.08765 m",
557 u"0.008765 m",
558 u"0 m");
559
560 assertFormatDescending(
561 u"Meters Long and adoptUnit() method",
562 u"measure-unit/length-meter unit-width-full-name",
563 u"unit/meter unit-width-full-name",
564 NumberFormatter::with().adoptUnit(new MeasureUnit(METER))
565 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
566 Locale::getEnglish(),
567 u"87,650 meters",
568 u"8,765 meters",
569 u"876.5 meters",
570 u"87.65 meters",
571 u"8.765 meters",
572 u"0.8765 meters",
573 u"0.08765 meters",
574 u"0.008765 meters",
575 u"0 meters");
576
577 assertFormatDescending(
578 u"Compact Meters Long",
579 u"compact-long measure-unit/length-meter unit-width-full-name",
580 u"KK unit/meter unit-width-full-name",
581 NumberFormatter::with().notation(Notation::compactLong())
582 .unit(METER)
583 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
584 Locale::getEnglish(),
585 u"88 thousand meters",
586 u"8.8 thousand meters",
587 u"876 meters",
588 u"88 meters",
589 u"8.8 meters",
590 u"0.88 meters",
591 u"0.088 meters",
592 u"0.0088 meters",
593 u"0 meters");
594
595 assertFormatDescending(
596 u"Hectometers",
597 u"unit/hectometer",
598 u"unit/hectometer",
599 NumberFormatter::with().unit(MeasureUnit::forIdentifier("hectometer", status)),
600 Locale::getEnglish(),
601 u"87,650 hm",
602 u"8,765 hm",
603 u"876.5 hm",
604 u"87.65 hm",
605 u"8.765 hm",
606 u"0.8765 hm",
607 u"0.08765 hm",
608 u"0.008765 hm",
609 u"0 hm");
610
611 // TODO: Implement Measure in C++
612 // assertFormatSingleMeasure(
613 // u"Meters with Measure Input",
614 // NumberFormatter::with().unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
615 // Locale::getEnglish(),
616 // new Measure(5.43, new MeasureUnit(METER)),
617 // u"5.43 meters");
618
619 // TODO: Implement Measure in C++
620 // assertFormatSingleMeasure(
621 // u"Measure format method takes precedence over fluent chain",
622 // NumberFormatter::with().unit(METER),
623 // Locale::getEnglish(),
624 // new Measure(5.43, USD),
625 // u"$5.43");
626
627 assertFormatSingle(
628 u"Meters with Negative Sign",
629 u"measure-unit/length-meter",
630 u"unit/meter",
631 NumberFormatter::with().unit(METER),
632 Locale::getEnglish(),
633 -9876543.21,
634 u"-9,876,543.21 m");
635
636 // The locale string "सान" appears only in brx.txt:
637 assertFormatSingle(
638 u"Interesting Data Fallback 1",
639 u"measure-unit/duration-day unit-width-full-name",
640 u"unit/day unit-width-full-name",
641 NumberFormatter::with().unit(DAY).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
642 Locale::createFromName("brx"),
643 5.43,
644 u"5.43 सान");
645
646 // Requires following the alias from unitsNarrow to unitsShort:
647 assertFormatSingle(
648 u"Interesting Data Fallback 2",
649 u"measure-unit/duration-day unit-width-narrow",
650 u"unit/day unit-width-narrow",
651 NumberFormatter::with().unit(DAY).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
652 Locale::createFromName("brx"),
653 5.43,
654 u"5.43 d");
655
656 // en_001.txt has a unitsNarrow/area/square-meter table, but table does not contain the OTHER unit,
657 // requiring fallback to the root.
658 assertFormatSingle(
659 u"Interesting Data Fallback 3",
660 u"measure-unit/area-square-meter unit-width-narrow",
661 u"unit/square-meter unit-width-narrow",
662 NumberFormatter::with().unit(SQUARE_METER).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
663 Locale::createFromName("en-GB"),
664 5.43,
665 u"5.43m²");
666
667 // Try accessing a narrow unit directly from root.
668 assertFormatSingle(
669 u"Interesting Data Fallback 4",
670 u"measure-unit/area-square-meter unit-width-narrow",
671 u"unit/square-meter unit-width-narrow",
672 NumberFormatter::with().unit(SQUARE_METER).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_NARROW),
673 Locale::createFromName("root"),
674 5.43,
675 u"5.43 m²");
676
677 // es_US has "{0}°" for unitsNarrow/temperature/FAHRENHEIT.
678 // NOTE: This example is in the documentation.
679 assertFormatSingle(
680 u"Difference between Narrow and Short (Narrow Version)",
681 u"measure-unit/temperature-fahrenheit unit-width-narrow",
682 u"unit/fahrenheit unit-width-narrow",
683 NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_NARROW),
684 Locale("es-US"),
685 5.43,
686 u"5.43°");
687
688 assertFormatSingle(
689 u"Difference between Narrow and Short (Short Version)",
690 u"measure-unit/temperature-fahrenheit unit-width-short",
691 u"unit/fahrenheit unit-width-short",
692 NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_SHORT),
693 Locale("es-US"),
694 5.43,
695 u"5.43 °F");
696
697 assertFormatSingle(
698 u"MeasureUnit form without {0} in CLDR pattern",
699 u"measure-unit/temperature-kelvin unit-width-full-name",
700 u"unit/kelvin unit-width-full-name",
701 NumberFormatter::with().unit(KELVIN).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
702 Locale("es-MX"),
703 1,
704 u"kelvin");
705
706 assertFormatSingle(
707 u"MeasureUnit form without {0} in CLDR pattern and wide base form",
708 u"measure-unit/temperature-kelvin .00000000000000000000 unit-width-full-name",
709 u"unit/kelvin .00000000000000000000 unit-width-full-name",
710 NumberFormatter::with().precision(Precision::fixedFraction(20))
711 .unit(KELVIN)
712 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
713 Locale("es-MX"),
714 1,
715 u"kelvin");
716
717 assertFormatSingle(
718 u"Person unit not in short form",
719 u"measure-unit/duration-year-person unit-width-full-name",
720 u"unit/year-person unit-width-full-name",
721 NumberFormatter::with().unit(MeasureUnit::getYearPerson())
722 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
723 Locale("es-MX"),
724 5,
725 u"5 a\u00F1os");
726
727 assertFormatSingle(
728 u"Hubble Constant - usually expressed in km/s/Mpc",
729 u"unit/kilometer-per-megaparsec-second",
730 u"unit/kilometer-per-megaparsec-second",
731 NumberFormatter::with().unit(MeasureUnit::forIdentifier("kilometer-per-second-per-megaparsec", status)),
732 Locale("en"),
733 74, // Approximate 2019-03-18 measurement
734 u"74 km/Mpc⋅sec");
735
736 assertFormatSingle(
737 u"Mixed unit",
738 u"unit/yard-and-foot-and-inch",
739 u"unit/yard-and-foot-and-inch",
740 NumberFormatter::with()
741 .unit(MeasureUnit::forIdentifier("yard-and-foot-and-inch", status)),
742 Locale("en-US"),
743 3.65,
744 "3 yd, 1 ft, 11.4 in");
745
746 assertFormatSingle(
747 u"Mixed unit, Scientific",
748 u"unit/yard-and-foot-and-inch E0",
749 u"unit/yard-and-foot-and-inch E0",
750 NumberFormatter::with()
751 .unit(MeasureUnit::forIdentifier("yard-and-foot-and-inch", status))
752 .notation(Notation::scientific()),
753 Locale("en-US"),
754 3.65,
755 "3 yd, 1 ft, 1.14E1 in");
756
757 assertFormatSingle(
758 u"Mixed Unit (Narrow Version)",
759 u"unit/tonne-and-kilogram-and-gram unit-width-narrow",
760 u"unit/tonne-and-kilogram-and-gram unit-width-narrow",
761 NumberFormatter::with()
762 .unit(MeasureUnit::forIdentifier("tonne-and-kilogram-and-gram", status))
763 .unitWidth(UNUM_UNIT_WIDTH_NARROW),
764 Locale("en-US"),
765 4.28571,
766 u"4t 285kg 710g");
767
768 assertFormatSingle(
769 u"Mixed Unit (Short Version)",
770 u"unit/tonne-and-kilogram-and-gram unit-width-short",
771 u"unit/tonne-and-kilogram-and-gram unit-width-short",
772 NumberFormatter::with()
773 .unit(MeasureUnit::forIdentifier("tonne-and-kilogram-and-gram", status))
774 .unitWidth(UNUM_UNIT_WIDTH_SHORT),
775 Locale("en-US"),
776 4.28571,
777 u"4 t, 285 kg, 710 g");
778
779 assertFormatSingle(
780 u"Mixed Unit (Full Name Version)",
781 u"unit/tonne-and-kilogram-and-gram unit-width-full-name",
782 u"unit/tonne-and-kilogram-and-gram unit-width-full-name",
783 NumberFormatter::with()
784 .unit(MeasureUnit::forIdentifier("tonne-and-kilogram-and-gram", status))
785 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
786 Locale("en-US"),
787 4.28571,
788 u"4 metric tons, 285 kilograms, 710 grams");
789
790 assertFormatSingle(u"Mixed Unit (Not Sorted) [metric]", //
791 u"unit/gram-and-kilogram unit-width-full-name", //
792 u"unit/gram-and-kilogram unit-width-full-name", //
793 NumberFormatter::with() //
794 .unit(MeasureUnit::forIdentifier("gram-and-kilogram", status)) //
795 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), //
796 Locale("en-US"), //
797 4.28571, //
798 u"285.71 grams, 4 kilograms"); //
799
800 assertFormatSingle(u"Mixed Unit (Not Sorted) [imperial]", //
801 u"unit/inch-and-yard-and-foot unit-width-full-name", //
802 u"unit/inch-and-yard-and-foot unit-width-full-name", //
803 NumberFormatter::with() //
804 .unit(MeasureUnit::forIdentifier("inch-and-yard-and-foot", status)) //
805 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), //
806 Locale("en-US"), //
807 4.28571, //
808 u"10.28556 inches, 4 yards, 0 feet"); //
809
810 assertFormatSingle(u"Mixed Unit (Not Sorted) [imperial full]", //
811 u"unit/inch-and-yard-and-foot unit-width-full-name", //
812 u"unit/inch-and-yard-and-foot unit-width-full-name", //
813 NumberFormatter::with() //
814 .unit(MeasureUnit::forIdentifier("inch-and-yard-and-foot", status)) //
815 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), //
816 Locale("en-US"), //
817 4.38571, //
818 u"1.88556 inches, 4 yards, 1 foot"); //
819
820 assertFormatSingle(u"Mixed Unit (Not Sorted) [imperial full integers]", //
821 u"unit/inch-and-yard-and-foot @# unit-width-full-name", //
822 u"unit/inch-and-yard-and-foot @# unit-width-full-name", //
823 NumberFormatter::with() //
824 .unit(MeasureUnit::forIdentifier("inch-and-yard-and-foot", status)) //
825 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME) //
826 .precision(Precision::maxSignificantDigits(2)), //
827 Locale("en-US"), //
828 4.36112, //
829 u"1 inch, 4 yards, 1 foot"); //
830
831 assertFormatSingle(u"Mixed Unit (Not Sorted) [imperial full] with `And` in the end", //
832 u"unit/inch-and-yard-and-foot unit-width-full-name", //
833 u"unit/inch-and-yard-and-foot unit-width-full-name", //
834 NumberFormatter::with() //
835 .unit(MeasureUnit::forIdentifier("inch-and-yard-and-foot", status)) //
836 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME), //
837 Locale("fr-FR"), //
838 4.38571, //
839 u"1,88556\u00A0pouce, 4\u00A0yards et 1\u00A0pied"); //
840
841 assertFormatSingle(u"Mixed unit, Scientific [Not in Order]", //
842 u"unit/foot-and-inch-and-yard E0", //
843 u"unit/foot-and-inch-and-yard E0", //
844 NumberFormatter::with() //
845 .unit(MeasureUnit::forIdentifier("foot-and-inch-and-yard", status)) //
846 .notation(Notation::scientific()), //
847 Locale("en-US"), //
848 3.65, //
849 "1 ft, 1.14E1 in, 3 yd"); //
850
851 assertFormatSingle(
852 u"Testing \"1 foot 12 inches\"",
853 u"unit/foot-and-inch @### unit-width-full-name",
854 u"unit/foot-and-inch @### unit-width-full-name",
855 NumberFormatter::with()
856 .unit(MeasureUnit::forIdentifier("foot-and-inch", status))
857 .precision(Precision::maxSignificantDigits(4))
858 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
859 Locale("en-US"),
860 1.9999,
861 u"2 feet, 0 inches");
862
863 assertFormatSingle(
864 u"Negative numbers: temperature",
865 u"measure-unit/temperature-celsius",
866 u"unit/celsius",
867 NumberFormatter::with().unit(MeasureUnit::forIdentifier("celsius", status)),
868 Locale("nl-NL"),
869 -6.5,
870 u"-6,5°C");
871
872 assertFormatSingle(
873 u"Negative numbers: time",
874 u"unit/hour-and-minute-and-second",
875 u"unit/hour-and-minute-and-second",
876 NumberFormatter::with().unit(MeasureUnit::forIdentifier("hour-and-minute-and-second", status)),
877 Locale("de-DE"),
878 -1.24,
879 u"-1 Std., 14 Min. und 24 Sek.");
880
881 assertFormatSingle(
882 u"Zero out the unit field",
883 u"",
884 u"",
885 NumberFormatter::with().unit(KELVIN).unit(MeasureUnit()),
886 Locale("en"),
887 100,
888 u"100");
889
890 // TODO: desired behaviour for this "pathological" case?
891 // Since this is pointless, we don't test that its behaviour doesn't change.
892 // As of January 2021, the produced result has a missing sign: 23.5 Kelvin
893 // is "23 Kelvin and -272.65 degrees Celsius":
894 // assertFormatSingle(
895 // u"Meaningless: kelvin-and-celcius",
896 // u"unit/kelvin-and-celsius",
897 // u"unit/kelvin-and-celsius",
898 // NumberFormatter::with().unit(MeasureUnit::forIdentifier("kelvin-and-celsius", status)),
899 // Locale("en"),
900 // 23.5,
901 // u"23 K, 272.65°C");
902
903 if (uprv_getNaN() != 0.0) {
904 assertFormatSingle(
905 u"Measured -Inf",
906 u"measure-unit/electric-ampere",
907 u"unit/ampere",
908 NumberFormatter::with().unit(MeasureUnit::getAmpere()),
909 Locale("en"),
910 -uprv_getInfinity(),
911 u"-∞ A");
912
913 assertFormatSingle(
914 u"Measured NaN",
915 u"measure-unit/temperature-celsius",
916 u"unit/celsius",
917 NumberFormatter::with().unit(MeasureUnit::forIdentifier("celsius", status)),
918 Locale("en"),
919 uprv_getNaN(),
920 u"NaN°C");
921 }
922 }
923
unitCompoundMeasure()924 void NumberFormatterApiTest::unitCompoundMeasure() {
925 IcuTestErrorCode status(*this, "unitCompoundMeasure()");
926
927 assertFormatDescending(
928 u"Meters Per Second Short (unit that simplifies) and perUnit method",
929 u"measure-unit/length-meter per-measure-unit/duration-second",
930 u"unit/meter-per-second",
931 NumberFormatter::with().unit(METER).perUnit(SECOND),
932 Locale::getEnglish(),
933 u"87,650 m/s",
934 u"8,765 m/s",
935 u"876.5 m/s",
936 u"87.65 m/s",
937 u"8.765 m/s",
938 u"0.8765 m/s",
939 u"0.08765 m/s",
940 u"0.008765 m/s",
941 u"0 m/s");
942
943 assertFormatDescending(
944 u"Meters Per Second Short, built-in m/s",
945 u"measure-unit/speed-meter-per-second",
946 u"unit/meter-per-second",
947 NumberFormatter::with().unit(METER_PER_SECOND),
948 Locale::getEnglish(),
949 u"87,650 m/s",
950 u"8,765 m/s",
951 u"876.5 m/s",
952 u"87.65 m/s",
953 u"8.765 m/s",
954 u"0.8765 m/s",
955 u"0.08765 m/s",
956 u"0.008765 m/s",
957 u"0 m/s");
958
959 assertFormatDescending(
960 u"Pounds Per Square Mile Short (secondary unit has per-format) and adoptPerUnit method",
961 u"measure-unit/mass-pound per-measure-unit/area-square-mile",
962 u"unit/pound-per-square-mile",
963 NumberFormatter::with().unit(POUND).adoptPerUnit(new MeasureUnit(SQUARE_MILE)),
964 Locale::getEnglish(),
965 u"87,650 lb/mi²",
966 u"8,765 lb/mi²",
967 u"876.5 lb/mi²",
968 u"87.65 lb/mi²",
969 u"8.765 lb/mi²",
970 u"0.8765 lb/mi²",
971 u"0.08765 lb/mi²",
972 u"0.008765 lb/mi²",
973 u"0 lb/mi²");
974
975 assertFormatDescending(
976 u"Joules Per Furlong Short (unit with no simplifications or special patterns)",
977 u"measure-unit/energy-joule per-measure-unit/length-furlong",
978 u"unit/joule-per-furlong",
979 NumberFormatter::with().unit(JOULE).perUnit(FURLONG),
980 Locale::getEnglish(),
981 u"87,650 J/fur",
982 u"8,765 J/fur",
983 u"876.5 J/fur",
984 u"87.65 J/fur",
985 u"8.765 J/fur",
986 u"0.8765 J/fur",
987 u"0.08765 J/fur",
988 u"0.008765 J/fur",
989 u"0 J/fur");
990
991 assertFormatDescending(
992 u"Joules Per Furlong Short with unit identifier via API",
993 u"measure-unit/energy-joule per-measure-unit/length-furlong",
994 u"unit/joule-per-furlong",
995 NumberFormatter::with().unit(MeasureUnit::forIdentifier("joule-per-furlong", status)),
996 Locale::getEnglish(),
997 u"87,650 J/fur",
998 u"8,765 J/fur",
999 u"876.5 J/fur",
1000 u"87.65 J/fur",
1001 u"8.765 J/fur",
1002 u"0.8765 J/fur",
1003 u"0.08765 J/fur",
1004 u"0.008765 J/fur",
1005 u"0 J/fur");
1006
1007 assertFormatDescending(
1008 u"Pounds per Square Inch: composed",
1009 u"measure-unit/force-pound-force per-measure-unit/area-square-inch",
1010 u"unit/pound-force-per-square-inch",
1011 NumberFormatter::with().unit(POUND_FORCE).perUnit(SQUARE_INCH),
1012 Locale::getEnglish(),
1013 u"87,650 psi",
1014 u"8,765 psi",
1015 u"876.5 psi",
1016 u"87.65 psi",
1017 u"8.765 psi",
1018 u"0.8765 psi",
1019 u"0.08765 psi",
1020 u"0.008765 psi",
1021 u"0 psi");
1022
1023 assertFormatDescending(
1024 u"Pounds per Square Inch: built-in",
1025 u"measure-unit/force-pound-force per-measure-unit/area-square-inch",
1026 u"unit/pound-force-per-square-inch",
1027 NumberFormatter::with().unit(MeasureUnit::getPoundPerSquareInch()),
1028 Locale::getEnglish(),
1029 u"87,650 psi",
1030 u"8,765 psi",
1031 u"876.5 psi",
1032 u"87.65 psi",
1033 u"8.765 psi",
1034 u"0.8765 psi",
1035 u"0.08765 psi",
1036 u"0.008765 psi",
1037 u"0 psi");
1038
1039 assertFormatSingle(
1040 u"m/s/s simplifies to m/s^2",
1041 u"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
1042 u"unit/meter-per-square-second",
1043 NumberFormatter::with().unit(METER_PER_SECOND).perUnit(SECOND),
1044 Locale("en-GB"),
1045 2.4,
1046 u"2.4 m/s\u00B2");
1047
1048 assertFormatSingle(
1049 u"Negative numbers: acceleration",
1050 u"measure-unit/acceleration-meter-per-square-second",
1051 u"unit/meter-per-second-second",
1052 NumberFormatter::with().unit(MeasureUnit::forIdentifier("meter-per-pow2-second", status)),
1053 Locale("af-ZA"),
1054 -9.81,
1055 u"-9,81 m/s\u00B2");
1056
1057 // Testing the rejection of invalid specifications
1058
1059 // If .unit() is not given a built-in type, .perUnit() is not allowed
1060 // (because .unit is now flexible enough to handle compound units,
1061 // .perUnit() is supported for backward compatibility).
1062 LocalizedNumberFormatter nf = NumberFormatter::with()
1063 .unit(MeasureUnit::forIdentifier("furlong-pascal", status))
1064 .perUnit(METER)
1065 .locale("en-GB");
1066 status.assertSuccess(); // Error is only returned once we try to format.
1067 FormattedNumber num = nf.formatDouble(2.4, status);
1068 if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) {
1069 errln(UnicodeString("Expected failure for unit/furlong-pascal per-unit/length-meter, got: \"") +
1070 nf.formatDouble(2.4, status).toString(status) + "\".");
1071 status.assertSuccess();
1072 }
1073
1074 // .perUnit() may only be passed a built-in type, or something that combines
1075 // to a built-in type together with .unit().
1076 MeasureUnit SQUARE_SECOND = MeasureUnit::forIdentifier("square-second", status);
1077 nf = NumberFormatter::with().unit(FURLONG).perUnit(SQUARE_SECOND).locale("en-GB");
1078 status.assertSuccess(); // Error is only returned once we try to format.
1079 num = nf.formatDouble(2.4, status);
1080 if (!status.expectErrorAndReset(U_UNSUPPORTED_ERROR)) {
1081 errln(UnicodeString("Expected failure, got: \"") +
1082 nf.formatDouble(2.4, status).toString(status) + "\".");
1083 status.assertSuccess();
1084 }
1085 // As above, "square-second" is not a built-in type, however this time,
1086 // meter-per-square-second is a built-in type.
1087 assertFormatSingle(
1088 u"meter per square-second works as a composed unit",
1089 u"measure-unit/speed-meter-per-second per-measure-unit/duration-second",
1090 u"unit/meter-per-square-second",
1091 NumberFormatter::with().unit(METER).perUnit(SQUARE_SECOND),
1092 Locale("en-GB"),
1093 2.4,
1094 u"2.4 m/s\u00B2");
1095 }
1096
unitArbitraryMeasureUnits()1097 void NumberFormatterApiTest::unitArbitraryMeasureUnits() {
1098 IcuTestErrorCode status(*this, "unitArbitraryMeasureUnits()");
1099
1100 // TODO: fix after data bug is resolved? See CLDR-14510.
1101 // assertFormatSingle(
1102 // u"Binary unit prefix: kibibyte",
1103 // u"unit/kibibyte",
1104 // u"unit/kibibyte",
1105 // NumberFormatter::with().unit(MeasureUnit::forIdentifier("kibibyte", status)),
1106 // Locale("en-GB"),
1107 // 2.4,
1108 // u"2.4 KiB");
1109
1110 assertFormatSingle(
1111 u"Binary unit prefix: kibibyte full-name",
1112 u"unit/kibibyte unit-width-full-name",
1113 u"unit/kibibyte unit-width-full-name",
1114 NumberFormatter::with()
1115 .unit(MeasureUnit::forIdentifier("kibibyte", status))
1116 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1117 Locale("en-GB"),
1118 2.4,
1119 u"2.4 kibibytes");
1120
1121 assertFormatSingle(
1122 u"Binary unit prefix: kibibyte full-name",
1123 u"unit/kibibyte unit-width-full-name",
1124 u"unit/kibibyte unit-width-full-name",
1125 NumberFormatter::with()
1126 .unit(MeasureUnit::forIdentifier("kibibyte", status))
1127 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1128 Locale("de"),
1129 2.4,
1130 u"2,4 Kibibyte");
1131
1132 assertFormatSingle(
1133 u"Binary prefix for non-digital units: kibimeter",
1134 u"unit/kibimeter",
1135 u"unit/kibimeter",
1136 NumberFormatter::with()
1137 .unit(MeasureUnit::forIdentifier("kibimeter", status)),
1138 Locale("en-GB"),
1139 2.4,
1140 u"2.4 Kim");
1141
1142 assertFormatSingle(
1143 u"Extra-large prefix: exabyte",
1144 u"unit/exabyte",
1145 u"unit/exabyte",
1146 NumberFormatter::with()
1147 .unit(MeasureUnit::forIdentifier("exabyte", status)),
1148 Locale("en-GB"),
1149 2.4,
1150 u"2.4 Ebyte");
1151
1152 assertFormatSingle(
1153 u"Extra-large prefix: exabyte (full-name)",
1154 u"unit/exabyte unit-width-full-name",
1155 u"unit/exabyte unit-width-full-name",
1156 NumberFormatter::with()
1157 .unit(MeasureUnit::forIdentifier("exabyte", status))
1158 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1159 Locale("en-GB"),
1160 2.4,
1161 u"2.4 exabytes");
1162
1163 assertFormatSingle(
1164 u"SI prefix falling back to root: microohm",
1165 u"unit/microohm",
1166 u"unit/microohm",
1167 NumberFormatter::with()
1168 .unit(MeasureUnit::forIdentifier("microohm", status)),
1169 Locale("de-CH"),
1170 2.4,
1171 u"2.4 μΩ");
1172
1173 assertFormatSingle(
1174 u"de-CH fallback to de: microohm unit-width-full-name",
1175 u"unit/microohm unit-width-full-name",
1176 u"unit/microohm unit-width-full-name",
1177 NumberFormatter::with()
1178 .unit(MeasureUnit::forIdentifier("microohm", status))
1179 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1180 Locale("de-CH"),
1181 2.4,
1182 u"2.4\u00A0Mikroohm");
1183
1184 assertFormatSingle(
1185 u"No prefixes, 'times' pattern: joule-furlong",
1186 u"unit/joule-furlong",
1187 u"unit/joule-furlong",
1188 NumberFormatter::with()
1189 .unit(MeasureUnit::forIdentifier("joule-furlong", status)),
1190 Locale("en"),
1191 2.4,
1192 u"2.4 J⋅fur");
1193
1194 assertFormatSingle(
1195 u"No numeratorUnitString: per-second",
1196 u"unit/per-second",
1197 u"unit/per-second",
1198 NumberFormatter::with()
1199 .unit(MeasureUnit::forIdentifier("per-second", status)),
1200 Locale("de-CH"),
1201 2.4,
1202 u"2.4/s");
1203
1204 assertFormatSingle(
1205 u"No numeratorUnitString: per-second unit-width-full-name",
1206 u"unit/per-second unit-width-full-name",
1207 u"unit/per-second unit-width-full-name",
1208 NumberFormatter::with()
1209 .unit(MeasureUnit::forIdentifier("per-second", status))
1210 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1211 Locale("de-CH"),
1212 2.4,
1213 u"2.4 pro Sekunde");
1214
1215 assertFormatSingle(
1216 u"Prefix in the denominator: nanogram-per-picobarrel",
1217 u"unit/nanogram-per-picobarrel",
1218 u"unit/nanogram-per-picobarrel",
1219 NumberFormatter::with()
1220 .unit(MeasureUnit::forIdentifier("nanogram-per-picobarrel", status)),
1221 Locale("en-ZA"),
1222 2.4,
1223 u"2.4 ng/pbbl");
1224
1225 assertFormatSingle(
1226 u"Prefix in the denominator: nanogram-per-picobarrel unit-width-full-name",
1227 u"unit/nanogram-per-picobarrel unit-width-full-name",
1228 u"unit/nanogram-per-picobarrel unit-width-full-name",
1229 NumberFormatter::with()
1230 .unit(MeasureUnit::forIdentifier("nanogram-per-picobarrel", status))
1231 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1232 Locale("en-ZA"),
1233 2.4,
1234 u"2.4 nanograms per picobarrel");
1235
1236 // Valid MeasureUnit, but unformattable, because we only have patterns for
1237 // pow2 and pow3 at this time:
1238 LocalizedNumberFormatter lnf = NumberFormatter::with()
1239 .unit(MeasureUnit::forIdentifier("pow4-mile", status))
1240 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
1241 .locale("en-ZA");
1242 lnf.operator=(lnf); // self-assignment should be a no-op
1243 lnf.formatInt(1, status);
1244 status.expectErrorAndReset(U_INTERNAL_PROGRAM_ERROR);
1245
1246 assertFormatSingle(
1247 u"kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name",
1248 u"unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name",
1249 u"unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name",
1250 NumberFormatter::with()
1251 .unit(MeasureUnit::forIdentifier("kibijoule-foot-per-cubic-gigafurlong-square-second",
1252 status))
1253 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1254 Locale("en-ZA"),
1255 2.4,
1256 u"2.4 kibijoule-feet per cubic gigafurlong-square second");
1257
1258 assertFormatSingle(
1259 u"kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name",
1260 u"unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name",
1261 u"unit/kibijoule-foot-per-cubic-gigafurlong-square-second unit-width-full-name",
1262 NumberFormatter::with()
1263 .unit(MeasureUnit::forIdentifier("kibijoule-foot-per-cubic-gigafurlong-square-second",
1264 status))
1265 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1266 Locale("de-CH"),
1267 2.4,
1268 u"2.4\u00A0Kibijoule⋅Fuss pro Kubikgigafurlong⋅Quadratsekunde");
1269
1270 // TODO(ICU-21504): We want to be able to format this, but "100-kilometer"
1271 // is not yet supported when it's not part of liter-per-100-kilometer:
1272 // Actually now in CLDR 40 this is supported directly in data, so change test.
1273 assertFormatSingle(
1274 u"kilowatt-hour-per-100-kilometer unit-width-full-name",
1275 u"unit/kilowatt-hour-per-100-kilometer unit-width-full-name",
1276 u"unit/kilowatt-hour-per-100-kilometer unit-width-full-name",
1277 NumberFormatter::with()
1278 .unit(MeasureUnit::forIdentifier("kilowatt-hour-per-100-kilometer",
1279 status))
1280 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1281 Locale("en-ZA"),
1282 2.4,
1283 u"2.4 kilowatt-hours per 100 kilometres");
1284 }
1285
1286 // TODO: merge these tests into numbertest_skeletons.cpp instead of here:
unitSkeletons()1287 void NumberFormatterApiTest::unitSkeletons() {
1288 const struct TestCase {
1289 const char *msg;
1290 const char16_t *inputSkeleton;
1291 const char16_t *normalizedSkeleton;
1292 } cases[] = {
1293 {"old-form built-in compound unit", //
1294 u"measure-unit/speed-meter-per-second", //
1295 u"unit/meter-per-second"},
1296
1297 {"old-form compound construction, converts to built-in", //
1298 u"measure-unit/length-meter per-measure-unit/duration-second", //
1299 u"unit/meter-per-second"},
1300
1301 {"old-form compound construction which does not simplify to a built-in", //
1302 u"measure-unit/energy-joule per-measure-unit/length-meter", //
1303 u"unit/joule-per-meter"},
1304
1305 {"old-form compound-compound ugliness resolves neatly", //
1306 u"measure-unit/speed-meter-per-second per-measure-unit/duration-second", //
1307 u"unit/meter-per-square-second"},
1308
1309 {"short-form built-in units stick with the built-in", //
1310 u"unit/meter-per-second", //
1311 u"unit/meter-per-second"},
1312
1313 {"short-form compound units stay as is", //
1314 u"unit/square-meter-per-square-meter", //
1315 u"unit/square-meter-per-square-meter"},
1316
1317 {"short-form compound units stay as is", //
1318 u"unit/joule-per-furlong", //
1319 u"unit/joule-per-furlong"},
1320
1321 {"short-form that doesn't consist of built-in units", //
1322 u"unit/hectometer-per-second", //
1323 u"unit/hectometer-per-second"},
1324
1325 {"short-form that doesn't consist of built-in units", //
1326 u"unit/meter-per-hectosecond", //
1327 u"unit/meter-per-hectosecond"},
1328
1329 {"percent compound skeletons handled correctly", //
1330 u"unit/percent-per-meter", //
1331 u"unit/percent-per-meter"},
1332
1333 {"permille compound skeletons handled correctly", //
1334 u"measure-unit/concentr-permille per-measure-unit/length-meter", //
1335 u"unit/permille-per-meter"},
1336
1337 {"percent simple unit is not actually considered a unit", //
1338 u"unit/percent", //
1339 u"percent"},
1340
1341 {"permille simple unit is not actually considered a unit", //
1342 u"measure-unit/concentr-permille", //
1343 u"permille"},
1344
1345 {"Round-trip example from icu-units#35", //
1346 u"unit/kibijoule-per-furlong", //
1347 u"unit/kibijoule-per-furlong"},
1348 };
1349 for (const auto& cas : cases) {
1350 IcuTestErrorCode status(*this, cas.msg);
1351 auto nf = NumberFormatter::forSkeleton(cas.inputSkeleton, status);
1352 if (status.errIfFailureAndReset("NumberFormatter::forSkeleton failed")) {
1353 continue;
1354 }
1355 assertEquals( //
1356 UnicodeString(true, cas.inputSkeleton, -1) + u" normalization", //
1357 cas.normalizedSkeleton, //
1358 nf.toSkeleton(status));
1359 status.errIfFailureAndReset("NumberFormatter::toSkeleton failed");
1360 }
1361
1362 const struct FailCase {
1363 const char *msg;
1364 const char16_t *inputSkeleton;
1365 UErrorCode expectedForSkelStatus;
1366 UErrorCode expectedToSkelStatus;
1367 } failCases[] = {
1368 {"Parsing measure-unit/* results in failure if not built-in unit",
1369 u"measure-unit/hectometer", //
1370 U_NUMBER_SKELETON_SYNTAX_ERROR, //
1371 U_ZERO_ERROR},
1372
1373 {"Parsing per-measure-unit/* results in failure if not built-in unit",
1374 u"measure-unit/meter per-measure-unit/hectosecond", //
1375 U_NUMBER_SKELETON_SYNTAX_ERROR, //
1376 U_ZERO_ERROR},
1377
1378 {"\"currency/EUR measure-unit/length-meter\" fails, conflicting skeleton.",
1379 u"currency/EUR measure-unit/length-meter", //
1380 U_NUMBER_SKELETON_SYNTAX_ERROR, //
1381 U_ZERO_ERROR},
1382
1383 {"\"measure-unit/length-meter currency/EUR\" fails, conflicting skeleton.",
1384 u"measure-unit/length-meter currency/EUR", //
1385 U_NUMBER_SKELETON_SYNTAX_ERROR, //
1386 U_ZERO_ERROR},
1387
1388 {"\"currency/EUR per-measure-unit/meter\" fails, conflicting skeleton.",
1389 u"currency/EUR per-measure-unit/length-meter", //
1390 U_NUMBER_SKELETON_SYNTAX_ERROR, //
1391 U_ZERO_ERROR},
1392 };
1393 for (const auto& cas : failCases) {
1394 IcuTestErrorCode status(*this, cas.msg);
1395 auto nf = NumberFormatter::forSkeleton(cas.inputSkeleton, status);
1396 if (status.expectErrorAndReset(cas.expectedForSkelStatus, cas.msg)) {
1397 continue;
1398 }
1399 nf.toSkeleton(status);
1400 status.expectErrorAndReset(cas.expectedToSkelStatus, cas.msg);
1401 }
1402
1403 IcuTestErrorCode status(*this, "unitSkeletons");
1404 assertEquals( //
1405 ".unit(METER_PER_SECOND) normalization", //
1406 u"unit/meter-per-second", //
1407 NumberFormatter::with().unit(METER_PER_SECOND).toSkeleton(status));
1408 assertEquals( //
1409 ".unit(METER).perUnit(SECOND) normalization", //
1410 u"unit/meter-per-second",
1411 NumberFormatter::with().unit(METER).perUnit(SECOND).toSkeleton(status));
1412 assertEquals( //
1413 ".unit(MeasureUnit::forIdentifier(\"hectometer\", status)) normalization", //
1414 u"unit/hectometer",
1415 NumberFormatter::with()
1416 .unit(MeasureUnit::forIdentifier("hectometer", status))
1417 .toSkeleton(status));
1418 assertEquals( //
1419 ".unit(MeasureUnit::forIdentifier(\"hectometer\", status)) normalization", //
1420 u"unit/meter-per-hectosecond",
1421 NumberFormatter::with()
1422 .unit(METER)
1423 .perUnit(MeasureUnit::forIdentifier("hectosecond", status))
1424 .toSkeleton(status));
1425
1426 status.assertSuccess();
1427 assertEquals( //
1428 ".unit(CURRENCY) produces a currency/CURRENCY skeleton", //
1429 u"currency/GBP", //
1430 NumberFormatter::with().unit(GBP).toSkeleton(status));
1431 status.assertSuccess();
1432 // .unit(CURRENCY).perUnit(ANYTHING) is not supported.
1433 NumberFormatter::with().unit(GBP).perUnit(METER).toSkeleton(status);
1434 status.expectErrorAndReset(U_UNSUPPORTED_ERROR);
1435 }
1436
unitUsage()1437 void NumberFormatterApiTest::unitUsage() {
1438 IcuTestErrorCode status(*this, "unitUsage()");
1439 UnlocalizedNumberFormatter unloc_formatter;
1440 LocalizedNumberFormatter formatter;
1441 FormattedNumber formattedNum;
1442 UnicodeString uTestCase;
1443
1444 status.assertSuccess();
1445 formattedNum =
1446 NumberFormatter::with().usage("road").locale(Locale::getEnglish()).formatInt(1, status);
1447 status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
1448
1449 unloc_formatter = NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter());
1450
1451 uTestCase = u"unitUsage() en-ZA road";
1452 formatter = unloc_formatter.locale("en-ZA");
1453 formattedNum = formatter.formatDouble(321, status);
1454 status.errIfFailureAndReset("unitUsage() en-ZA road formatDouble");
1455 assertTrue(
1456 uTestCase + u", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"",
1457 MeasureUnit::getMeter() == formattedNum.getOutputUnit(status));
1458 assertEquals(uTestCase, "300 m", formattedNum.toString(status));
1459 {
1460 static const UFieldPosition expectedFieldPositions[] = {
1461 {UNUM_INTEGER_FIELD, 0, 3},
1462 {UNUM_MEASURE_UNIT_FIELD, 4, 5}};
1463 assertNumberFieldPositions(
1464 (uTestCase + u" field positions").getTerminatedBuffer(),
1465 formattedNum,
1466 expectedFieldPositions,
1467 UPRV_LENGTHOF(expectedFieldPositions));
1468 }
1469 assertFormatDescendingBig(
1470 uTestCase.getTerminatedBuffer(),
1471 u"measure-unit/length-meter usage/road",
1472 u"unit/meter usage/road",
1473 unloc_formatter,
1474 Locale("en-ZA"),
1475 u"87,650 km",
1476 u"8,765 km",
1477 u"876 km", // 6.5 rounds down, 7.5 rounds up.
1478 u"88 km",
1479 u"8.8 km",
1480 u"900 m",
1481 u"90 m",
1482 u"9 m",
1483 u"0 m");
1484
1485 uTestCase = u"unitUsage() en-GB road";
1486 formatter = unloc_formatter.locale("en-GB");
1487 formattedNum = formatter.formatDouble(321, status);
1488 status.errIfFailureAndReset("unitUsage() en-GB road, formatDouble(...)");
1489 assertTrue(
1490 uTestCase + u", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"",
1491 MeasureUnit::getYard() == formattedNum.getOutputUnit(status));
1492 status.errIfFailureAndReset("unitUsage() en-GB road, getOutputUnit(...)");
1493 assertEquals(uTestCase, "350 yd", formattedNum.toString(status));
1494 status.errIfFailureAndReset("unitUsage() en-GB road, toString(...)");
1495 {
1496 static const UFieldPosition expectedFieldPositions[] = {
1497 {UNUM_INTEGER_FIELD, 0, 3},
1498 {UNUM_MEASURE_UNIT_FIELD, 4, 6}};
1499 assertNumberFieldPositions(
1500 (uTestCase + u" field positions").getTerminatedBuffer(),
1501 formattedNum,
1502 expectedFieldPositions,
1503 UPRV_LENGTHOF(expectedFieldPositions));
1504 }
1505 assertFormatDescendingBig(
1506 uTestCase.getTerminatedBuffer(),
1507 u"measure-unit/length-meter usage/road",
1508 u"unit/meter usage/road",
1509 unloc_formatter,
1510 Locale("en-GB"),
1511 u"54,463 mi",
1512 u"5,446 mi",
1513 u"545 mi",
1514 u"54 mi",
1515 u"5.4 mi",
1516 u"0.54 mi",
1517 u"100 yd",
1518 u"10 yd",
1519 u"0 yd");
1520
1521 uTestCase = u"unitUsage() en-US road";
1522 formatter = unloc_formatter.locale("en-US");
1523 formattedNum = formatter.formatDouble(321, status);
1524 status.errIfFailureAndReset("unitUsage() en-US road, formatDouble(...)");
1525 assertTrue(
1526 uTestCase + u", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"",
1527 MeasureUnit::getFoot() == formattedNum.getOutputUnit(status));
1528 status.errIfFailureAndReset("unitUsage() en-US road, getOutputUnit(...)");
1529 assertEquals(uTestCase, "1,050 ft", formattedNum.toString(status));
1530 status.errIfFailureAndReset("unitUsage() en-US road, toString(...)");
1531 {
1532 static const UFieldPosition expectedFieldPositions[] = {
1533 {UNUM_GROUPING_SEPARATOR_FIELD, 1, 2},
1534 {UNUM_INTEGER_FIELD, 0, 5},
1535 {UNUM_MEASURE_UNIT_FIELD, 6, 8}};
1536 assertNumberFieldPositions(
1537 (uTestCase + u" field positions").getTerminatedBuffer(),
1538 formattedNum,
1539 expectedFieldPositions,
1540 UPRV_LENGTHOF(expectedFieldPositions));
1541 }
1542 assertFormatDescendingBig(
1543 uTestCase.getTerminatedBuffer(),
1544 u"measure-unit/length-meter usage/road",
1545 u"unit/meter usage/road",
1546 unloc_formatter,
1547 Locale("en-US"),
1548 u"54,463 mi",
1549 u"5,446 mi",
1550 u"545 mi",
1551 u"54 mi",
1552 u"5.4 mi",
1553 u"0.54 mi",
1554 u"300 ft",
1555 u"30 ft",
1556 u"0 ft");
1557
1558 unloc_formatter = NumberFormatter::with().usage("person").unit(MeasureUnit::getKilogram());
1559 uTestCase = u"unitUsage() en-GB person";
1560 formatter = unloc_formatter.locale("en-GB");
1561 formattedNum = formatter.formatDouble(80, status);
1562 status.errIfFailureAndReset("unitUsage() en-GB person formatDouble");
1563 assertTrue(
1564 uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit(status).getIdentifier() + "\"",
1565 MeasureUnit::forIdentifier("stone-and-pound", status) == formattedNum.getOutputUnit(status));
1566 status.errIfFailureAndReset("unitUsage() en-GB person - formattedNum.getOutputUnit(status)");
1567 assertEquals(uTestCase, "12 st, 8.4 lb", formattedNum.toString(status));
1568 status.errIfFailureAndReset("unitUsage() en-GB person, toString(...)");
1569 {
1570 static const UFieldPosition expectedFieldPositions[] = {
1571 // // Desired output: TODO(icu-units#67)
1572 // {UNUM_INTEGER_FIELD, 0, 2},
1573 // {UNUM_MEASURE_UNIT_FIELD, 3, 5},
1574 // {ULISTFMT_LITERAL_FIELD, 5, 6},
1575 // {UNUM_INTEGER_FIELD, 7, 8},
1576 // {UNUM_DECIMAL_SEPARATOR_FIELD, 8, 9},
1577 // {UNUM_FRACTION_FIELD, 9, 10},
1578 // {UNUM_MEASURE_UNIT_FIELD, 11, 13}};
1579
1580 // Current output: rather no fields than wrong fields
1581 {UNUM_INTEGER_FIELD, 7, 8},
1582 {UNUM_DECIMAL_SEPARATOR_FIELD, 8, 9},
1583 {UNUM_FRACTION_FIELD, 9, 10},
1584 };
1585 assertNumberFieldPositions(
1586 (uTestCase + u" field positions").getTerminatedBuffer(),
1587 formattedNum,
1588 expectedFieldPositions,
1589 UPRV_LENGTHOF(expectedFieldPositions));
1590 }
1591 assertFormatDescending(
1592 uTestCase.getTerminatedBuffer(),
1593 u"measure-unit/mass-kilogram usage/person",
1594 u"unit/kilogram usage/person",
1595 unloc_formatter,
1596 Locale("en-GB"),
1597 u"13,802 st, 7.2 lb",
1598 u"1,380 st, 3.5 lb",
1599 u"138 st, 0.35 lb",
1600 u"13 st, 11 lb",
1601 u"1 st, 5.3 lb",
1602 u"1 lb, 15 oz",
1603 u"0 lb, 3.1 oz",
1604 u"0 lb, 0.31 oz",
1605 u"0 lb, 0 oz");
1606
1607 assertFormatDescending(
1608 uTestCase.getTerminatedBuffer(),
1609 u"usage/person unit-width-narrow measure-unit/mass-kilogram",
1610 u"usage/person unit-width-narrow unit/kilogram",
1611 unloc_formatter.unitWidth(UNUM_UNIT_WIDTH_NARROW),
1612 Locale("en-GB"),
1613 u"13,802st 7.2lb",
1614 u"1,380st 3.5lb",
1615 u"138st 0.35lb",
1616 u"13st 11lb",
1617 u"1st 5.3lb",
1618 u"1lb 15oz",
1619 u"0lb 3.1oz",
1620 u"0lb 0.31oz",
1621 u"0lb 0oz");
1622
1623 assertFormatDescending(
1624 uTestCase.getTerminatedBuffer(),
1625 u"usage/person unit-width-short measure-unit/mass-kilogram",
1626 u"usage/person unit-width-short unit/kilogram",
1627 unloc_formatter.unitWidth(UNUM_UNIT_WIDTH_SHORT),
1628 Locale("en-GB"),
1629 u"13,802 st, 7.2 lb",
1630 u"1,380 st, 3.5 lb",
1631 u"138 st, 0.35 lb",
1632 u"13 st, 11 lb",
1633 u"1 st, 5.3 lb",
1634 u"1 lb, 15 oz",
1635 u"0 lb, 3.1 oz",
1636 u"0 lb, 0.31 oz",
1637 u"0 lb, 0 oz");
1638
1639 assertFormatDescending(
1640 uTestCase.getTerminatedBuffer(),
1641 u"usage/person unit-width-full-name measure-unit/mass-kilogram",
1642 u"usage/person unit-width-full-name unit/kilogram",
1643 unloc_formatter.unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
1644 Locale("en-GB"),
1645 u"13,802 stone, 7.2 pounds",
1646 u"1,380 stone, 3.5 pounds",
1647 u"138 stone, 0.35 pounds",
1648 u"13 stone, 11 pounds",
1649 u"1 stone, 5.3 pounds",
1650 u"1 pound, 15 ounces",
1651 u"0 pounds, 3.1 ounces",
1652 u"0 pounds, 0.31 ounces",
1653 u"0 pounds, 0 ounces");
1654
1655 assertFormatDescendingBig(
1656 u"Scientific notation with Usage: possible when using a reasonable Precision",
1657 u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
1658 u"scientific @### usage/default unit/square-meter unit-width-full-name",
1659 NumberFormatter::with()
1660 .unit(SQUARE_METER)
1661 .usage("default")
1662 .notation(Notation::scientific())
1663 .precision(Precision::minMaxSignificantDigits(1, 4))
1664 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
1665 Locale("en-ZA"),
1666 u"8.765E1 square kilometres",
1667 u"8.765E0 square kilometres",
1668 u"8.765E1 hectares",
1669 u"8.765E0 hectares",
1670 u"8.765E3 square metres",
1671 u"8.765E2 square metres",
1672 u"8.765E1 square metres",
1673 u"8.765E0 square metres",
1674 u"0E0 square centimetres");
1675
1676 assertFormatSingle(
1677 u"Negative Infinity with Unit Preferences",
1678 u"measure-unit/area-acre usage/default",
1679 u"unit/acre usage/default",
1680 NumberFormatter::with().unit(MeasureUnit::getAcre()).usage("default"),
1681 Locale::getEnglish(),
1682 -uprv_getInfinity(),
1683 u"-∞ sq mi");
1684
1685 // // TODO(icu-units#131): do we care about NaN?
1686 // // TODO: on some platforms with MSVC, "-NaN sec" is returned.
1687 // assertFormatSingle(
1688 // u"NaN with Unit Preferences",
1689 // u"measure-unit/area-acre usage/default",
1690 // u"unit/acre usage/default",
1691 // NumberFormatter::with().unit(MeasureUnit::getAcre()).usage("default"),
1692 // Locale::getEnglish(),
1693 // uprv_getNaN(),
1694 // u"NaN cm²");
1695
1696 assertFormatSingle(
1697 u"Negative numbers: minute-and-second",
1698 u"measure-unit/duration-second usage/media",
1699 u"unit/second usage/media",
1700 NumberFormatter::with().unit(SECOND).usage("media"),
1701 Locale("nl-NL"),
1702 -77.7,
1703 u"-1 min, 18 sec");
1704
1705 assertFormatSingle(
1706 u"Negative numbers: media seconds",
1707 u"measure-unit/duration-second usage/media",
1708 u"unit/second usage/media",
1709 NumberFormatter::with().unit(SECOND).usage("media"),
1710 Locale("nl-NL"),
1711 -2.7,
1712 u"-2,7 sec");
1713
1714 // // TODO: on some platforms with MSVC, "-NaN sec" is returned.
1715 // assertFormatSingle(
1716 // u"NaN minute-and-second",
1717 // u"measure-unit/duration-second usage/media",
1718 // u"unit/second usage/media",
1719 // NumberFormatter::with().unit(SECOND).usage("media"),
1720 // Locale("nl-NL"),
1721 // uprv_getNaN(),
1722 // u"NaN sec");
1723
1724 assertFormatSingle(
1725 u"NaN meter-and-centimeter",
1726 u"measure-unit/length-meter usage/person-height",
1727 u"unit/meter usage/person-height",
1728 NumberFormatter::with().unit(METER).usage("person-height"),
1729 Locale("sv-SE"),
1730 uprv_getNaN(),
1731 u"0 m, NaN cm");
1732
1733 assertFormatSingle(
1734 u"Rounding Mode propagates: rounding down",
1735 u"usage/road measure-unit/length-centimeter rounding-mode-floor",
1736 u"usage/road unit/centimeter rounding-mode-floor",
1737 NumberFormatter::with()
1738 .unit(MeasureUnit::forIdentifier("centimeter", status))
1739 .usage("road")
1740 .roundingMode(UNUM_ROUND_FLOOR),
1741 Locale("en-ZA"),
1742 34500,
1743 u"300 m");
1744
1745 assertFormatSingle(
1746 u"Rounding Mode propagates: rounding up",
1747 u"usage/road measure-unit/length-centimeter rounding-mode-ceiling",
1748 u"usage/road unit/centimeter rounding-mode-ceiling",
1749 NumberFormatter::with()
1750 .unit(MeasureUnit::forIdentifier("centimeter", status))
1751 .usage("road")
1752 .roundingMode(UNUM_ROUND_CEILING),
1753 Locale("en-ZA"),
1754 30500,
1755 u"350 m");
1756
1757 assertFormatSingle(u"Fuel consumption: inverted units", //
1758 u"unit/liter-per-100-kilometer usage/vehicle-fuel", //
1759 u"unit/liter-per-100-kilometer usage/vehicle-fuel", //
1760 NumberFormatter::with() //
1761 .unit(MeasureUnit::forIdentifier("liter-per-100-kilometer", status)) //
1762 .usage("vehicle-fuel"), //
1763 Locale("en-US"), //
1764 6.6, //
1765 "36 mpg");
1766
1767 assertFormatSingle(u"Fuel consumption: inverted units, divide-by-zero, en-US", //
1768 u"unit/liter-per-100-kilometer usage/vehicle-fuel", //
1769 u"unit/liter-per-100-kilometer usage/vehicle-fuel", //
1770 NumberFormatter::with() //
1771 .unit(MeasureUnit::forIdentifier("liter-per-100-kilometer", status)) //
1772 .usage("vehicle-fuel"), //
1773 Locale("en-US"), //
1774 0, //
1775 u"∞ mpg");
1776
1777 assertFormatSingle(u"Fuel consumption: inverted units, divide-by-zero, en-ZA", //
1778 u"unit/mile-per-gallon usage/vehicle-fuel", //
1779 u"unit/mile-per-gallon usage/vehicle-fuel", //
1780 NumberFormatter::with() //
1781 .unit(MeasureUnit::forIdentifier("mile-per-gallon", status)) //
1782 .usage("vehicle-fuel"), //
1783 Locale("en-ZA"), //
1784 0, //
1785 u"∞ l/100 km");
1786
1787 assertFormatSingle(u"Fuel consumption: inverted units, divide-by-inf", //
1788 u"unit/mile-per-gallon usage/vehicle-fuel", //
1789 u"unit/mile-per-gallon usage/vehicle-fuel", //
1790 NumberFormatter::with() //
1791 .unit(MeasureUnit::forIdentifier("mile-per-gallon", status)) //
1792 .usage("vehicle-fuel"), //
1793 Locale("de-CH"), //
1794 uprv_getInfinity(), //
1795 u"0 L/100 km");
1796
1797 // Test calling `.usage("")` should unset the existing usage.
1798 // First: without usage
1799 assertFormatSingle(u"Rounding Mode propagates: rounding up",
1800 u"measure-unit/length-centimeter rounding-mode-ceiling",
1801 u"unit/centimeter rounding-mode-ceiling",
1802 NumberFormatter::with()
1803 .unit(MeasureUnit::forIdentifier("centimeter", status))
1804 .roundingMode(UNUM_ROUND_CEILING),
1805 Locale("en-US"), //
1806 3048, //
1807 u"3,048 cm");
1808
1809 // Second: with "road" usage
1810 assertFormatSingle(u"Rounding Mode propagates: rounding up",
1811 u"usage/road measure-unit/length-centimeter rounding-mode-ceiling",
1812 u"usage/road unit/centimeter rounding-mode-ceiling",
1813 NumberFormatter::with()
1814 .unit(MeasureUnit::forIdentifier("centimeter", status))
1815 .usage("road")
1816 .roundingMode(UNUM_ROUND_CEILING),
1817 Locale("en-US"), //
1818 3048, //
1819 u"100 ft");
1820
1821 // Third: with "road" usage, then the usage unsetted by calling .usage("")
1822 assertFormatSingle(u"Rounding Mode propagates: rounding up",
1823 u"measure-unit/length-centimeter rounding-mode-ceiling",
1824 u"unit/centimeter rounding-mode-ceiling",
1825 NumberFormatter::with()
1826 .unit(MeasureUnit::forIdentifier("centimeter", status))
1827 .usage("road")
1828 .roundingMode(UNUM_ROUND_CEILING)
1829 .usage(""), // unset
1830 Locale("en-US"), //
1831 3048, //
1832 u"3,048 cm");
1833
1834 assertFormatSingle(u"kilometer-per-liter match the correct category", //
1835 u"unit/kilometer-per-liter usage/default", //
1836 u"unit/kilometer-per-liter usage/default", //
1837 NumberFormatter::with() //
1838 .unit(MeasureUnit::forIdentifier("kilometer-per-liter", status)) //
1839 .usage("default"), //
1840 Locale("en-US"), //
1841 1, //
1842 u"100 L/100 km");
1843
1844 assertFormatSingle(u"gallon-per-mile match the correct category", //
1845 u"unit/gallon-per-mile usage/default", //
1846 u"unit/gallon-per-mile usage/default", //
1847 NumberFormatter::with() //
1848 .unit(MeasureUnit::forIdentifier("gallon-per-mile", status)) //
1849 .usage("default"), //
1850 Locale("en-US"), //
1851 1, //
1852 u"235 L/100 km");
1853
1854 assertFormatSingle(u"psi match the correct category", //
1855 u"unit/megapascal usage/default", //
1856 u"unit/megapascal usage/default", //
1857 NumberFormatter::with() //
1858 .unit(MeasureUnit::forIdentifier("megapascal", status)) //
1859 .usage("default"), //
1860 Locale("en-US"), //
1861 1, //
1862 "145 psi");
1863
1864 assertFormatSingle(u"millibar match the correct category", //
1865 u"unit/millibar usage/default", //
1866 u"unit/millibar usage/default", //
1867 NumberFormatter::with() //
1868 .unit(MeasureUnit::forIdentifier("millibar", status)) //
1869 .usage("default"), //
1870 Locale("en-US"), //
1871 1, //
1872 "0.015 psi");
1873
1874 assertFormatSingle(u"pound-force-per-square-inch match the correct category", //
1875 u"unit/pound-force-per-square-inch usage/default", //
1876 u"unit/pound-force-per-square-inch usage/default", //
1877 NumberFormatter::with() //
1878 .unit(MeasureUnit::forIdentifier("pound-force-per-square-inch", status)) //
1879 .usage("default"), //
1880 Locale("en-US"), //
1881 1, //
1882 "1 psi"); //
1883
1884 assertFormatSingle(u"inch-ofhg match the correct category", //
1885 u"unit/inch-ofhg usage/default", //
1886 u"unit/inch-ofhg usage/default", //
1887 NumberFormatter::with() //
1888 .unit(MeasureUnit::forIdentifier("inch-ofhg", status)) //
1889 .usage("default"), //
1890 Locale("en-US"), //
1891 1, //
1892 "0.49 psi");
1893
1894 assertFormatSingle(u"millimeter-ofhg match the correct category", //
1895 u"unit/millimeter-ofhg usage/default", //
1896 u"unit/millimeter-ofhg usage/default", //
1897 NumberFormatter::with() //
1898 .unit(MeasureUnit::forIdentifier("millimeter-ofhg", status)) //
1899 .usage("default"), //
1900 Locale("en-US"), //
1901 1, //
1902 "0.019 psi");
1903
1904 assertFormatSingle(u"negative temperature conversion", //
1905 u"measure-unit/temperature-celsius unit-width-short usage/default", //
1906 u"measure-unit/temperature-celsius unit-width-short usage/default", //
1907 NumberFormatter::with() //
1908 .unit(MeasureUnit::forIdentifier("celsius", status)) //
1909 .usage("default") //
1910 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_SHORT), //
1911 Locale("en-US"), //
1912 -1, //
1913 u"30°F");
1914 }
1915
unitUsageErrorCodes()1916 void NumberFormatterApiTest::unitUsageErrorCodes() {
1917 IcuTestErrorCode status(*this, "unitUsageErrorCodes()");
1918 UnlocalizedNumberFormatter unloc_formatter;
1919
1920 unloc_formatter = NumberFormatter::forSkeleton(u"unit/foobar", status);
1921 // This gives an error, because foobar is an invalid unit:
1922 status.expectErrorAndReset(U_NUMBER_SKELETON_SYNTAX_ERROR);
1923
1924 unloc_formatter = NumberFormatter::forSkeleton(u"usage/foobar", status);
1925 // This does not give an error, because usage is not looked up yet.
1926 status.errIfFailureAndReset("Expected behaviour: no immediate error for invalid usage");
1927 unloc_formatter.locale("en-GB").formatInt(1, status);
1928 // Lacking a unit results in a failure. The skeleton is "incomplete", but we
1929 // support adding the unit via the fluent API, so it is not an error until
1930 // we build the formatting pipeline itself.
1931 status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
1932 // Adding the unit as part of the fluent chain leads to success.
1933 unloc_formatter.unit(MeasureUnit::getMeter()).locale("en-GB").formatInt(1, status);
1934 status.assertSuccess();
1935
1936 // Setting unit to the "base dimensionless unit" is like clearing unit.
1937 unloc_formatter = NumberFormatter::with().unit(MeasureUnit()).usage("default");
1938 // This does not give an error, because usage-vs-unit isn't resolved yet.
1939 status.errIfFailureAndReset("Expected behaviour: no immediate error for invalid unit");
1940 unloc_formatter.locale("en-GB").formatInt(1, status);
1941 status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
1942 }
1943
1944 // Tests for the "skeletons" field in unitPreferenceData, as well as precision
1945 // and notation overrides.
unitUsageSkeletons()1946 void NumberFormatterApiTest::unitUsageSkeletons() {
1947 IcuTestErrorCode status(*this, "unitUsageSkeletons()");
1948
1949 assertFormatSingle(
1950 u"Default >300m road preference skeletons round to 50m",
1951 u"usage/road measure-unit/length-meter",
1952 u"usage/road unit/meter",
1953 NumberFormatter::with().unit(METER).usage("road"),
1954 Locale("en-ZA"),
1955 321,
1956 u"300 m");
1957
1958 assertFormatSingle(
1959 u"Precision can be overridden: override takes precedence",
1960 u"usage/road measure-unit/length-meter @#",
1961 u"usage/road unit/meter @#",
1962 NumberFormatter::with()
1963 .unit(METER)
1964 .usage("road")
1965 .precision(Precision::maxSignificantDigits(2)),
1966 Locale("en-ZA"),
1967 321,
1968 u"320 m");
1969
1970 assertFormatSingle(
1971 u"Compact notation with Usage: bizarre, but possible (short)",
1972 u"compact-short usage/road measure-unit/length-meter",
1973 u"compact-short usage/road unit/meter",
1974 NumberFormatter::with()
1975 .unit(METER)
1976 .usage("road")
1977 .notation(Notation::compactShort()),
1978 Locale("en-ZA"),
1979 987654321,
1980 u"988K km");
1981
1982 assertFormatSingle(
1983 u"Compact notation with Usage: bizarre, but possible (short, precision override)",
1984 u"compact-short usage/road measure-unit/length-meter @#",
1985 u"compact-short usage/road unit/meter @#",
1986 NumberFormatter::with()
1987 .unit(METER)
1988 .usage("road")
1989 .notation(Notation::compactShort())
1990 .precision(Precision::maxSignificantDigits(2)),
1991 Locale("en-ZA"),
1992 987654321,
1993 u"990K km");
1994
1995 assertFormatSingle(
1996 u"Compact notation with Usage: unusual but possible (long)",
1997 u"compact-long usage/road measure-unit/length-meter @#",
1998 u"compact-long usage/road unit/meter @#",
1999 NumberFormatter::with()
2000 .unit(METER)
2001 .usage("road")
2002 .notation(Notation::compactLong())
2003 .precision(Precision::maxSignificantDigits(2)),
2004 Locale("en-ZA"),
2005 987654321,
2006 u"990 thousand km");
2007
2008 assertFormatSingle(
2009 u"Compact notation with Usage: unusual but possible (long, precision override)",
2010 u"compact-long usage/road measure-unit/length-meter @#",
2011 u"compact-long usage/road unit/meter @#",
2012 NumberFormatter::with()
2013 .unit(METER)
2014 .usage("road")
2015 .notation(Notation::compactLong())
2016 .precision(Precision::maxSignificantDigits(2)),
2017 Locale("en-ZA"),
2018 987654321,
2019 u"990 thousand km");
2020
2021 assertFormatSingle(
2022 u"Scientific notation, not recommended, requires precision override for road",
2023 u"scientific usage/road measure-unit/length-meter",
2024 u"scientific usage/road unit/meter",
2025 NumberFormatter::with().unit(METER).usage("road").notation(Notation::scientific()),
2026 Locale("en-ZA"),
2027 321.45,
2028 // Rounding to the nearest "50" is not exponent-adjusted in scientific notation:
2029 u"0E2 m");
2030
2031 assertFormatSingle(
2032 u"Scientific notation with Usage: possible when using a reasonable Precision",
2033 u"scientific usage/road measure-unit/length-meter @###",
2034 u"scientific usage/road unit/meter @###",
2035 NumberFormatter::with()
2036 .unit(METER)
2037 .usage("road")
2038 .notation(Notation::scientific())
2039 .precision(Precision::maxSignificantDigits(4)),
2040 Locale("en-ZA"),
2041 321.45, // 0.45 rounds down, 0.55 rounds up.
2042 u"3.214E2 m");
2043
2044 assertFormatSingle(
2045 u"Scientific notation with Usage: possible when using a reasonable Precision",
2046 u"scientific usage/default measure-unit/length-astronomical-unit unit-width-full-name",
2047 u"scientific usage/default unit/astronomical-unit unit-width-full-name",
2048 NumberFormatter::with()
2049 .unit(MeasureUnit::forIdentifier("astronomical-unit", status))
2050 .usage("default")
2051 .notation(Notation::scientific())
2052 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
2053 Locale("en-ZA"),
2054 1e20,
2055 u"1.5E28 kilometres");
2056
2057 status.assertSuccess();
2058 }
2059
unitCurrency()2060 void NumberFormatterApiTest::unitCurrency() {
2061 assertFormatDescending(
2062 u"Currency",
2063 u"currency/GBP",
2064 u"currency/GBP",
2065 NumberFormatter::with().unit(GBP),
2066 Locale::getEnglish(),
2067 u"£87,650.00",
2068 u"£8,765.00",
2069 u"£876.50",
2070 u"£87.65",
2071 u"£8.76",
2072 u"£0.88",
2073 u"£0.09",
2074 u"£0.01",
2075 u"£0.00");
2076
2077 assertFormatDescending(
2078 u"Currency ISO",
2079 u"currency/GBP unit-width-iso-code",
2080 u"currency/GBP unit-width-iso-code",
2081 NumberFormatter::with().unit(GBP).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE),
2082 Locale::getEnglish(),
2083 u"GBP 87,650.00",
2084 u"GBP 8,765.00",
2085 u"GBP 876.50",
2086 u"GBP 87.65",
2087 u"GBP 8.76",
2088 u"GBP 0.88",
2089 u"GBP 0.09",
2090 u"GBP 0.01",
2091 u"GBP 0.00");
2092
2093 assertFormatDescending(
2094 u"Currency Long Name",
2095 u"currency/GBP unit-width-full-name",
2096 u"currency/GBP unit-width-full-name",
2097 NumberFormatter::with().unit(GBP).unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
2098 Locale::getEnglish(),
2099 u"87,650.00 British pounds",
2100 u"8,765.00 British pounds",
2101 u"876.50 British pounds",
2102 u"87.65 British pounds",
2103 u"8.76 British pounds",
2104 u"0.88 British pounds",
2105 u"0.09 British pounds",
2106 u"0.01 British pounds",
2107 u"0.00 British pounds");
2108
2109 assertFormatDescending(
2110 u"Currency Hidden",
2111 u"currency/GBP unit-width-hidden",
2112 u"currency/GBP unit-width-hidden",
2113 NumberFormatter::with().unit(GBP).unitWidth(UNUM_UNIT_WIDTH_HIDDEN),
2114 Locale::getEnglish(),
2115 u"87,650.00",
2116 u"8,765.00",
2117 u"876.50",
2118 u"87.65",
2119 u"8.76",
2120 u"0.88",
2121 u"0.09",
2122 u"0.01",
2123 u"0.00");
2124
2125 // TODO: Implement Measure in C++
2126 // assertFormatSingleMeasure(
2127 // u"Currency with CurrencyAmount Input",
2128 // NumberFormatter::with(),
2129 // Locale::getEnglish(),
2130 // new CurrencyAmount(5.43, GBP),
2131 // u"£5.43");
2132
2133 // TODO: Enable this test when DecimalFormat wrapper is done.
2134 // assertFormatSingle(
2135 // u"Currency Long Name from Pattern Syntax", NumberFormatter.fromDecimalFormat(
2136 // PatternStringParser.parseToProperties("0 ¤¤¤"),
2137 // DecimalFormatSymbols.getInstance(Locale::getEnglish()),
2138 // null).unit(GBP), Locale::getEnglish(), 1234567.89, u"1234568 British pounds");
2139
2140 assertFormatSingle(
2141 u"Currency with Negative Sign",
2142 u"currency/GBP",
2143 u"currency/GBP",
2144 NumberFormatter::with().unit(GBP),
2145 Locale::getEnglish(),
2146 -9876543.21,
2147 u"-£9,876,543.21");
2148
2149 // The full currency symbol is not shown in NARROW format.
2150 // NOTE: This example is in the documentation.
2151 assertFormatSingle(
2152 u"Currency Difference between Narrow and Short (Narrow Version)",
2153 u"currency/USD unit-width-narrow",
2154 u"currency/USD unit-width-narrow",
2155 NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_NARROW),
2156 Locale("en-CA"),
2157 5.43,
2158 u"$5.43");
2159
2160 assertFormatSingle(
2161 u"Currency Difference between Narrow and Short (Short Version)",
2162 u"currency/USD unit-width-short",
2163 u"currency/USD unit-width-short",
2164 NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
2165 Locale("en-CA"),
2166 5.43,
2167 u"US$5.43");
2168
2169 assertFormatSingle(
2170 u"Currency Difference between Formal and Short (Formal Version)",
2171 u"currency/TWD unit-width-formal",
2172 u"currency/TWD unit-width-formal",
2173 NumberFormatter::with().unit(TWD).unitWidth(UNUM_UNIT_WIDTH_FORMAL),
2174 Locale("zh-TW"),
2175 5.43,
2176 u"NT$5.43");
2177
2178 assertFormatSingle(
2179 u"Currency Difference between Formal and Short (Short Version)",
2180 u"currency/TWD unit-width-short",
2181 u"currency/TWD unit-width-short",
2182 NumberFormatter::with().unit(TWD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
2183 Locale("zh-TW"),
2184 5.43,
2185 u"$5.43");
2186
2187 assertFormatSingle(
2188 u"Currency Difference between Variant and Short (Formal Version)",
2189 u"currency/TRY unit-width-variant",
2190 u"currency/TRY unit-width-variant",
2191 NumberFormatter::with().unit(TRY).unitWidth(UNUM_UNIT_WIDTH_VARIANT),
2192 Locale("tr-TR"),
2193 5.43,
2194 u"TL\u00A05,43");
2195
2196 assertFormatSingle(
2197 u"Currency Difference between Variant and Short (Short Version)",
2198 u"currency/TRY unit-width-short",
2199 u"currency/TRY unit-width-short",
2200 NumberFormatter::with().unit(TRY).unitWidth(UNUM_UNIT_WIDTH_SHORT),
2201 Locale("tr-TR"),
2202 5.43,
2203 u"₺5,43");
2204
2205 assertFormatSingle(
2206 u"Currency-dependent format (Control)",
2207 u"currency/USD unit-width-short",
2208 u"currency/USD unit-width-short",
2209 NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
2210 Locale("ca"),
2211 444444.55,
2212 u"444.444,55 USD");
2213
2214 assertFormatSingle(
2215 u"Currency-dependent format (Test)",
2216 u"currency/ESP unit-width-short",
2217 u"currency/ESP unit-width-short",
2218 NumberFormatter::with().unit(ESP).unitWidth(UNUM_UNIT_WIDTH_SHORT),
2219 Locale("ca"),
2220 444444.55,
2221 u"₧ 444.445");
2222
2223 assertFormatSingle(
2224 u"Currency-dependent symbols (Control)",
2225 u"currency/USD unit-width-short",
2226 u"currency/USD unit-width-short",
2227 NumberFormatter::with().unit(USD).unitWidth(UNUM_UNIT_WIDTH_SHORT),
2228 Locale("pt-PT"),
2229 444444.55,
2230 u"444 444,55 US$");
2231
2232 // NOTE: This is a bit of a hack on CLDR's part. They set the currency symbol to U+200B (zero-
2233 // width space), and they set the decimal separator to the $ symbol.
2234 assertFormatSingle(
2235 u"Currency-dependent symbols (Test Short)",
2236 u"currency/PTE unit-width-short",
2237 u"currency/PTE unit-width-short",
2238 NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_SHORT),
2239 Locale("pt-PT"),
2240 444444.55,
2241 u"444,444$55 \u200B");
2242
2243 assertFormatSingle(
2244 u"Currency-dependent symbols (Test Narrow)",
2245 u"currency/PTE unit-width-narrow",
2246 u"currency/PTE unit-width-narrow",
2247 NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_NARROW),
2248 Locale("pt-PT"),
2249 444444.55,
2250 u"444,444$55 \u200B");
2251
2252 assertFormatSingle(
2253 u"Currency-dependent symbols (Test ISO Code)",
2254 u"currency/PTE unit-width-iso-code",
2255 u"currency/PTE unit-width-iso-code",
2256 NumberFormatter::with().unit(PTE).unitWidth(UNUM_UNIT_WIDTH_ISO_CODE),
2257 Locale("pt-PT"),
2258 444444.55,
2259 u"444,444$55 PTE");
2260
2261 assertFormatSingle(
2262 u"Plural form depending on visible digits (ICU-20499)",
2263 u"currency/RON unit-width-full-name",
2264 u"currency/RON unit-width-full-name",
2265 NumberFormatter::with().unit(RON).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
2266 Locale("ro-RO"),
2267 24,
2268 u"24,00 lei românești");
2269
2270 assertFormatSingle(
2271 u"Currency spacing in suffix (ICU-20954)",
2272 u"currency/CNY",
2273 u"currency/CNY",
2274 NumberFormatter::with().unit(CNY),
2275 Locale("lu"),
2276 123.12,
2277 u"123,12 CN¥");
2278
2279 // de-CH has currency pattern "¤ #,##0.00;¤-#,##0.00"
2280 assertFormatSingle(
2281 u"Sign position on negative number with pattern spacing",
2282 u"currency/RON",
2283 u"currency/RON",
2284 NumberFormatter::with().unit(RON),
2285 Locale("de-CH"),
2286 -123.12,
2287 u"RON-123.12");
2288
2289 // TODO(ICU-21420): Move the sign to the inside of the number
2290 assertFormatSingle(
2291 u"Sign position on negative number with currency spacing",
2292 u"currency/RON",
2293 u"currency/RON",
2294 NumberFormatter::with().unit(RON),
2295 Locale("en"),
2296 -123.12,
2297 u"-RON 123.12");
2298 }
2299
runUnitInflectionsTestCases(UnlocalizedNumberFormatter unf,UnicodeString skeleton,const UnitInflectionTestCase * cases,int32_t numCases,IcuTestErrorCode & status)2300 void NumberFormatterApiTest::runUnitInflectionsTestCases(UnlocalizedNumberFormatter unf,
2301 UnicodeString skeleton,
2302 const UnitInflectionTestCase *cases,
2303 int32_t numCases,
2304 IcuTestErrorCode &status) {
2305 for (int32_t i = 0; i < numCases; i++) {
2306 UnitInflectionTestCase t = cases[i];
2307 status.assertSuccess();
2308 MeasureUnit mu = MeasureUnit::forIdentifier(t.unitIdentifier, status);
2309 if (status.errIfFailureAndReset("MeasureUnit::forIdentifier(\"%s\", ...) failed",
2310 t.unitIdentifier)) {
2311 continue;
2312 };
2313 UnicodeString skelString = UnicodeString("unit/") + t.unitIdentifier + u" " + skeleton;
2314 const char16_t *skel;
2315 if (t.unitDisplayCase == nullptr || t.unitDisplayCase[0] == 0) {
2316 unf = unf.unit(mu).unitDisplayCase("");
2317 skel = skelString.getTerminatedBuffer();
2318 } else {
2319 unf = unf.unit(mu).unitDisplayCase(t.unitDisplayCase);
2320 // No skeleton support for unitDisplayCase yet.
2321 skel = nullptr;
2322 }
2323 assertFormatSingle((UnicodeString("Unit: \"") + t.unitIdentifier + ("\", \"") + skeleton +
2324 u"\", locale=\"" + t.locale + u"\", case=\"" +
2325 (t.unitDisplayCase ? t.unitDisplayCase : "") + u"\", value=" + t.value)
2326 .getTerminatedBuffer(),
2327 skel, skel, unf, Locale(t.locale), t.value, t.expected);
2328 status.assertSuccess();
2329 }
2330
2331 for (int32_t i = 0; i < numCases; i++) {
2332 UnitInflectionTestCase t = cases[i];
2333 status.assertSuccess();
2334 MeasureUnit mu = MeasureUnit::forIdentifier(t.unitIdentifier, status);
2335 if (status.errIfFailureAndReset("MeasureUnit::forIdentifier(\"%s\", ...) failed",
2336 t.unitIdentifier)) {
2337 continue;
2338 };
2339
2340 UnicodeString skelString = UnicodeString("unit/") + t.unitIdentifier + u" " + skeleton;
2341 const char16_t *skel;
2342 auto displayOptionsBuilder = DisplayOptions::builder();
2343 if (t.unitDisplayCase == nullptr || t.unitDisplayCase[0] == 0) {
2344 auto displayoptions = displayOptionsBuilder.build();
2345 unf = unf.unit(mu).displayOptions(displayoptions);
2346 skel = skelString.getTerminatedBuffer();
2347 } else {
2348 auto displayoptions =
2349 displayOptionsBuilder
2350 .setGrammaticalCase(udispopt_fromGrammaticalCaseIdentifier(t.unitDisplayCase))
2351 .build();
2352 unf = unf.unit(mu).displayOptions(displayoptions);
2353 // No skeleton support for unitDisplayCase yet.
2354 skel = nullptr;
2355 }
2356 assertFormatSingle((UnicodeString("Unit: \"") + t.unitIdentifier + ("\", \"") + skeleton +
2357 u"\", locale=\"" + t.locale + u"\", case=\"" +
2358 (t.unitDisplayCase ? t.unitDisplayCase : "") + u"\", value=" + t.value)
2359 .getTerminatedBuffer(),
2360 skel, skel, unf, Locale(t.locale), t.value, t.expected);
2361 status.assertSuccess();
2362 }
2363 }
2364
unitInflections()2365 void NumberFormatterApiTest::unitInflections() {
2366 IcuTestErrorCode status(*this, "unitInflections");
2367
2368 UnlocalizedNumberFormatter unf;
2369 const char16_t *skeleton;
2370 {
2371 // Simple inflected form test - test case based on the example in CLDR's
2372 // grammaticalFeatures.xml
2373 unf = NumberFormatter::with().unitWidth(UNUM_UNIT_WIDTH_FULL_NAME);
2374 skeleton = u"unit-width-full-name";
2375 const UnitInflectionTestCase percentCases[] = {
2376 {"percent", "ru", nullptr, 10, u"10 процентов"}, // many
2377 {"percent", "ru", "genitive", 10, u"10 процентов"}, // many
2378 {"percent", "ru", nullptr, 33, u"33 процента"}, // few
2379 {"percent", "ru", "genitive", 33, u"33 процентов"}, // few
2380 {"percent", "ru", nullptr, 1, u"1 процент"}, // one
2381 {"percent", "ru", "genitive", 1, u"1 процента"}, // one
2382 };
2383 runUnitInflectionsTestCases(unf, skeleton, percentCases, UPRV_LENGTHOF(percentCases), status);
2384 }
2385 {
2386 // General testing of inflection rules
2387 unf = NumberFormatter::with().unitWidth(UNUM_UNIT_WIDTH_FULL_NAME);
2388 skeleton = u"unit-width-full-name";
2389 const UnitInflectionTestCase testCases[] = {
2390 // Check up on the basic values that the compound patterns below are
2391 // derived from:
2392 {"meter", "de", nullptr, 1, u"1 Meter"},
2393 {"meter", "de", "genitive", 1, u"1 Meters"},
2394 {"meter", "de", nullptr, 2, u"2 Meter"},
2395 {"meter", "de", "dative", 2, u"2 Metern"},
2396 {"mile", "de", nullptr, 1, u"1 Meile"},
2397 {"mile", "de", nullptr, 2, u"2 Meilen"},
2398 {"day", "de", nullptr, 1, u"1 Tag"},
2399 {"day", "de", "genitive", 1, u"1 Tages"},
2400 {"day", "de", nullptr, 2, u"2 Tage"},
2401 {"day", "de", "dative", 2, u"2 Tagen"},
2402 {"decade", "de", nullptr, 1, u"1\u00A0Jahrzehnt"},
2403 {"decade", "de", nullptr, 2, u"2\u00A0Jahrzehnte"},
2404
2405 // Testing de "per" rules:
2406 // <deriveComponent feature="case" structure="per" value0="compound" value1="accusative"/>
2407 // <deriveComponent feature="plural" structure="per" value0="compound" value1="one"/>
2408 // per-patterns use accusative, but since the accusative form
2409 // matches the nominative form, we're not effectively testing value1
2410 // in the "case & per" rule above.
2411
2412 // We have a perUnitPattern for "day" in de, so "per" rules are not
2413 // applied for these:
2414 {"meter-per-day", "de", nullptr, 1, u"1 Meter pro Tag"},
2415 {"meter-per-day", "de", "genitive", 1, u"1 Meters pro Tag"},
2416 {"meter-per-day", "de", nullptr, 2, u"2 Meter pro Tag"},
2417 {"meter-per-day", "de", "dative", 2, u"2 Metern pro Tag"},
2418
2419 // testing code path that falls back to "root" grammaticalFeatures
2420 // but does not inflect:
2421 {"meter-per-day", "af", nullptr, 1, u"1 meter per dag"},
2422 {"meter-per-day", "af", "dative", 1, u"1 meter per dag"},
2423
2424 // Decade does not have a perUnitPattern at this time (CLDR 39 / ICU
2425 // 69), so we can use it to test for selection of correct plural form.
2426 // - Note: fragile test cases, these cases will break when
2427 // whitespace is more consistently applied.
2428 {"parsec-per-decade", "de", nullptr, 1, u"1\u00A0Parsec pro Jahrzehnt"},
2429 {"parsec-per-decade", "de", "genitive", 1, u"1 Parsec pro Jahrzehnt"},
2430 {"parsec-per-decade", "de", nullptr, 2, u"2\u00A0Parsec pro Jahrzehnt"},
2431 {"parsec-per-decade", "de", "dative", 2, u"2 Parsec pro Jahrzehnt"},
2432
2433 // Testing de "times", "power" and "prefix" rules:
2434 //
2435 // <deriveComponent feature="plural" structure="times" value0="one" value1="compound"/>
2436 // <deriveComponent feature="case" structure="times" value0="nominative" value1="compound"/>
2437 //
2438 // <deriveComponent feature="plural" structure="prefix" value0="one" value1="compound"/>
2439 // <deriveComponent feature="case" structure="prefix" value0="nominative" value1="compound"/>
2440 //
2441 // Prefixes in German don't change with plural or case, so these
2442 // tests can't test value0 of the following two rules:
2443 // <deriveComponent feature="plural" structure="power" value0="one" value1="compound"/>
2444 // <deriveComponent feature="case" structure="power" value0="nominative" value1="compound"/>
2445 {"square-decimeter-dekameter", "de", nullptr, 1, u"1 Dekameter⋅Quadratdezimeter"},
2446 {"square-decimeter-dekameter", "de", "genitive", 1, u"1 Dekameter⋅Quadratdezimeter"},
2447 {"square-decimeter-dekameter", "de", nullptr, 2, u"2 Dekameter⋅Quadratdezimeter"},
2448 {"square-decimeter-dekameter", "de", "dative", 2, u"2 Dekameter⋅Quadratdezimeter"},
2449 // Feminine "Meile" better demonstrates singular-vs-plural form:
2450 {"cubic-mile-dekamile", "de", nullptr, 1, u"1 Dekameile⋅Kubikmeile"},
2451 {"cubic-mile-dekamile", "de", nullptr, 2, u"2 Dekameile⋅Kubikmeilen"},
2452
2453 // French handles plural "times" and "power" structures differently:
2454 // plural form impacts all "numerator" units (denominator remains
2455 // singular like German), and "pow2" prefixes have different forms
2456 // <deriveComponent feature="plural" structure="times" value0="compound" value1="compound"/>
2457 // <deriveComponent feature="plural" structure="power" value0="compound" value1="compound"/>
2458 {"square-decimeter-square-second", "fr", nullptr, 1, u"1\u00A0décimètre carré-seconde carrée"},
2459 {"square-decimeter-square-second", "fr", nullptr, 2, u"2\u00A0décimètres carrés-secondes carrées"},
2460 };
2461 runUnitInflectionsTestCases(unf, skeleton, testCases, UPRV_LENGTHOF(testCases), status);
2462 }
2463 {
2464 // Testing inflection of mixed units:
2465 unf = NumberFormatter::with().unitWidth(UNUM_UNIT_WIDTH_FULL_NAME);
2466 skeleton = u"unit-width-full-name";
2467 const UnitInflectionTestCase testCases[] = {
2468 {"meter", "de", nullptr, 1, u"1 Meter"},
2469 {"meter", "de", "genitive", 1, u"1 Meters"},
2470 {"meter", "de", "dative", 2, u"2 Metern"},
2471 {"centimeter", "de", nullptr, 1, u"1 Zentimeter"},
2472 {"centimeter", "de", "genitive", 1, u"1 Zentimeters"},
2473 {"centimeter", "de", "dative", 10, u"10 Zentimetern"},
2474 // TODO(CLDR-14582): check that these inflections are correct, and
2475 // whether CLDR needs any rules for them (presumably CLDR spec
2476 // should mention it, if it's a consistent rule):
2477 {"meter-and-centimeter", "de", nullptr, 1.01, u"1 Meter, 1 Zentimeter"},
2478 {"meter-and-centimeter", "de", "genitive", 1.01, u"1 Meters, 1 Zentimeters"},
2479 {"meter-and-centimeter", "de", "genitive", 1.1, u"1 Meters, 10 Zentimeter"},
2480 {"meter-and-centimeter", "de", "dative", 1.1, u"1 Meter, 10 Zentimetern"},
2481 {"meter-and-centimeter", "de", "dative", 2.1, u"2 Metern, 10 Zentimetern"},
2482 };
2483 runUnitInflectionsTestCases(unf, skeleton, testCases, UPRV_LENGTHOF(testCases),
2484 status);
2485 }
2486 // TODO: add a usage case that selects between preferences with different
2487 // genders (e.g. year, month, day, hour).
2488 // TODO: look at "↑↑↑" cases: check that inheritance is done right.
2489 }
2490
unitNounClass()2491 void NumberFormatterApiTest::unitNounClass() {
2492 IcuTestErrorCode status(*this, "unitNounClass");
2493 const struct TestCase {
2494 const char *locale;
2495 const char *unitIdentifier;
2496 const UDisplayOptionsNounClass expectedNounClass;
2497 } cases[] = {
2498 {"de", "inch", UDISPOPT_NOUN_CLASS_MASCULINE},
2499 {"de", "yard", UDISPOPT_NOUN_CLASS_NEUTER},
2500 {"de", "meter", UDISPOPT_NOUN_CLASS_MASCULINE},
2501 {"de", "liter", UDISPOPT_NOUN_CLASS_MASCULINE},
2502 {"de", "second", UDISPOPT_NOUN_CLASS_FEMININE},
2503 {"de", "minute", UDISPOPT_NOUN_CLASS_FEMININE},
2504 {"de", "hour", UDISPOPT_NOUN_CLASS_FEMININE},
2505 {"de", "day", UDISPOPT_NOUN_CLASS_MASCULINE},
2506 {"de", "year", UDISPOPT_NOUN_CLASS_NEUTER},
2507 {"de", "gram", UDISPOPT_NOUN_CLASS_NEUTER},
2508 {"de", "watt", UDISPOPT_NOUN_CLASS_NEUTER},
2509 {"de", "bit", UDISPOPT_NOUN_CLASS_NEUTER},
2510 {"de", "byte", UDISPOPT_NOUN_CLASS_NEUTER},
2511
2512 {"fr", "inch", UDISPOPT_NOUN_CLASS_MASCULINE},
2513 {"fr", "yard", UDISPOPT_NOUN_CLASS_MASCULINE},
2514 {"fr", "meter", UDISPOPT_NOUN_CLASS_MASCULINE},
2515 {"fr", "liter", UDISPOPT_NOUN_CLASS_MASCULINE},
2516 {"fr", "second", UDISPOPT_NOUN_CLASS_FEMININE},
2517 {"fr", "minute", UDISPOPT_NOUN_CLASS_FEMININE},
2518 {"fr", "hour", UDISPOPT_NOUN_CLASS_FEMININE},
2519 {"fr", "day", UDISPOPT_NOUN_CLASS_MASCULINE},
2520 {"fr", "year", UDISPOPT_NOUN_CLASS_MASCULINE},
2521 {"fr", "gram", UDISPOPT_NOUN_CLASS_MASCULINE},
2522
2523 // grammaticalFeatures deriveCompound "per" rule takes the gender of the
2524 // numerator unit:
2525 {"de", "meter-per-hour", UDISPOPT_NOUN_CLASS_MASCULINE},
2526 {"fr", "meter-per-hour", UDISPOPT_NOUN_CLASS_MASCULINE},
2527 {"af", "meter-per-hour",
2528 UDISPOPT_NOUN_CLASS_UNDEFINED}, // ungendered language
2529
2530 // French "times" takes gender from first value, German takes the
2531 // second. Prefix and power does not have impact on gender for these
2532 // languages:
2533 {"de", "square-decimeter-square-second", UDISPOPT_NOUN_CLASS_FEMININE},
2534 {"fr", "square-decimeter-square-second",
2535 UDISPOPT_NOUN_CLASS_MASCULINE},
2536
2537 // TODO(icu-units#149): percent and permille bypasses LongNameHandler
2538 // when unitWidth is not FULL_NAME:
2539 // // Gender of per-second might be that of percent? TODO(icu-units#28)
2540 // {"de", "percent", UNounClass::UNOUN_CLASS_NEUTER},
2541 // {"fr", "percent", UNounClass::UNOUN_CLASS_MASCULINE},
2542
2543 // Built-in units whose simple units lack gender in the CLDR data file
2544 {"de", "kilopascal", UDISPOPT_NOUN_CLASS_NEUTER},
2545 {"fr", "kilopascal", UDISPOPT_NOUN_CLASS_MASCULINE},
2546 // {"de", "pascal", UNounClass::UNOUN_CLASS_UNDEFINED},
2547 // {"fr", "pascal", UNounClass::UNOUN_CLASS_UNDEFINED},
2548
2549 // Built-in units that lack gender in the CLDR data file
2550 // {"de", "revolution", UNounClass::UNOUN_CLASS_UNDEFINED},
2551 // {"de", "radian", UNounClass::UNOUN_CLASS_UNDEFINED},
2552 // {"de", "arc-minute", UNounClass::UNOUN_CLASS_UNDEFINED},
2553 // {"de", "arc-second", UNounClass::UNOUN_CLASS_UNDEFINED},
2554 {"de", "square-yard", UDISPOPT_NOUN_CLASS_NEUTER}, // POWER
2555 {"de", "square-inch", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2556 // {"de", "dunam", UNounClass::UNOUN_CLASS_ UNDEFINED},
2557 // {"de", "karat", UNounClass::UNOUN_CLASS_ UNDEFINED},
2558 // {"de", "milligram-ofglucose-per-deciliter", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND,
2559 // ofglucose
2560 // {"de", "millimole-per-liter", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND,
2561 // mole
2562 // {"de", "permillion", UNounClass::UNOUN_CLASS_UNDEFINED},
2563 // {"de", "permille", UNounClass::UNOUN_CLASS_UNDEFINED},
2564 // {"de", "permyriad", UNounClass::UNOUN_CLASS_UNDEFINED},
2565 // {"de", "mole", UNounClass::UNOUN_CLASS_UNDEFINED},
2566 {"de", "liter-per-kilometer",
2567 UDISPOPT_NOUN_CLASS_MASCULINE}, // COMPOUND
2568 {"de", "petabyte", UDISPOPT_NOUN_CLASS_NEUTER}, // PREFIX
2569 {"de", "terabit", UDISPOPT_NOUN_CLASS_NEUTER}, // PREFIX
2570 // {"de", "century", UNounClass::UNOUN_CLASS_UNDEFINED},
2571 // {"de", "decade", UNounClass::UNOUN_CLASS_UNDEFINED},
2572 {"de", "millisecond", UDISPOPT_NOUN_CLASS_FEMININE}, // PREFIX
2573 {"de", "microsecond", UDISPOPT_NOUN_CLASS_FEMININE}, // PREFIX
2574 {"de", "nanosecond", UDISPOPT_NOUN_CLASS_FEMININE}, // PREFIX
2575 // {"de", "ampere", UNounClass::UNOUN_CLASS_UNDEFINED},
2576 // {"de", "milliampere", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, ampere
2577 // {"de", "ohm", UNounClass::UNOUN_CLASS_UNDEFINED},
2578 // {"de", "calorie", UNounClass::UNOUN_CLASS_UNDEFINED},
2579 // {"de", "kilojoule", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, joule
2580 // {"de", "joule", UNounClass::UNOUN_CLASS_ UNDEFINED},
2581 {"de", "kilowatt-hour", UDISPOPT_NOUN_CLASS_FEMININE}, // COMPOUND
2582 // {"de", "electronvolt", UNounClass::UNOUN_CLASS_UNDEFINED},
2583 // {"de", "british-thermal-unit", UNounClass::UNOUN_CLASS_UNDEFINED},
2584 // {"de", "therm-us", UNounClass::UNOUN_CLASS_UNDEFINED},
2585 // {"de", "pound-force", UNounClass::UNOUN_CLASS_UNDEFINED},
2586 // {"de", "newton", UNounClass::UNOUN_CLASS_UNDEFINED},
2587 // {"de", "gigahertz", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, hertz
2588 // {"de", "megahertz", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, hertz
2589 // {"de", "kilohertz", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, hertz
2590 // {"de", "hertz", UNounClass::UNOUN_CLASS_ UNDEFINED},
2591 // {"de", "em", UNounClass::UNOUN_CLASS_UNDEFINED},
2592 // {"de", "pixel", UNounClass::UNOUN_CLASS_ UNDEFINED},
2593 // {"de", "megapixel", UNounClass::UNOUN_CLASS_UNDEFINED},
2594 // {"de", "pixel-per-centimeter", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND, pixel
2595 // {"de", "pixel-per-inch", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND, pixel
2596 // {"de", "dot-per-centimeter", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND, dot
2597 // {"de", "dot-per-inch", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND, dot
2598 // {"de", "dot", UNounClass::UNOUN_CLASS_UNDEFINED},
2599 // {"de", "earth-radius", UNounClass::UNOUN_CLASS_UNDEFINED},
2600 {"de", "decimeter", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2601 {"de", "micrometer", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2602 {"de", "nanometer", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2603 // {"de", "light-year", UNounClass::UNOUN_CLASS_UNDEFINED},
2604 // {"de", "astronomical-unit", UNounClass::UNOUN_CLASS_UNDEFINED},
2605 // {"de", "furlong", UNounClass::UNOUN_CLASS_UNDEFINED},
2606 // {"de", "fathom", UNounClass::UNOUN_CLASS_UNDEFINED},
2607 // {"de", "nautical-mile", UNounClass::UNOUN_CLASS_ UNDEFINED},
2608 // {"de", "mile-scandinavian", UNounClass::UNOUN_CLASS_UNDEFINED},
2609 // {"de", "point", UNounClass::UNOUN_CLASS_ UNDEFINED},
2610 // {"de", "lux", UNounClass::UNOUN_CLASS_UNDEFINED},
2611 // {"de", "candela", UNounClass::UNOUN_CLASS_UNDEFINED},
2612 // {"de", "lumen", UNounClass::UNOUN_CLASS_ UNDEFINED},
2613 // {"de", "tonne", UNounClass::UNOUN_CLASS_UNDEFINED},
2614 // {"de", "microgram", UNounClass::UNOUN_CLASS_NEUTER}, // PREFIX
2615 // {"de", "ton", UNounClass::UNOUN_CLASS_UNDEFINED},
2616 // {"de", "stone", UNounClass::UNOUN_CLASS_ UNDEFINED},
2617 // {"de", "ounce-troy", UNounClass::UNOUN_CLASS_UNDEFINED},
2618 // {"de", "carat", UNounClass::UNOUN_CLASS_ UNDEFINED},
2619 {"de", "gigawatt", UDISPOPT_NOUN_CLASS_NEUTER}, // PREFIX
2620 {"de", "milliwatt", UDISPOPT_NOUN_CLASS_NEUTER}, // PREFIX
2621 // {"de", "horsepower", UNounClass::UNOUN_CLASS_UNDEFINED},
2622 // {"de", "millimeter-ofhg", UNounClass::UNOUN_CLASS_UNDEFINED},
2623 // {"de", "pound-force-per-square-inch", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND,
2624 // pound-force
2625 // {"de", "inch-ofhg", UNounClass::UNOUN_CLASS_UNDEFINED},
2626 // {"de", "bar", UNounClass::UNOUN_CLASS_UNDEFINED},
2627 // {"de", "millibar", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, bar
2628 // {"de", "atmosphere", UNounClass::UNOUN_CLASS_UNDEFINED},
2629 // {"de", "pascal", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, kilopascal? neuter?
2630 // {"de", "hectopascal", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, pascal, neuter?
2631 // {"de", "megapascal", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, pascal, neuter?
2632 // {"de", "knot", UNounClass::UNOUN_CLASS_UNDEFINED},
2633 {"de", "pound-force-foot", UDISPOPT_NOUN_CLASS_MASCULINE}, // COMPOUND
2634 {"de", "newton-meter", UDISPOPT_NOUN_CLASS_MASCULINE}, // COMPOUND
2635 {"de", "cubic-kilometer", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2636 {"de", "cubic-yard", UDISPOPT_NOUN_CLASS_NEUTER}, // POWER
2637 {"de", "cubic-inch", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2638 {"de", "megaliter", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2639 {"de", "hectoliter", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2640 // {"de", "pint-metric", UNounClass::UNOUN_CLASS_UNDEFINED},
2641 // {"de", "cup-metric", UNounClass::UNOUN_CLASS_UNDEFINED},
2642 {"de", "acre-foot", UDISPOPT_NOUN_CLASS_MASCULINE}, // COMPOUND
2643 // {"de", "bushel", UNounClass::UNOUN_CLASS_UNDEFINED},
2644 // {"de", "barrel", UNounClass::UNOUN_CLASS_UNDEFINED},
2645 // Units missing gender in German also misses gender in French:
2646 // {"fr", "revolution", UNounClass::UNOUN_CLASS_UNDEFINED},
2647 // {"fr", "radian", UNounClass::UNOUN_CLASS_UNDEFINED},
2648 // {"fr", "arc-minute", UNounClass::UNOUN_CLASS_UNDEFINED},
2649 // {"fr", "arc-second", UNounClass::UNOUN_CLASS_UNDEFINED},
2650 {"fr", "square-yard", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2651 {"fr", "square-inch", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2652 // {"fr", "dunam", UNounClass::UNOUN_CLASS_ UNDEFINED},
2653 // {"fr", "karat", UNounClass::UNOUN_CLASS_ UNDEFINED},
2654 {"fr", "milligram-ofglucose-per-deciliter",
2655 UDISPOPT_NOUN_CLASS_MASCULINE}, // COMPOUND
2656 // {"fr", "millimole-per-liter", UNounClass::UNOUN_CLASS_UNDEFINED}, //
2657 // COMPOUND, mole
2658 // {"fr", "permillion", UNounClass::UNOUN_CLASS_UNDEFINED},
2659 // {"fr", "permille", UNounClass::UNOUN_CLASS_UNDEFINED},
2660 // {"fr", "permyriad", UNounClass::UNOUN_CLASS_UNDEFINED},
2661 // {"fr", "mole", UNounClass::UNOUN_CLASS_UNDEFINED},
2662 {"fr", "liter-per-kilometer",
2663 UDISPOPT_NOUN_CLASS_MASCULINE}, // COMPOUND
2664 // {"fr", "petabyte", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX
2665 // {"fr", "terabit", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX
2666 // {"fr", "century", UNounClass::UNOUN_CLASS_UNDEFINED},
2667 // {"fr", "decade", UNounClass::UNOUN_CLASS_UNDEFINED},
2668 {"fr", "millisecond", UDISPOPT_NOUN_CLASS_FEMININE}, // PREFIX
2669 {"fr", "microsecond", UDISPOPT_NOUN_CLASS_FEMININE}, // PREFIX
2670 {"fr", "nanosecond", UDISPOPT_NOUN_CLASS_FEMININE}, // PREFIX
2671 // {"fr", "ampere", UNounClass::UNOUN_CLASS_UNDEFINED},
2672 // {"fr", "milliampere", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, ampere
2673 // {"fr", "ohm", UNounClass::UNOUN_CLASS_UNDEFINED},
2674 // {"fr", "calorie", UNounClass::UNOUN_CLASS_UNDEFINED},
2675 // {"fr", "kilojoule", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, joule
2676 // {"fr", "joule", UNounClass::UNOUN_CLASS_UNDEFINED},
2677 // {"fr", "kilowatt-hour", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND
2678 // {"fr", "electronvolt", UNounClass::UNOUN_CLASS_UNDEFINED},
2679 // {"fr", "british-thermal-unit", UNounClass::UNOUN_CLASS_UNDEFINED},
2680 // {"fr", "therm-us", UNounClass::UNOUN_CLASS_UNDEFINED},
2681 // {"fr", "pound-force", UNounClass::UNOUN_CLASS_UNDEFINED},
2682 // {"fr", "newton", UNounClass::UNOUN_CLASS_UNDEFINED},
2683 // {"fr", "gigahertz", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, hertz
2684 // {"fr", "megahertz", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, hertz
2685 // {"fr", "kilohertz", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, hertz
2686 // {"fr", "hertz", UNounClass::UNOUN_CLASS_ UNDEFINED},
2687 // {"fr", "em", UNounClass::UNOUN_CLASS_UNDEFINED},
2688 // {"fr", "pixel", UNounClass::UNOUN_CLASS_ UNDEFINED},
2689 // {"fr", "megapixel", UNounClass::UNOUN_CLASS_UNDEFINED},
2690 // {"fr", "pixel-per-centimeter", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND, pixel
2691 // {"fr", "pixel-per-inch", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND, pixel
2692 // {"fr", "dot-per-centimeter", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND, dot
2693 // {"fr", "dot-per-inch", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND, dot
2694 // {"fr", "dot", UNounClass::UNOUN_CLASS_UNDEFINED},
2695 // {"fr", "earth-radius", UNounClass::UNOUN_CLASS_UNDEFINED},
2696 {"fr", "decimeter", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2697 {"fr", "micrometer", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2698 {"fr", "nanometer", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2699 // {"fr", "light-year", UNounClass::UNOUN_CLASS_UNDEFINED},
2700 // {"fr", "astronomical-unit", UNounClass::UNOUN_CLASS_UNDEFINED},
2701 // {"fr", "furlong", UNounClass::UNOUN_CLASS_UNDEFINED},
2702 // {"fr", "fathom", UNounClass::UNOUN_CLASS_UNDEFINED},
2703 // {"fr", "nautical-mile", UNounClass::UNOUN_CLASS_ UNDEFINED},
2704 // {"fr", "mile-scandinavian", UNounClass::UNOUN_CLASS_UNDEFINED},
2705 // {"fr", "point", UNounClass::UNOUN_CLASS_ UNDEFINED},
2706 // {"fr", "lux", UNounClass::UNOUN_CLASS_UNDEFINED},
2707 // {"fr", "candela", UNounClass::UNOUN_CLASS_UNDEFINED},
2708 // {"fr", "lumen", UNounClass::UNOUN_CLASS_ UNDEFINED},
2709 // {"fr", "tonne", UNounClass::UNOUN_CLASS_UNDEFINED},
2710 // {"fr", "microgram", UNounClass::UNOUN_CLASS_MASCULINE}, // PREFIX
2711 // {"fr", "ton", UNounClass::UNOUN_CLASS_UNDEFINED},
2712 // {"fr", "stone", UNounClass::UNOUN_CLASS_ UNDEFINED},
2713 // {"fr", "ounce-troy", UNounClass::UNOUN_CLASS_UNDEFINED},
2714 // {"fr", "carat", UNounClass::UNOUN_CLASS_ UNDEFINED},
2715 // {"fr", "gigawatt", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX
2716 // {"fr", "milliwatt", UNounClass::UNOUN_CLASS_UNDEFINED},
2717 // {"fr", "horsepower", UNounClass::UNOUN_CLASS_UNDEFINED},
2718 {"fr", "millimeter-ofhg", UDISPOPT_NOUN_CLASS_MASCULINE},
2719 // {"fr", "pound-force-per-square-inch", UNounClass::UNOUN_CLASS_UNDEFINED}, // COMPOUND,
2720 // pound-force
2721 {"fr", "inch-ofhg", UDISPOPT_NOUN_CLASS_MASCULINE},
2722 // {"fr", "bar", UNounClass::UNOUN_CLASS_UNDEFINED},
2723 // {"fr", "millibar", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, bar
2724 // {"fr", "atmosphere", UNounClass::UNOUN_CLASS_UNDEFINED},
2725 // {"fr", "pascal", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, kilopascal?
2726 // {"fr", "hectopascal", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, pascal
2727 // {"fr", "megapascal", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, pascal
2728 // {"fr", "knot", UNounClass::UNOUN_CLASS_UNDEFINED},
2729 // {"fr", "pound-force-foot", UNounClass::UNOUN_CLASS_UNDEFINED},
2730 // {"fr", "newton-meter", UNounClass::UNOUN_CLASS_UNDEFINED},
2731 {"fr", "cubic-kilometer", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2732 {"fr", "cubic-yard", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2733 {"fr", "cubic-inch", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2734 {"fr", "megaliter", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2735 {"fr", "hectoliter", UDISPOPT_NOUN_CLASS_MASCULINE}, // PREFIX
2736 // {"fr", "pint-metric", UNounClass::UNOUN_CLASS_UNDEFINED},
2737 // {"fr", "cup-metric", UNounClass::UNOUN_CLASS_UNDEFINED},
2738 {"fr", "acre-foot", UDISPOPT_NOUN_CLASS_FEMININE}, // COMPOUND
2739 // {"fr", "bushel", UNounClass::UNOUN_CLASS_UNDEFINED},
2740 // {"fr", "barrel", UNounClass::UNOUN_CLASS_UNDEFINED},
2741 // Some more French units missing gender:
2742 // {"fr", "degree", UNounClass::UNOUN_CLASS_UNDEFINED},
2743 {"fr", "square-meter", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2744 // {"fr", "terabyte", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, byte
2745 // {"fr", "gigabyte", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, byte
2746 // {"fr", "gigabit", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, bit
2747 // {"fr", "megabyte", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, byte
2748 // {"fr", "megabit", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, bit
2749 // {"fr", "kilobyte", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, byte
2750 // {"fr", "kilobit", UNounClass::UNOUN_CLASS_UNDEFINED}, // PREFIX, bit
2751 // {"fr", "byte", UNounClass::UNOUN_CLASS_UNDEFINED},
2752 // {"fr", "bit", UNounClass::UNOUN_CLASS_UNDEFINED},
2753 // {"fr", "volt", UNounClass::UNOUN_CLASS_UNDEFINED},
2754 // {"fr", "watt", UNounClass::UNOUN_CLASS_UNDEFINED},
2755 {"fr", "cubic-meter", UDISPOPT_NOUN_CLASS_MASCULINE}, // POWER
2756
2757 // gender-lacking builtins within compound units
2758 {"de", "newton-meter-per-second", UDISPOPT_NOUN_CLASS_MASCULINE},
2759
2760 // TODO(ICU-21494): determine whether list genders behave as follows,
2761 // and implement proper getListGender support (covering more than just
2762 // two genders):
2763 // // gender rule for lists of people: de "neutral", fr "maleTaints"
2764 // {"de", "day-and-hour-and-minute", UNounClass::UNOUN_CLASS_NEUTER},
2765 // {"de", "hour-and-minute", UNounClass::UNOUN_CLASS_FEMININE},
2766 // {"fr", "day-and-hour-and-minute", UNounClass::UNOUN_CLASS_MASCULINE},
2767 // {"fr", "hour-and-minute", UNounClass::UNOUN_CLASS_FEMININE},
2768 };
2769
2770 LocalizedNumberFormatter formatter;
2771 FormattedNumber fn;
2772 for (const TestCase &t : cases) {
2773 formatter = NumberFormatter::with()
2774 .unit(MeasureUnit::forIdentifier(t.unitIdentifier, status))
2775 .locale(Locale(t.locale));
2776 fn = formatter.formatDouble(1.1, status);
2777 assertEquals(UnicodeString("Testing NounClass with default width, unit: ") + t.unitIdentifier +
2778 ", locale: " + t.locale,
2779 t.expectedNounClass, fn.getNounClass(status));
2780 status.assertSuccess();
2781
2782 formatter = NumberFormatter::with()
2783 .unit(MeasureUnit::forIdentifier(t.unitIdentifier, status))
2784 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
2785 .locale(Locale(t.locale));
2786 fn = formatter.formatDouble(1.1, status);
2787 assertEquals(UnicodeString("Testing NounClass with UNUM_UNIT_WIDTH_FULL_NAME, unit: ") +
2788 t.unitIdentifier + ", locale: " + t.locale,
2789 t.expectedNounClass, fn.getNounClass(status));
2790 status.assertSuccess();
2791 }
2792
2793 // Make sure getNounClass does not return garbage for languages without noun classes.
2794 formatter = NumberFormatter::with().locale(Locale::getEnglish());
2795 fn = formatter.formatDouble(1.1, status);
2796 status.assertSuccess();
2797 assertEquals("getNounClasses for a not supported language",
2798 UDISPOPT_NOUN_CLASS_UNDEFINED, fn.getNounClass(status));
2799 }
2800
2801 // The following test of getGender (removed in ICU 72) is replaced by the above
2802 // parallel test unitNounClass using getNounClass (getGender replacement).
2803 //void NumberFormatterApiTest::unitGender() {...}
2804
unitNotConvertible()2805 void NumberFormatterApiTest::unitNotConvertible() {
2806 IcuTestErrorCode status(*this, "unitNotConvertible");
2807 const double randomNumber = 1234;
2808
2809 NumberFormatter::with()
2810 .unit(MeasureUnit::forIdentifier("meter-and-liter", status))
2811 .locale("en_US")
2812 .formatDouble(randomNumber, status);
2813 assertEquals(u"error must be returned", status.errorName(), u"U_ARGUMENT_TYPE_MISMATCH");
2814
2815 status.reset();
2816 NumberFormatter::with()
2817 .unit(MeasureUnit::forIdentifier("month-and-week", status))
2818 .locale("en_US")
2819 .formatDouble(randomNumber, status);
2820 assertEquals(u"error must be returned", status.errorName(), u"U_ARGUMENT_TYPE_MISMATCH");
2821
2822 status.reset();
2823 NumberFormatter::with()
2824 .unit(MeasureUnit::forIdentifier("week-and-day", status))
2825 .locale("en_US")
2826 .formatDouble(randomNumber, status);
2827 assertTrue(u"no error", !U_FAILURE(status));
2828 }
2829
unitPercent()2830 void NumberFormatterApiTest::unitPercent() {
2831 assertFormatDescending(
2832 u"Percent",
2833 u"percent",
2834 u"%",
2835 NumberFormatter::with().unit(NoUnit::percent()),
2836 Locale::getEnglish(),
2837 u"87,650%",
2838 u"8,765%",
2839 u"876.5%",
2840 u"87.65%",
2841 u"8.765%",
2842 u"0.8765%",
2843 u"0.08765%",
2844 u"0.008765%",
2845 u"0%");
2846
2847 assertFormatDescending(
2848 u"Permille",
2849 u"permille",
2850 u"permille",
2851 NumberFormatter::with().unit(NoUnit::permille()),
2852 Locale::getEnglish(),
2853 u"87,650‰",
2854 u"8,765‰",
2855 u"876.5‰",
2856 u"87.65‰",
2857 u"8.765‰",
2858 u"0.8765‰",
2859 u"0.08765‰",
2860 u"0.008765‰",
2861 u"0‰");
2862
2863 assertFormatSingle(
2864 u"NoUnit Base",
2865 u"base-unit",
2866 u"",
2867 NumberFormatter::with().unit(NoUnit::base()),
2868 Locale::getEnglish(),
2869 51423,
2870 u"51,423");
2871
2872 assertFormatSingle(
2873 u"Percent with Negative Sign",
2874 u"percent",
2875 u"%",
2876 NumberFormatter::with().unit(NoUnit::percent()),
2877 Locale::getEnglish(),
2878 -98.7654321,
2879 u"-98.765432%");
2880
2881 // ICU-20923
2882 assertFormatDescendingBig(
2883 u"Compact Percent",
2884 u"compact-short percent",
2885 u"K %",
2886 NumberFormatter::with()
2887 .notation(Notation::compactShort())
2888 .unit(NoUnit::percent()),
2889 Locale::getEnglish(),
2890 u"88M%",
2891 u"8.8M%",
2892 u"876K%",
2893 u"88K%",
2894 u"8.8K%",
2895 u"876%",
2896 u"88%",
2897 u"8.8%",
2898 u"0%");
2899
2900 // ICU-20923
2901 assertFormatDescendingBig(
2902 u"Compact Percent with Scale",
2903 u"compact-short percent scale/100",
2904 u"K %x100",
2905 NumberFormatter::with()
2906 .notation(Notation::compactShort())
2907 .unit(NoUnit::percent())
2908 .scale(Scale::powerOfTen(2)),
2909 Locale::getEnglish(),
2910 u"8.8B%",
2911 u"876M%",
2912 u"88M%",
2913 u"8.8M%",
2914 u"876K%",
2915 u"88K%",
2916 u"8.8K%",
2917 u"876%",
2918 u"0%");
2919
2920 // ICU-20923
2921 assertFormatDescendingBig(
2922 u"Compact Percent Long Name",
2923 u"compact-short percent unit-width-full-name",
2924 u"K % unit-width-full-name",
2925 NumberFormatter::with()
2926 .notation(Notation::compactShort())
2927 .unit(NoUnit::percent())
2928 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
2929 Locale::getEnglish(),
2930 u"88M percent",
2931 u"8.8M percent",
2932 u"876K percent",
2933 u"88K percent",
2934 u"8.8K percent",
2935 u"876 percent",
2936 u"88 percent",
2937 u"8.8 percent",
2938 u"0 percent");
2939
2940 assertFormatSingle(
2941 u"Per Percent",
2942 u"measure-unit/length-meter per-measure-unit/concentr-percent unit-width-full-name",
2943 u"measure-unit/length-meter per-measure-unit/concentr-percent unit-width-full-name",
2944 NumberFormatter::with()
2945 .unit(MeasureUnit::getMeter())
2946 .perUnit(MeasureUnit::getPercent())
2947 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
2948 Locale::getEnglish(),
2949 50,
2950 u"50 meters per percent");
2951 }
2952
unitLocaleTags()2953 void NumberFormatterApiTest::unitLocaleTags() {
2954 IcuTestErrorCode status(*this, "unitLocaleTags");
2955
2956 const struct TestCase {
2957 const UnicodeString message;
2958 const char *locale;
2959 const char *inputUnit;
2960 const double inputValue;
2961 const char *usage;
2962 const char *expectedOutputUnit;
2963 const double expectedOutputValue;
2964 const UnicodeString expectedFormattedNumber;
2965 } cases[] = {
2966 // Test without any tag behaviour
2967 {u"Test the locale without any addition and without usage", "en-US", "celsius", 0, nullptr,
2968 "celsius", 0.0, u"0 degrees Celsius"},
2969 {u"Test the locale without any addition and usage", "en-US", "celsius", 0, "default",
2970 "fahrenheit", 32.0, u"32 degrees Fahrenheit"},
2971
2972 // Test the behaviour of the `mu` tag.
2973 {u"Test the locale with mu = celsius and without usage", "en-US-u-mu-celsius", "fahrenheit", 0,
2974 nullptr, "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
2975 {u"Test the locale with mu = celsius and with usage", "en-US-u-mu-celsius", "fahrenheit", 0,
2976 "default", "celsius", -18.0, u"-18 degrees Celsius"},
2977 {u"Test the locale with mu = calsius (wrong spelling) and with usage", "en-US-u-mu-calsius",
2978 "fahrenheit", 0, "default", "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
2979 {u"Test the locale with mu = meter (only temprature units are supported) and with usage",
2980 "en-US-u-mu-meter", "foot", 0, "default", "inch", 0.0, u"0 inches"},
2981
2982 // Test the behaviour of the `ms` tag
2983 {u"Test the locale with ms = metric and without usage", "en-US-u-ms-metric", "fahrenheit", 0,
2984 nullptr, "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
2985 {u"Test the locale with ms = metric and with usage", "en-US-u-ms-metric", "fahrenheit", 0,
2986 "default", "celsius", -18, u"-18 degrees Celsius"},
2987 {u"Test the locale with ms = Matric (wrong spelling) and with usage", "en-US-u-ms-Matric",
2988 "fahrenheit", 0, "default", "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
2989
2990 // Test the behaviour of the `rg` tag
2991 {u"Test the locale with rg = UK and without usage", "en-US-u-rg-ukzzzz", "fahrenheit", 0,
2992 nullptr, "fahrenheit", 0.0, u"0 degrees Fahrenheit"},
2993 {u"Test the locale with rg = UK and with usage", "en-US-u-rg-ukzzzz", "fahrenheit", 0, "default",
2994 "celsius", -18, u"-18 degrees Celsius"},
2995 {"Test the locale with mu = fahrenheit and without usage", "en-US-u-mu-fahrenheit", "celsius", 0,
2996 nullptr, "celsius", 0.0, "0 degrees Celsius"},
2997 {"Test the locale with mu = fahrenheit and with usage", "en-US-u-mu-fahrenheit", "celsius", 0,
2998 "default", "fahrenheit", 32.0, "32 degrees Fahrenheit"},
2999 {u"Test the locale with rg = UKOI and with usage", "en-US-u-rg-ukoi", "fahrenheit", 0,
3000 "default", "celsius", -18.0, u"-18 degrees Celsius"},
3001
3002 // Test the priorities
3003 {u"Test the locale with mu,ms,rg --> mu tag wins", "en-US-u-mu-celsius-ms-ussystem-rg-uszzzz",
3004 "celsius", 0, "default", "celsius", 0.0, u"0 degrees Celsius"},
3005 {u"Test the locale with ms,rg --> ms tag wins", "en-US-u-ms-metric-rg-uszzzz", "foot", 1,
3006 "default", "centimeter", 30.0, u"30 centimeters"},
3007
3008 // Test the liklihood of the languages
3009 {"Test the region of `en` --> region should be US", "en", "celsius", 1, "default", "fahrenheit",
3010 34.0, u"34 degrees Fahrenheit"},
3011 {"Test the region of `de` --> region should be DE", "de", "celsius", 1, "default", "celsius",
3012 1.0, u"1 Grad Celsius"},
3013 {"Test the region of `ar` --> region should be EG", "ar", "celsius", 1, "default", "celsius",
3014 1.0, u"١ درجة مئوية"},
3015 };
3016
3017 for (const auto &testCase : cases) {
3018 UnicodeString message = testCase.message;
3019 Locale locale(testCase.locale);
3020 auto inputUnit = MeasureUnit::forIdentifier(testCase.inputUnit, status);
3021 auto inputValue = testCase.inputValue;
3022 const auto* usage = testCase.usage;
3023 auto expectedOutputUnit = MeasureUnit::forIdentifier(testCase.expectedOutputUnit, status);
3024 UnicodeString expectedFormattedNumber = testCase.expectedFormattedNumber;
3025
3026 auto nf = NumberFormatter::with()
3027 .locale(locale) //
3028 .unit(inputUnit) //
3029 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME); //
3030 if (usage != nullptr) {
3031 nf = nf.usage(usage);
3032 }
3033 auto fn = nf.formatDouble(inputValue, status);
3034 if (status.errIfFailureAndReset()) {
3035 continue;
3036 }
3037
3038 assertEquals(message, fn.toString(status), expectedFormattedNumber);
3039 // TODO: ICU-22154
3040 // assertEquals(message, fn.getOutputUnit(status).getIdentifier(),
3041 // expectedOutputUnit.getIdentifier());
3042 }
3043 }
3044
percentParity()3045 void NumberFormatterApiTest::percentParity() {
3046 IcuTestErrorCode status(*this, "percentParity");
3047 UnlocalizedNumberFormatter uNoUnitPercent = NumberFormatter::with().unit(NoUnit::percent());
3048 UnlocalizedNumberFormatter uNoUnitPermille = NumberFormatter::with().unit(NoUnit::permille());
3049 UnlocalizedNumberFormatter uMeasurePercent = NumberFormatter::with().unit(MeasureUnit::getPercent());
3050 UnlocalizedNumberFormatter uMeasurePermille = NumberFormatter::with().unit(MeasureUnit::getPermille());
3051
3052 int32_t localeCount;
3053 const auto* locales = Locale::getAvailableLocales(localeCount);
3054 for (int32_t i=0; i<localeCount; i++) {
3055 const auto& locale = locales[i];
3056 UnicodeString sNoUnitPercent = uNoUnitPercent.locale(locale)
3057 .formatDouble(50, status).toString(status);
3058 UnicodeString sNoUnitPermille = uNoUnitPermille.locale(locale)
3059 .formatDouble(50, status).toString(status);
3060 UnicodeString sMeasurePercent = uMeasurePercent.locale(locale)
3061 .formatDouble(50, status).toString(status);
3062 UnicodeString sMeasurePermille = uMeasurePermille.locale(locale)
3063 .formatDouble(50, status).toString(status);
3064
3065 assertEquals(u"Percent, locale " + UnicodeString(locale.getName()),
3066 sNoUnitPercent, sMeasurePercent);
3067 assertEquals(u"Permille, locale " + UnicodeString(locale.getName()),
3068 sNoUnitPermille, sMeasurePermille);
3069 }
3070 }
3071
roundingFraction()3072 void NumberFormatterApiTest::roundingFraction() {
3073 assertFormatDescending(
3074 u"Integer",
3075 u"precision-integer",
3076 u".",
3077 NumberFormatter::with().precision(Precision::integer()),
3078 Locale::getEnglish(),
3079 u"87,650",
3080 u"8,765",
3081 u"876",
3082 u"88",
3083 u"9",
3084 u"1",
3085 u"0",
3086 u"0",
3087 u"0");
3088
3089 assertFormatDescending(
3090 u"Fixed Fraction",
3091 u".000",
3092 u".000",
3093 NumberFormatter::with().precision(Precision::fixedFraction(3)),
3094 Locale::getEnglish(),
3095 u"87,650.000",
3096 u"8,765.000",
3097 u"876.500",
3098 u"87.650",
3099 u"8.765",
3100 u"0.876",
3101 u"0.088",
3102 u"0.009",
3103 u"0.000");
3104
3105 assertFormatDescending(
3106 u"Min Fraction",
3107 u".0*",
3108 u".0+",
3109 NumberFormatter::with().precision(Precision::minFraction(1)),
3110 Locale::getEnglish(),
3111 u"87,650.0",
3112 u"8,765.0",
3113 u"876.5",
3114 u"87.65",
3115 u"8.765",
3116 u"0.8765",
3117 u"0.08765",
3118 u"0.008765",
3119 u"0.0");
3120
3121 assertFormatDescending(
3122 u"Max Fraction",
3123 u".#",
3124 u".#",
3125 NumberFormatter::with().precision(Precision::maxFraction(1)),
3126 Locale::getEnglish(),
3127 u"87,650",
3128 u"8,765",
3129 u"876.5",
3130 u"87.6",
3131 u"8.8",
3132 u"0.9",
3133 u"0.1",
3134 u"0",
3135 u"0");
3136
3137 assertFormatDescending(
3138 u"Min/Max Fraction",
3139 u".0##",
3140 u".0##",
3141 NumberFormatter::with().precision(Precision::minMaxFraction(1, 3)),
3142 Locale::getEnglish(),
3143 u"87,650.0",
3144 u"8,765.0",
3145 u"876.5",
3146 u"87.65",
3147 u"8.765",
3148 u"0.876",
3149 u"0.088",
3150 u"0.009",
3151 u"0.0");
3152
3153 assertFormatSingle(
3154 u"Hide If Whole A",
3155 u".00/w",
3156 u".00/w",
3157 NumberFormatter::with().precision(Precision::fixedFraction(2)
3158 .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)),
3159 Locale::getEnglish(),
3160 1.2,
3161 "1.20");
3162
3163 assertFormatSingle(
3164 u"Hide If Whole B",
3165 u".00/w",
3166 u".00/w",
3167 NumberFormatter::with().precision(Precision::fixedFraction(2)
3168 .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)),
3169 Locale::getEnglish(),
3170 1,
3171 "1");
3172
3173 assertFormatSingle(
3174 u"Hide If Whole with Rounding Mode A (ICU-21881)",
3175 u".00/w rounding-mode-floor",
3176 u".00/w rounding-mode-floor",
3177 NumberFormatter::with().precision(Precision::fixedFraction(2)
3178 .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE))
3179 .roundingMode(UNUM_ROUND_FLOOR),
3180 Locale::getEnglish(),
3181 3.009,
3182 "3");
3183
3184 assertFormatSingle(
3185 u"Hide If Whole with Rounding Mode B (ICU-21881)",
3186 u".00/w rounding-mode-half-up",
3187 u".00/w rounding-mode-half-up",
3188 NumberFormatter::with().precision(Precision::fixedFraction(2)
3189 .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE))
3190 .roundingMode(UNUM_ROUND_HALFUP),
3191 Locale::getEnglish(),
3192 3.001,
3193 "3");
3194 }
3195
roundingFigures()3196 void NumberFormatterApiTest::roundingFigures() {
3197 assertFormatSingle(
3198 u"Fixed Significant",
3199 u"@@@",
3200 u"@@@",
3201 NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)),
3202 Locale::getEnglish(),
3203 -98,
3204 u"-98.0");
3205
3206 assertFormatSingle(
3207 u"Fixed Significant Rounding",
3208 u"@@@",
3209 u"@@@",
3210 NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)),
3211 Locale::getEnglish(),
3212 -98.7654321,
3213 u"-98.8");
3214
3215 assertFormatSingle(
3216 u"Fixed Significant at rounding boundary",
3217 u"@@@",
3218 u"@@@",
3219 NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)),
3220 Locale::getEnglish(),
3221 9.999,
3222 u"10.0");
3223
3224 assertFormatSingle(
3225 u"Fixed Significant Zero",
3226 u"@@@",
3227 u"@@@",
3228 NumberFormatter::with().precision(Precision::fixedSignificantDigits(3)),
3229 Locale::getEnglish(),
3230 0,
3231 u"0.00");
3232
3233 assertFormatSingle(
3234 u"Min Significant",
3235 u"@@*",
3236 u"@@+",
3237 NumberFormatter::with().precision(Precision::minSignificantDigits(2)),
3238 Locale::getEnglish(),
3239 -9,
3240 u"-9.0");
3241
3242 assertFormatSingle(
3243 u"Max Significant",
3244 u"@###",
3245 u"@###",
3246 NumberFormatter::with().precision(Precision::maxSignificantDigits(4)),
3247 Locale::getEnglish(),
3248 98.7654321,
3249 u"98.77");
3250
3251 assertFormatSingle(
3252 u"Min/Max Significant",
3253 u"@@@#",
3254 u"@@@#",
3255 NumberFormatter::with().precision(Precision::minMaxSignificantDigits(3, 4)),
3256 Locale::getEnglish(),
3257 9.99999,
3258 u"10.0");
3259
3260 assertFormatSingle(
3261 u"Fixed Significant on zero with lots of integer width",
3262 u"@ integer-width/+000",
3263 u"@ 000",
3264 NumberFormatter::with().precision(Precision::fixedSignificantDigits(1))
3265 .integerWidth(IntegerWidth::zeroFillTo(3)),
3266 Locale::getEnglish(),
3267 0,
3268 "000");
3269
3270 assertFormatSingle(
3271 u"Fixed Significant on zero with zero integer width",
3272 u"@ integer-width/*",
3273 u"@ integer-width/+",
3274 NumberFormatter::with().precision(Precision::fixedSignificantDigits(1))
3275 .integerWidth(IntegerWidth::zeroFillTo(0)),
3276 Locale::getEnglish(),
3277 0,
3278 "0");
3279 }
3280
roundingFractionFigures()3281 void NumberFormatterApiTest::roundingFractionFigures() {
3282 assertFormatDescending(
3283 u"Basic Significant", // for comparison
3284 u"@#",
3285 u"@#",
3286 NumberFormatter::with().precision(Precision::maxSignificantDigits(2)),
3287 Locale::getEnglish(),
3288 u"88,000",
3289 u"8,800",
3290 u"880",
3291 u"88",
3292 u"8.8",
3293 u"0.88",
3294 u"0.088",
3295 u"0.0088",
3296 u"0");
3297
3298 assertFormatDescending(
3299 u"FracSig minMaxFrac minSig",
3300 u".0#/@@@*",
3301 u".0#/@@@+",
3302 NumberFormatter::with().precision(Precision::minMaxFraction(1, 2).withMinDigits(3)),
3303 Locale::getEnglish(),
3304 u"87,650.0",
3305 u"8,765.0",
3306 u"876.5",
3307 u"87.65",
3308 u"8.76",
3309 u"0.876", // minSig beats maxFrac
3310 u"0.0876", // minSig beats maxFrac
3311 u"0.00876", // minSig beats maxFrac
3312 u"0.0");
3313
3314 assertFormatDescending(
3315 u"FracSig minMaxFrac maxSig A",
3316 u".0##/@#",
3317 u".0##/@#",
3318 NumberFormatter::with().precision(Precision::minMaxFraction(1, 3).withMaxDigits(2)),
3319 Locale::getEnglish(),
3320 u"88,000.0", // maxSig beats maxFrac
3321 u"8,800.0", // maxSig beats maxFrac
3322 u"880.0", // maxSig beats maxFrac
3323 u"88.0", // maxSig beats maxFrac
3324 u"8.8", // maxSig beats maxFrac
3325 u"0.88", // maxSig beats maxFrac
3326 u"0.088",
3327 u"0.009",
3328 u"0.0");
3329
3330 assertFormatDescending(
3331 u"FracSig minMaxFrac maxSig B",
3332 u".00/@#",
3333 u".00/@#",
3334 NumberFormatter::with().precision(Precision::fixedFraction(2).withMaxDigits(2)),
3335 Locale::getEnglish(),
3336 u"88,000.00", // maxSig beats maxFrac
3337 u"8,800.00", // maxSig beats maxFrac
3338 u"880.00", // maxSig beats maxFrac
3339 u"88.00", // maxSig beats maxFrac
3340 u"8.80", // maxSig beats maxFrac
3341 u"0.88",
3342 u"0.09",
3343 u"0.01",
3344 u"0.00");
3345
3346 assertFormatSingle(
3347 u"FracSig with trailing zeros A",
3348 u".00/@@@*",
3349 u".00/@@@+",
3350 NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)),
3351 Locale::getEnglish(),
3352 0.1,
3353 u"0.10");
3354
3355 assertFormatSingle(
3356 u"FracSig with trailing zeros B",
3357 u".00/@@@*",
3358 u".00/@@@+",
3359 NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)),
3360 Locale::getEnglish(),
3361 0.0999999,
3362 u"0.10");
3363
3364 assertFormatDescending(
3365 u"FracSig withSignificantDigits RELAXED",
3366 u"precision-integer/@#r",
3367 u"./@#r",
3368 NumberFormatter::with().precision(Precision::maxFraction(0)
3369 .withSignificantDigits(1, 2, UNUM_ROUNDING_PRIORITY_RELAXED)),
3370 Locale::getEnglish(),
3371 u"87,650",
3372 u"8,765",
3373 u"876",
3374 u"88",
3375 u"8.8",
3376 u"0.88",
3377 u"0.088",
3378 u"0.0088",
3379 u"0");
3380
3381 assertFormatDescending(
3382 u"FracSig withSignificantDigits STRICT",
3383 u"precision-integer/@#s",
3384 u"./@#s",
3385 NumberFormatter::with().precision(Precision::maxFraction(0)
3386 .withSignificantDigits(1, 2, UNUM_ROUNDING_PRIORITY_STRICT)),
3387 Locale::getEnglish(),
3388 u"88,000",
3389 u"8,800",
3390 u"880",
3391 u"88",
3392 u"9",
3393 u"1",
3394 u"0",
3395 u"0",
3396 u"0");
3397
3398 assertFormatSingle(
3399 u"FracSig withSignificantDigits Trailing Zeros RELAXED",
3400 u".0/@@@r",
3401 u".0/@@@r",
3402 NumberFormatter::with().precision(Precision::fixedFraction(1)
3403 .withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_RELAXED)),
3404 Locale::getEnglish(),
3405 1,
3406 u"1.00");
3407
3408 // Trailing zeros follow the strategy that was chosen:
3409 assertFormatSingle(
3410 u"FracSig withSignificantDigits Trailing Zeros STRICT",
3411 u".0/@@@s",
3412 u".0/@@@s",
3413 NumberFormatter::with().precision(Precision::fixedFraction(1)
3414 .withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_STRICT)),
3415 Locale::getEnglish(),
3416 1,
3417 u"1.0");
3418
3419 assertFormatSingle(
3420 u"FracSig withSignificantDigits at rounding boundary",
3421 u"precision-integer/@@@s",
3422 u"./@@@s",
3423 NumberFormatter::with().precision(Precision::fixedFraction(0)
3424 .withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_STRICT)),
3425 Locale::getEnglish(),
3426 9.99,
3427 u"10");
3428
3429 assertFormatSingle(
3430 u"FracSig with Trailing Zero Display",
3431 u".00/@@@*/w",
3432 u".00/@@@+/w",
3433 NumberFormatter::with().precision(Precision::fixedFraction(2).withMinDigits(3)
3434 .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE)),
3435 Locale::getEnglish(),
3436 1,
3437 u"1");
3438 }
3439
roundingOther()3440 void NumberFormatterApiTest::roundingOther() {
3441 assertFormatDescending(
3442 u"Rounding None",
3443 u"precision-unlimited",
3444 u".+",
3445 NumberFormatter::with().precision(Precision::unlimited()),
3446 Locale::getEnglish(),
3447 u"87,650",
3448 u"8,765",
3449 u"876.5",
3450 u"87.65",
3451 u"8.765",
3452 u"0.8765",
3453 u"0.08765",
3454 u"0.008765",
3455 u"0");
3456
3457 assertFormatDescending(
3458 u"Increment",
3459 u"precision-increment/0.5",
3460 u"precision-increment/0.5",
3461 NumberFormatter::with().precision(Precision::increment(0.5).withMinFraction(1)),
3462 Locale::getEnglish(),
3463 u"87,650.0",
3464 u"8,765.0",
3465 u"876.5",
3466 u"87.5",
3467 u"9.0",
3468 u"1.0",
3469 u"0.0",
3470 u"0.0",
3471 u"0.0");
3472
3473 assertFormatDescending(
3474 u"Increment with Min Fraction",
3475 u"precision-increment/0.50",
3476 u"precision-increment/0.50",
3477 NumberFormatter::with().precision(Precision::increment(0.5).withMinFraction(2)),
3478 Locale::getEnglish(),
3479 u"87,650.00",
3480 u"8,765.00",
3481 u"876.50",
3482 u"87.50",
3483 u"9.00",
3484 u"1.00",
3485 u"0.00",
3486 u"0.00",
3487 u"0.00");
3488
3489 assertFormatDescending(
3490 u"Strange Increment",
3491 u"precision-increment/3.140",
3492 u"precision-increment/3.140",
3493 NumberFormatter::with().precision(Precision::increment(3.14).withMinFraction(3)),
3494 Locale::getEnglish(),
3495 u"87,649.960",
3496 u"8,763.740",
3497 u"876.060",
3498 u"87.920",
3499 u"9.420",
3500 u"0.000",
3501 u"0.000",
3502 u"0.000",
3503 u"0.000");
3504
3505 assertFormatDescending(
3506 u"Medium nickel increment with rounding mode ceiling (ICU-21668)",
3507 u"precision-increment/50 rounding-mode-ceiling",
3508 u"precision-increment/50 rounding-mode-ceiling",
3509 NumberFormatter::with()
3510 .precision(Precision::increment(50))
3511 .roundingMode(UNUM_ROUND_CEILING),
3512 Locale::getEnglish(),
3513 u"87,650",
3514 u"8,800",
3515 u"900",
3516 u"100",
3517 u"50",
3518 u"50",
3519 u"50",
3520 u"50",
3521 u"0");
3522
3523 assertFormatDescending(
3524 u"Large nickel increment with rounding mode up (ICU-21668)",
3525 u"precision-increment/5000 rounding-mode-up",
3526 u"precision-increment/5000 rounding-mode-up",
3527 NumberFormatter::with()
3528 .precision(Precision::increment(5000))
3529 .roundingMode(UNUM_ROUND_UP),
3530 Locale::getEnglish(),
3531 u"90,000",
3532 u"10,000",
3533 u"5,000",
3534 u"5,000",
3535 u"5,000",
3536 u"5,000",
3537 u"5,000",
3538 u"5,000",
3539 u"0");
3540
3541 assertFormatDescending(
3542 u"Large dime increment with rounding mode up (ICU-21668)",
3543 u"precision-increment/10000 rounding-mode-up",
3544 u"precision-increment/10000 rounding-mode-up",
3545 NumberFormatter::with()
3546 .precision(Precision::increment(10000))
3547 .roundingMode(UNUM_ROUND_UP),
3548 Locale::getEnglish(),
3549 u"90,000",
3550 u"10,000",
3551 u"10,000",
3552 u"10,000",
3553 u"10,000",
3554 u"10,000",
3555 u"10,000",
3556 u"10,000",
3557 u"0");
3558
3559 assertFormatDescending(
3560 u"Large non-nickel increment with rounding mode up (ICU-21668)",
3561 u"precision-increment/15000 rounding-mode-up",
3562 u"precision-increment/15000 rounding-mode-up",
3563 NumberFormatter::with()
3564 .precision(Precision::increment(15000))
3565 .roundingMode(UNUM_ROUND_UP),
3566 Locale::getEnglish(),
3567 u"90,000",
3568 u"15,000",
3569 u"15,000",
3570 u"15,000",
3571 u"15,000",
3572 u"15,000",
3573 u"15,000",
3574 u"15,000",
3575 u"0");
3576
3577 assertFormatDescending(
3578 u"Increment Resolving to Power of 10",
3579 u"precision-increment/0.010",
3580 u"precision-increment/0.010",
3581 NumberFormatter::with().precision(Precision::increment(0.01).withMinFraction(3)),
3582 Locale::getEnglish(),
3583 u"87,650.000",
3584 u"8,765.000",
3585 u"876.500",
3586 u"87.650",
3587 u"8.760",
3588 u"0.880",
3589 u"0.090",
3590 u"0.010",
3591 u"0.000");
3592
3593 assertFormatDescending(
3594 u"Integer increment with trailing zeros (ICU-21654)",
3595 u"precision-increment/50",
3596 u"precision-increment/50",
3597 NumberFormatter::with().precision(Precision::increment(50)),
3598 Locale::getEnglish(),
3599 u"87,650",
3600 u"8,750",
3601 u"900",
3602 u"100",
3603 u"0",
3604 u"0",
3605 u"0",
3606 u"0",
3607 u"0");
3608
3609 assertFormatDescending(
3610 u"Integer increment with minFraction (ICU-21654)",
3611 u"precision-increment/5.0",
3612 u"precision-increment/5.0",
3613 NumberFormatter::with().precision(Precision::increment(5).withMinFraction(1)),
3614 Locale::getEnglish(),
3615 u"87,650.0",
3616 u"8,765.0",
3617 u"875.0",
3618 u"90.0",
3619 u"10.0",
3620 u"0.0",
3621 u"0.0",
3622 u"0.0",
3623 u"0.0");
3624
3625 assertFormatSingle(
3626 u"Large integer increment",
3627 u"precision-increment/24000000000000000000000",
3628 u"precision-increment/24000000000000000000000",
3629 NumberFormatter::with().precision(Precision::incrementExact(24, 21)),
3630 Locale::getEnglish(),
3631 3.1e22,
3632 u"24,000,000,000,000,000,000,000");
3633
3634 assertFormatSingle(
3635 u"Quarter rounding",
3636 u"precision-increment/250",
3637 u"precision-increment/250",
3638 NumberFormatter::with().precision(Precision::incrementExact(250, 0)),
3639 Locale::getEnglish(),
3640 700,
3641 u"750");
3642
3643 assertFormatSingle(
3644 u"ECMA-402 limit",
3645 u"precision-increment/.00000000000000000020",
3646 u"precision-increment/.00000000000000000020",
3647 NumberFormatter::with().precision(Precision::incrementExact(20, -20)),
3648 Locale::getEnglish(),
3649 333e-20,
3650 u"0.00000000000000000340");
3651
3652 assertFormatSingle(
3653 u"ECMA-402 limit with increment = 1",
3654 u"precision-increment/.00000000000000000001",
3655 u"precision-increment/.00000000000000000001",
3656 NumberFormatter::with().precision(Precision::incrementExact(1, -20)),
3657 Locale::getEnglish(),
3658 4321e-21,
3659 u"0.00000000000000000432");
3660
3661 assertFormatDescending(
3662 u"Currency Standard",
3663 u"currency/CZK precision-currency-standard",
3664 u"currency/CZK precision-currency-standard",
3665 NumberFormatter::with().precision(Precision::currency(UCurrencyUsage::UCURR_USAGE_STANDARD))
3666 .unit(CZK),
3667 Locale::getEnglish(),
3668 u"CZK 87,650.00",
3669 u"CZK 8,765.00",
3670 u"CZK 876.50",
3671 u"CZK 87.65",
3672 u"CZK 8.76",
3673 u"CZK 0.88",
3674 u"CZK 0.09",
3675 u"CZK 0.01",
3676 u"CZK 0.00");
3677
3678 assertFormatDescending(
3679 u"Currency Cash",
3680 u"currency/CZK precision-currency-cash",
3681 u"currency/CZK precision-currency-cash",
3682 NumberFormatter::with().precision(Precision::currency(UCurrencyUsage::UCURR_USAGE_CASH))
3683 .unit(CZK),
3684 Locale::getEnglish(),
3685 u"CZK 87,650",
3686 u"CZK 8,765",
3687 u"CZK 876",
3688 u"CZK 88",
3689 u"CZK 9",
3690 u"CZK 1",
3691 u"CZK 0",
3692 u"CZK 0",
3693 u"CZK 0");
3694
3695 assertFormatDescending(
3696 u"Currency Standard with Trailing Zero Display",
3697 u"currency/CZK precision-currency-standard/w",
3698 u"currency/CZK precision-currency-standard/w",
3699 NumberFormatter::with().precision(
3700 Precision::currency(UCurrencyUsage::UCURR_USAGE_STANDARD)
3701 .trailingZeroDisplay(UNUM_TRAILING_ZERO_HIDE_IF_WHOLE))
3702 .unit(CZK),
3703 Locale::getEnglish(),
3704 u"CZK 87,650",
3705 u"CZK 8,765",
3706 u"CZK 876.50",
3707 u"CZK 87.65",
3708 u"CZK 8.76",
3709 u"CZK 0.88",
3710 u"CZK 0.09",
3711 u"CZK 0.01",
3712 u"CZK 0");
3713
3714 assertFormatDescending(
3715 u"Currency Cash with Nickel Rounding",
3716 u"currency/CAD precision-currency-cash",
3717 u"currency/CAD precision-currency-cash",
3718 NumberFormatter::with().precision(Precision::currency(UCurrencyUsage::UCURR_USAGE_CASH))
3719 .unit(CAD),
3720 Locale::getEnglish(),
3721 u"CA$87,650.00",
3722 u"CA$8,765.00",
3723 u"CA$876.50",
3724 u"CA$87.65",
3725 u"CA$8.75",
3726 u"CA$0.90",
3727 u"CA$0.10",
3728 u"CA$0.00",
3729 u"CA$0.00");
3730
3731 assertFormatDescending(
3732 u"Currency not in top-level fluent chain",
3733 u"precision-integer", // calling .withCurrency() applies currency rounding rules immediately
3734 u".",
3735 NumberFormatter::with().precision(
3736 Precision::currency(UCurrencyUsage::UCURR_USAGE_CASH).withCurrency(CZK)),
3737 Locale::getEnglish(),
3738 u"87,650",
3739 u"8,765",
3740 u"876",
3741 u"88",
3742 u"9",
3743 u"1",
3744 u"0",
3745 u"0",
3746 u"0");
3747
3748 // NOTE: Other tests cover the behavior of the other rounding modes.
3749 assertFormatDescending(
3750 u"Rounding Mode CEILING",
3751 u"precision-integer rounding-mode-ceiling",
3752 u". rounding-mode-ceiling",
3753 NumberFormatter::with().precision(Precision::integer()).roundingMode(UNUM_ROUND_CEILING),
3754 Locale::getEnglish(),
3755 u"87,650",
3756 u"8,765",
3757 u"877",
3758 u"88",
3759 u"9",
3760 u"1",
3761 u"1",
3762 u"1",
3763 u"0");
3764
3765 assertFormatSingle(
3766 u"ICU-20974 Double.MIN_NORMAL",
3767 u"scientific",
3768 u"E0",
3769 NumberFormatter::with().notation(Notation::scientific()),
3770 Locale::getEnglish(),
3771 DBL_MIN,
3772 u"2.225074E-308");
3773
3774 #ifndef DBL_TRUE_MIN
3775 #define DBL_TRUE_MIN 4.9E-324
3776 #endif
3777
3778 // Note: this behavior is intentionally different from Java; see
3779 // https://github.com/google/double-conversion/issues/126
3780 assertFormatSingle(
3781 u"ICU-20974 Double.MIN_VALUE",
3782 u"scientific",
3783 u"E0",
3784 NumberFormatter::with().notation(Notation::scientific()),
3785 Locale::getEnglish(),
3786 DBL_TRUE_MIN,
3787 u"5E-324");
3788 }
3789
3790 /** Test for ICU-21654 and ICU-21668 */
roundingIncrementRegressionTest()3791 void NumberFormatterApiTest::roundingIncrementRegressionTest() {
3792 IcuTestErrorCode status(*this, "roundingIncrementRegressionTest");
3793 Locale locale = Locale::getEnglish();
3794
3795 for (int min_fraction_digits = 1; min_fraction_digits < 8; min_fraction_digits++) {
3796 // pattern is a snprintf pattern string like "precision-increment/%0.5f"
3797 char pattern[256];
3798 snprintf(pattern, 256, "precision-increment/%%0.%df", min_fraction_digits);
3799 double increment = 0.05;
3800 for (int i = 0; i < 8 ; i++, increment *= 10.0) {
3801 const UnlocalizedNumberFormatter f =
3802 NumberFormatter::with().precision(
3803 Precision::increment(increment).withMinFraction(
3804 min_fraction_digits));
3805 const LocalizedNumberFormatter l = f.locale(locale);
3806
3807 std::string skeleton;
3808 f.toSkeleton(status).toUTF8String<std::string>(skeleton);
3809
3810 char message[256];
3811 snprintf(message, 256,
3812 "ICU-21654: Precision::increment(%0.5f).withMinFraction(%d) '%s'\n",
3813 increment, min_fraction_digits,
3814 skeleton.c_str());
3815
3816 if (increment == 0.05 && min_fraction_digits == 1) {
3817 // Special case when the number of fraction digits is too low:
3818 // Precision::increment(0.05000).withMinFraction(1) 'precision-increment/0.05'
3819 assertEquals(message, "precision-increment/0.05", skeleton.c_str());
3820 } else {
3821 // All other cases: use the snprintf pattern computed above:
3822 // Precision::increment(0.50000).withMinFraction(1) 'precision-increment/0.5'
3823 // Precision::increment(5.00000).withMinFraction(1) 'precision-increment/5.0'
3824 // Precision::increment(50.00000).withMinFraction(1) 'precision-increment/50.0'
3825 // ...
3826 // Precision::increment(0.05000).withMinFraction(2) 'precision-increment/0.05'
3827 // Precision::increment(0.50000).withMinFraction(2) 'precision-increment/0.50'
3828 // Precision::increment(5.00000).withMinFraction(2) 'precision-increment/5.00'
3829 // ...
3830
3831 char expected[256];
3832 snprintf(expected, 256, pattern, increment);
3833 assertEquals(message, expected, skeleton.c_str());
3834 }
3835 }
3836 }
3837
3838 auto increment = NumberFormatter::with()
3839 .precision(Precision::increment(5000).withMinFraction(0))
3840 .roundingMode(UNUM_ROUND_UP)
3841 .locale(Locale::getEnglish())
3842 .formatDouble(5.625, status)
3843 .toString(status);
3844 assertEquals("ICU-21668", u"5,000", increment);
3845 }
3846
roundingPriorityCoverageTest()3847 void NumberFormatterApiTest::roundingPriorityCoverageTest() {
3848 IcuTestErrorCode status(*this, "roundingPriorityCoverageTest");
3849 struct TestCase {
3850 double input;
3851 const char16_t* expectedRelaxed0113;
3852 const char16_t* expectedStrict0113;
3853 const char16_t* expectedRelaxed1133;
3854 const char16_t* expectedStrict1133;
3855 } cases[] = {
3856 { 0.9999, u"1", u"1", u"1.00", u"1.0" },
3857 { 9.9999, u"10", u"10", u"10.0", u"10.0" },
3858 { 99.999, u"100", u"100", u"100.0", u"100" },
3859 { 999.99, u"1000", u"1000", u"1000.0", u"1000" },
3860
3861 { 0, u"0", u"0", u"0.00", u"0.0" },
3862
3863 { 9.876, u"9.88", u"9.9", u"9.88", u"9.9" },
3864 { 9.001, u"9", u"9", u"9.00", u"9.0" },
3865 };
3866 for (const auto& cas : cases) {
3867 auto precisionRelaxed0113 = Precision::minMaxFraction(0, 1)
3868 .withSignificantDigits(1, 3, UNUM_ROUNDING_PRIORITY_RELAXED);
3869 auto precisionStrict0113 = Precision::minMaxFraction(0, 1)
3870 .withSignificantDigits(1, 3, UNUM_ROUNDING_PRIORITY_STRICT);
3871 auto precisionRelaxed1133 = Precision::minMaxFraction(1, 1)
3872 .withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_RELAXED);
3873 auto precisionStrict1133 = Precision::minMaxFraction(1, 1)
3874 .withSignificantDigits(3, 3, UNUM_ROUNDING_PRIORITY_STRICT);
3875
3876 auto messageBase = DoubleToUnicodeString(cas.input);
3877
3878 auto check = [&](
3879 const char16_t* name,
3880 const UnicodeString& expected,
3881 const Precision& precision
3882 ) {
3883 assertEquals(
3884 messageBase + name,
3885 expected,
3886 NumberFormatter::withLocale(Locale::getEnglish())
3887 .precision(precision)
3888 .grouping(UNUM_GROUPING_OFF)
3889 .formatDouble(cas.input, status)
3890 .toString(status)
3891 );
3892 };
3893
3894 check(u" Relaxed 0113", cas.expectedRelaxed0113, precisionRelaxed0113);
3895 if (status.errIfFailureAndReset()) continue;
3896
3897 check(u" Strict 0113", cas.expectedStrict0113, precisionStrict0113);
3898 if (status.errIfFailureAndReset()) continue;
3899
3900 check(u" Relaxed 1133", cas.expectedRelaxed1133, precisionRelaxed1133);
3901 if (status.errIfFailureAndReset()) continue;
3902
3903 check(u" Strict 1133", cas.expectedStrict1133, precisionStrict1133);
3904 if (status.errIfFailureAndReset()) continue;
3905 }
3906 }
3907
grouping()3908 void NumberFormatterApiTest::grouping() {
3909 assertFormatDescendingBig(
3910 u"Western Grouping",
3911 u"group-auto",
3912 u"",
3913 NumberFormatter::with().grouping(UNUM_GROUPING_AUTO),
3914 Locale::getEnglish(),
3915 u"87,650,000",
3916 u"8,765,000",
3917 u"876,500",
3918 u"87,650",
3919 u"8,765",
3920 u"876.5",
3921 u"87.65",
3922 u"8.765",
3923 u"0");
3924
3925 assertFormatDescendingBig(
3926 u"Indic Grouping",
3927 u"group-auto",
3928 u"",
3929 NumberFormatter::with().grouping(UNUM_GROUPING_AUTO),
3930 Locale("en-IN"),
3931 u"8,76,50,000",
3932 u"87,65,000",
3933 u"8,76,500",
3934 u"87,650",
3935 u"8,765",
3936 u"876.5",
3937 u"87.65",
3938 u"8.765",
3939 u"0");
3940
3941 assertFormatDescendingBig(
3942 u"Western Grouping, Min 2",
3943 u"group-min2",
3944 u",?",
3945 NumberFormatter::with().grouping(UNUM_GROUPING_MIN2),
3946 Locale::getEnglish(),
3947 u"87,650,000",
3948 u"8,765,000",
3949 u"876,500",
3950 u"87,650",
3951 u"8765",
3952 u"876.5",
3953 u"87.65",
3954 u"8.765",
3955 u"0");
3956
3957 assertFormatDescendingBig(
3958 u"Indic Grouping, Min 2",
3959 u"group-min2",
3960 u",?",
3961 NumberFormatter::with().grouping(UNUM_GROUPING_MIN2),
3962 Locale("en-IN"),
3963 u"8,76,50,000",
3964 u"87,65,000",
3965 u"8,76,500",
3966 u"87,650",
3967 u"8765",
3968 u"876.5",
3969 u"87.65",
3970 u"8.765",
3971 u"0");
3972
3973 assertFormatDescendingBig(
3974 u"No Grouping",
3975 u"group-off",
3976 u",_",
3977 NumberFormatter::with().grouping(UNUM_GROUPING_OFF),
3978 Locale("en-IN"),
3979 u"87650000",
3980 u"8765000",
3981 u"876500",
3982 u"87650",
3983 u"8765",
3984 u"876.5",
3985 u"87.65",
3986 u"8.765",
3987 u"0");
3988
3989 assertFormatDescendingBig(
3990 u"Indic locale with THOUSANDS grouping",
3991 u"group-thousands",
3992 u"group-thousands",
3993 NumberFormatter::with().grouping(UNUM_GROUPING_THOUSANDS),
3994 Locale("en-IN"),
3995 u"87,650,000",
3996 u"8,765,000",
3997 u"876,500",
3998 u"87,650",
3999 u"8,765",
4000 u"876.5",
4001 u"87.65",
4002 u"8.765",
4003 u"0");
4004
4005 // NOTE: Polish is interesting because it has minimumGroupingDigits=2 in locale data
4006 // (Most locales have either 1 or 2)
4007 // If this test breaks due to data changes, find another locale that has minimumGroupingDigits.
4008 assertFormatDescendingBig(
4009 u"Polish Grouping",
4010 u"group-auto",
4011 u"",
4012 NumberFormatter::with().grouping(UNUM_GROUPING_AUTO),
4013 Locale("pl"),
4014 u"87 650 000",
4015 u"8 765 000",
4016 u"876 500",
4017 u"87 650",
4018 u"8765",
4019 u"876,5",
4020 u"87,65",
4021 u"8,765",
4022 u"0");
4023
4024 assertFormatDescendingBig(
4025 u"Polish Grouping, Min 2",
4026 u"group-min2",
4027 u",?",
4028 NumberFormatter::with().grouping(UNUM_GROUPING_MIN2),
4029 Locale("pl"),
4030 u"87 650 000",
4031 u"8 765 000",
4032 u"876 500",
4033 u"87 650",
4034 u"8765",
4035 u"876,5",
4036 u"87,65",
4037 u"8,765",
4038 u"0");
4039
4040 assertFormatDescendingBig(
4041 u"Polish Grouping, Always",
4042 u"group-on-aligned",
4043 u",!",
4044 NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED),
4045 Locale("pl"),
4046 u"87 650 000",
4047 u"8 765 000",
4048 u"876 500",
4049 u"87 650",
4050 u"8 765",
4051 u"876,5",
4052 u"87,65",
4053 u"8,765",
4054 u"0");
4055
4056 // NOTE: en_US_POSIX is interesting because it has no grouping in the default currency format.
4057 // If this test breaks due to data changes, find another locale that has no default grouping.
4058 assertFormatDescendingBig(
4059 u"en_US_POSIX Currency Grouping",
4060 u"currency/USD group-auto",
4061 u"currency/USD",
4062 NumberFormatter::with().grouping(UNUM_GROUPING_AUTO).unit(USD),
4063 Locale("en_US_POSIX"),
4064 u"$ 87650000.00",
4065 u"$ 8765000.00",
4066 u"$ 876500.00",
4067 u"$ 87650.00",
4068 u"$ 8765.00",
4069 u"$ 876.50",
4070 u"$ 87.65",
4071 u"$ 8.76",
4072 u"$ 0.00");
4073
4074 assertFormatDescendingBig(
4075 u"en_US_POSIX Currency Grouping, Always",
4076 u"currency/USD group-on-aligned",
4077 u"currency/USD ,!",
4078 NumberFormatter::with().grouping(UNUM_GROUPING_ON_ALIGNED).unit(USD),
4079 Locale("en_US_POSIX"),
4080 u"$ 87,650,000.00",
4081 u"$ 8,765,000.00",
4082 u"$ 876,500.00",
4083 u"$ 87,650.00",
4084 u"$ 8,765.00",
4085 u"$ 876.50",
4086 u"$ 87.65",
4087 u"$ 8.76",
4088 u"$ 0.00");
4089
4090 MacroProps macros;
4091 macros.grouper = Grouper(4, 1, 3, UNUM_GROUPING_COUNT);
4092 assertFormatDescendingBig(
4093 u"Custom Grouping via Internal API",
4094 nullptr,
4095 nullptr,
4096 NumberFormatter::with().macros(macros),
4097 Locale::getEnglish(),
4098 u"8,7,6,5,0000",
4099 u"8,7,6,5000",
4100 u"876500",
4101 u"87650",
4102 u"8765",
4103 u"876.5",
4104 u"87.65",
4105 u"8.765",
4106 u"0");
4107 }
4108
padding()4109 void NumberFormatterApiTest::padding() {
4110 assertFormatDescending(
4111 u"Padding",
4112 nullptr,
4113 nullptr,
4114 NumberFormatter::with().padding(Padder::none()),
4115 Locale::getEnglish(),
4116 u"87,650",
4117 u"8,765",
4118 u"876.5",
4119 u"87.65",
4120 u"8.765",
4121 u"0.8765",
4122 u"0.08765",
4123 u"0.008765",
4124 u"0");
4125
4126 assertFormatDescending(
4127 u"Padding",
4128 nullptr,
4129 nullptr,
4130 NumberFormatter::with().padding(
4131 Padder::codePoints(
4132 '*', 8, PadPosition::UNUM_PAD_AFTER_PREFIX)),
4133 Locale::getEnglish(),
4134 u"**87,650",
4135 u"***8,765",
4136 u"***876.5",
4137 u"***87.65",
4138 u"***8.765",
4139 u"**0.8765",
4140 u"*0.08765",
4141 u"0.008765",
4142 u"*******0");
4143
4144 assertFormatDescending(
4145 u"Padding with code points",
4146 nullptr,
4147 nullptr,
4148 NumberFormatter::with().padding(
4149 Padder::codePoints(
4150 0x101E4, 8, PadPosition::UNUM_PAD_AFTER_PREFIX)),
4151 Locale::getEnglish(),
4152 u"87,650",
4153 u"8,765",
4154 u"876.5",
4155 u"87.65",
4156 u"8.765",
4157 u"0.8765",
4158 u"0.08765",
4159 u"0.008765",
4160 u"0");
4161
4162 assertFormatDescending(
4163 u"Padding with wide digits",
4164 nullptr,
4165 nullptr,
4166 NumberFormatter::with().padding(
4167 Padder::codePoints(
4168 '*', 8, PadPosition::UNUM_PAD_AFTER_PREFIX))
4169 .adoptSymbols(new NumberingSystem(MATHSANB)),
4170 Locale::getEnglish(),
4171 u"**,",
4172 u"***,",
4173 u"***.",
4174 u"***.",
4175 u"***.",
4176 u"**.",
4177 u"*.",
4178 u".",
4179 u"*******");
4180
4181 assertFormatDescending(
4182 u"Padding with currency spacing",
4183 nullptr,
4184 nullptr,
4185 NumberFormatter::with().padding(
4186 Padder::codePoints(
4187 '*', 10, PadPosition::UNUM_PAD_AFTER_PREFIX))
4188 .unit(GBP)
4189 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE),
4190 Locale::getEnglish(),
4191 u"GBP 87,650.00",
4192 u"GBP 8,765.00",
4193 u"GBP*876.50",
4194 u"GBP**87.65",
4195 u"GBP***8.76",
4196 u"GBP***0.88",
4197 u"GBP***0.09",
4198 u"GBP***0.01",
4199 u"GBP***0.00");
4200
4201 assertFormatSingle(
4202 u"Pad Before Prefix",
4203 nullptr,
4204 nullptr,
4205 NumberFormatter::with().padding(
4206 Padder::codePoints(
4207 '*', 8, PadPosition::UNUM_PAD_BEFORE_PREFIX)),
4208 Locale::getEnglish(),
4209 -88.88,
4210 u"**-88.88");
4211
4212 assertFormatSingle(
4213 u"Pad After Prefix",
4214 nullptr,
4215 nullptr,
4216 NumberFormatter::with().padding(
4217 Padder::codePoints(
4218 '*', 8, PadPosition::UNUM_PAD_AFTER_PREFIX)),
4219 Locale::getEnglish(),
4220 -88.88,
4221 u"-**88.88");
4222
4223 assertFormatSingle(
4224 u"Pad Before Suffix",
4225 nullptr,
4226 nullptr,
4227 NumberFormatter::with().padding(
4228 Padder::codePoints(
4229 '*', 8, PadPosition::UNUM_PAD_BEFORE_SUFFIX)).unit(NoUnit::percent()),
4230 Locale::getEnglish(),
4231 88.88,
4232 u"88.88**%");
4233
4234 assertFormatSingle(
4235 u"Pad After Suffix",
4236 nullptr,
4237 nullptr,
4238 NumberFormatter::with().padding(
4239 Padder::codePoints(
4240 '*', 8, PadPosition::UNUM_PAD_AFTER_SUFFIX)).unit(NoUnit::percent()),
4241 Locale::getEnglish(),
4242 88.88,
4243 u"88.88%**");
4244
4245 assertFormatSingle(
4246 u"Currency Spacing with Zero Digit Padding Broken",
4247 nullptr,
4248 nullptr,
4249 NumberFormatter::with().padding(
4250 Padder::codePoints(
4251 '0', 12, PadPosition::UNUM_PAD_AFTER_PREFIX))
4252 .unit(GBP)
4253 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_ISO_CODE),
4254 Locale::getEnglish(),
4255 514.23,
4256 u"GBP 000514.23"); // TODO: This is broken; it renders too wide (13 instead of 12).
4257 }
4258
integerWidth()4259 void NumberFormatterApiTest::integerWidth() {
4260 assertFormatDescending(
4261 u"Integer Width Default",
4262 u"integer-width/+0",
4263 u"0",
4264 NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(1)),
4265 Locale::getEnglish(),
4266 u"87,650",
4267 u"8,765",
4268 u"876.5",
4269 u"87.65",
4270 u"8.765",
4271 u"0.8765",
4272 u"0.08765",
4273 u"0.008765",
4274 u"0");
4275
4276 assertFormatDescending(
4277 u"Integer Width Zero Fill 0",
4278 u"integer-width/*",
4279 u"integer-width/+",
4280 NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(0)),
4281 Locale::getEnglish(),
4282 u"87,650",
4283 u"8,765",
4284 u"876.5",
4285 u"87.65",
4286 u"8.765",
4287 u".8765",
4288 u".08765",
4289 u".008765",
4290 u"0"); // see ICU-20844
4291
4292 assertFormatDescending(
4293 u"Integer Width Zero Fill 3",
4294 u"integer-width/+000",
4295 u"000",
4296 NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(3)),
4297 Locale::getEnglish(),
4298 u"87,650",
4299 u"8,765",
4300 u"876.5",
4301 u"087.65",
4302 u"008.765",
4303 u"000.8765",
4304 u"000.08765",
4305 u"000.008765",
4306 u"000");
4307
4308 assertFormatDescending(
4309 u"Integer Width Max 3",
4310 u"integer-width/##0",
4311 u"integer-width/##0",
4312 NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(1).truncateAt(3)),
4313 Locale::getEnglish(),
4314 u"650",
4315 u"765",
4316 u"876.5",
4317 u"87.65",
4318 u"8.765",
4319 u"0.8765",
4320 u"0.08765",
4321 u"0.008765",
4322 u"0");
4323
4324 assertFormatDescending(
4325 u"Integer Width Fixed 2",
4326 u"integer-width/00",
4327 u"integer-width/00",
4328 NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)),
4329 Locale::getEnglish(),
4330 u"50",
4331 u"65",
4332 u"76.5",
4333 u"87.65",
4334 u"08.765",
4335 u"00.8765",
4336 u"00.08765",
4337 u"00.008765",
4338 u"00");
4339
4340 assertFormatDescending(
4341 u"Integer Width Compact",
4342 u"compact-short integer-width/000",
4343 u"compact-short integer-width/000",
4344 NumberFormatter::with()
4345 .notation(Notation::compactShort())
4346 .integerWidth(IntegerWidth::zeroFillTo(3).truncateAt(3)),
4347 Locale::getEnglish(),
4348 u"088K",
4349 u"008.8K",
4350 u"876",
4351 u"088",
4352 u"008.8",
4353 u"000.88",
4354 u"000.088",
4355 u"000.0088",
4356 u"000");
4357
4358 assertFormatDescending(
4359 u"Integer Width Scientific",
4360 u"scientific integer-width/000",
4361 u"scientific integer-width/000",
4362 NumberFormatter::with()
4363 .notation(Notation::scientific())
4364 .integerWidth(IntegerWidth::zeroFillTo(3).truncateAt(3)),
4365 Locale::getEnglish(),
4366 u"008.765E4",
4367 u"008.765E3",
4368 u"008.765E2",
4369 u"008.765E1",
4370 u"008.765E0",
4371 u"008.765E-1",
4372 u"008.765E-2",
4373 u"008.765E-3",
4374 u"000E0");
4375
4376 assertFormatDescending(
4377 u"Integer Width Engineering",
4378 u"engineering integer-width/000",
4379 u"engineering integer-width/000",
4380 NumberFormatter::with()
4381 .notation(Notation::engineering())
4382 .integerWidth(IntegerWidth::zeroFillTo(3).truncateAt(3)),
4383 Locale::getEnglish(),
4384 u"087.65E3",
4385 u"008.765E3",
4386 u"876.5E0",
4387 u"087.65E0",
4388 u"008.765E0",
4389 u"876.5E-3",
4390 u"087.65E-3",
4391 u"008.765E-3",
4392 u"000E0");
4393
4394 assertFormatSingle(
4395 u"Integer Width Remove All A",
4396 u"integer-width/00",
4397 u"integer-width/00",
4398 NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)),
4399 "en",
4400 2500,
4401 u"00");
4402
4403 assertFormatSingle(
4404 u"Integer Width Remove All B",
4405 u"integer-width/00",
4406 u"integer-width/00",
4407 NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)),
4408 "en",
4409 25000,
4410 u"00");
4411
4412 assertFormatSingle(
4413 u"Integer Width Remove All B, Bytes Mode",
4414 u"integer-width/00",
4415 u"integer-width/00",
4416 NumberFormatter::with().integerWidth(IntegerWidth::zeroFillTo(2).truncateAt(2)),
4417 "en",
4418 // Note: this double produces all 17 significant digits
4419 10000000000000002000.0,
4420 u"00");
4421
4422 assertFormatDescending(
4423 u"Integer Width Double Zero (ICU-21590)",
4424 u"integer-width-trunc",
4425 u"integer-width-trunc",
4426 NumberFormatter::with()
4427 .integerWidth(IntegerWidth::zeroFillTo(0).truncateAt(0)),
4428 Locale::getEnglish(),
4429 u"0",
4430 u"0",
4431 u".5",
4432 u".65",
4433 u".765",
4434 u".8765",
4435 u".08765",
4436 u".008765",
4437 u"0");
4438
4439 assertFormatDescending(
4440 u"Integer Width Double Zero with minFraction (ICU-21590)",
4441 u"integer-width-trunc .0*",
4442 u"integer-width-trunc .0*",
4443 NumberFormatter::with()
4444 .integerWidth(IntegerWidth::zeroFillTo(0).truncateAt(0))
4445 .precision(Precision::minFraction(1)),
4446 Locale::getEnglish(),
4447 u".0",
4448 u".0",
4449 u".5",
4450 u".65",
4451 u".765",
4452 u".8765",
4453 u".08765",
4454 u".008765",
4455 u".0");
4456 }
4457
symbols()4458 void NumberFormatterApiTest::symbols() {
4459 assertFormatDescending(
4460 u"French Symbols with Japanese Data 1",
4461 nullptr,
4462 nullptr,
4463 NumberFormatter::with().symbols(FRENCH_SYMBOLS),
4464 Locale::getJapan(),
4465 u"87\u202F650",
4466 u"8\u202F765",
4467 u"876,5",
4468 u"87,65",
4469 u"8,765",
4470 u"0,8765",
4471 u"0,08765",
4472 u"0,008765",
4473 u"0");
4474
4475 assertFormatSingle(
4476 u"French Symbols with Japanese Data 2",
4477 nullptr,
4478 nullptr,
4479 NumberFormatter::with().notation(Notation::compactShort()).symbols(FRENCH_SYMBOLS),
4480 Locale::getJapan(),
4481 12345,
4482 u"1,2\u4E07");
4483
4484 assertFormatDescending(
4485 u"Latin Numbering System with Arabic Data",
4486 u"currency/USD latin",
4487 u"currency/USD latin",
4488 NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).unit(USD),
4489 Locale("ar"),
4490 u"\u200F87,650.00 US$",
4491 u"\u200F8,765.00 US$",
4492 u"\u200F876.50 US$",
4493 u"\u200F87.65 US$",
4494 u"\u200F8.76 US$",
4495 u"\u200F0.88 US$",
4496 u"\u200F0.09 US$",
4497 u"\u200F0.01 US$",
4498 u"\u200F0.00 US$");
4499
4500 assertFormatDescending(
4501 u"Math Numbering System with French Data",
4502 u"numbering-system/mathsanb",
4503 u"numbering-system/mathsanb",
4504 NumberFormatter::with().adoptSymbols(new NumberingSystem(MATHSANB)),
4505 Locale::getFrench(),
4506 u"\u202F",
4507 u"\u202F",
4508 u",",
4509 u",",
4510 u",",
4511 u",",
4512 u",",
4513 u",",
4514 u"");
4515
4516 assertFormatSingle(
4517 u"Swiss Symbols (used in documentation)",
4518 nullptr,
4519 nullptr,
4520 NumberFormatter::with().symbols(SWISS_SYMBOLS),
4521 Locale::getEnglish(),
4522 12345.67,
4523 u"12’345.67");
4524
4525 assertFormatSingle(
4526 u"Myanmar Symbols (used in documentation)",
4527 nullptr,
4528 nullptr,
4529 NumberFormatter::with().symbols(MYANMAR_SYMBOLS),
4530 Locale::getEnglish(),
4531 12345.67,
4532 u"\u1041\u1042,\u1043\u1044\u1045.\u1046\u1047");
4533
4534 // NOTE: Locale ar puts ¤ after the number in NS arab but before the number in NS latn.
4535
4536 assertFormatSingle(
4537 u"Currency symbol should follow number in ar with NS latn",
4538 u"currency/USD latin",
4539 u"currency/USD latin",
4540 NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).unit(USD),
4541 Locale("ar"),
4542 12345.67,
4543 u"\u200F12,345.67 US$");
4544
4545 assertFormatSingle(
4546 u"Currency symbol should follow number in ar@numbers=latn",
4547 u"currency/USD",
4548 u"currency/USD",
4549 NumberFormatter::with().unit(USD),
4550 Locale("ar@numbers=latn"),
4551 12345.67,
4552 u"\u200F12,345.67 US$");
4553
4554 assertFormatSingle(
4555 u"Currency symbol should follow number in ar-EG with NS arab",
4556 u"currency/USD",
4557 u"currency/USD",
4558 NumberFormatter::with().unit(USD),
4559 Locale("ar-EG"),
4560 12345.67,
4561 u"\u200F١٢٬٣٤٥٫٦٧ US$");
4562
4563 assertFormatSingle(
4564 u"Currency symbol should follow number in ar@numbers=arab",
4565 u"currency/USD",
4566 u"currency/USD",
4567 NumberFormatter::with().unit(USD),
4568 Locale("ar@numbers=arab"),
4569 12345.67,
4570 u"\u200F١٢٬٣٤٥٫٦٧ US$");
4571
4572 assertFormatSingle(
4573 u"NumberingSystem in API should win over @numbers keyword",
4574 u"currency/USD latin",
4575 u"currency/USD latin",
4576 NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).unit(USD),
4577 Locale("ar@numbers=arab"),
4578 12345.67,
4579 u"\u200F12,345.67 US$");
4580
4581 UErrorCode status = U_ZERO_ERROR;
4582 assertEquals(
4583 "NumberingSystem in API should win over @numbers keyword in reverse order",
4584 u"\u200F12,345.67 US$",
4585 NumberFormatter::withLocale(Locale("ar@numbers=arab")).adoptSymbols(new NumberingSystem(LATN))
4586 .unit(USD)
4587 .formatDouble(12345.67, status)
4588 .toString(status));
4589
4590 DecimalFormatSymbols symbols = SWISS_SYMBOLS;
4591 UnlocalizedNumberFormatter f = NumberFormatter::with().symbols(symbols);
4592 symbols.setSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kGroupingSeparatorSymbol, u"!", status);
4593 assertFormatSingle(
4594 u"Symbols object should be copied",
4595 nullptr,
4596 nullptr,
4597 f,
4598 Locale::getEnglish(),
4599 12345.67,
4600 u"12’345.67");
4601
4602 assertFormatSingle(
4603 u"The last symbols setter wins",
4604 u"latin",
4605 u"latin",
4606 NumberFormatter::with().symbols(symbols).adoptSymbols(new NumberingSystem(LATN)),
4607 Locale::getEnglish(),
4608 12345.67,
4609 u"12,345.67");
4610
4611 assertFormatSingle(
4612 u"The last symbols setter wins",
4613 nullptr,
4614 nullptr,
4615 NumberFormatter::with().adoptSymbols(new NumberingSystem(LATN)).symbols(symbols),
4616 Locale::getEnglish(),
4617 12345.67,
4618 u"12!345.67");
4619 }
4620
4621 // TODO: Enable if/when currency symbol override is added.
4622 //void NumberFormatterTest::symbolsOverride() {
4623 // DecimalFormatSymbols dfs = DecimalFormatSymbols.getInstance(Locale::getEnglish());
4624 // dfs.setCurrencySymbol("@");
4625 // dfs.setInternationalCurrencySymbol("foo");
4626 // assertFormatSingle(
4627 // u"Custom Short Currency Symbol",
4628 // NumberFormatter::with().unit(Currency.getInstance("XXX")).symbols(dfs),
4629 // Locale::getEnglish(),
4630 // 12.3,
4631 // u"@ 12.30");
4632 //}
4633
sign()4634 void NumberFormatterApiTest::sign() {
4635 assertFormatSingle(
4636 u"Sign Auto Positive",
4637 u"sign-auto",
4638 u"",
4639 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO),
4640 Locale::getEnglish(),
4641 444444,
4642 u"444,444");
4643
4644 assertFormatSingle(
4645 u"Sign Auto Negative",
4646 u"sign-auto",
4647 u"",
4648 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO),
4649 Locale::getEnglish(),
4650 -444444,
4651 u"-444,444");
4652
4653 assertFormatSingle(
4654 u"Sign Auto Zero",
4655 u"sign-auto",
4656 u"",
4657 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_AUTO),
4658 Locale::getEnglish(),
4659 0,
4660 u"0");
4661
4662 assertFormatSingle(
4663 u"Sign Always Positive",
4664 u"sign-always",
4665 u"+!",
4666 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS),
4667 Locale::getEnglish(),
4668 444444,
4669 u"+444,444");
4670
4671 assertFormatSingle(
4672 u"Sign Always Negative",
4673 u"sign-always",
4674 u"+!",
4675 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS),
4676 Locale::getEnglish(),
4677 -444444,
4678 u"-444,444");
4679
4680 assertFormatSingle(
4681 u"Sign Always Zero",
4682 u"sign-always",
4683 u"+!",
4684 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ALWAYS),
4685 Locale::getEnglish(),
4686 0,
4687 u"+0");
4688
4689 assertFormatSingle(
4690 u"Sign Never Positive",
4691 u"sign-never",
4692 u"+_",
4693 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER),
4694 Locale::getEnglish(),
4695 444444,
4696 u"444,444");
4697
4698 assertFormatSingle(
4699 u"Sign Never Negative",
4700 u"sign-never",
4701 u"+_",
4702 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER),
4703 Locale::getEnglish(),
4704 -444444,
4705 u"444,444");
4706
4707 assertFormatSingle(
4708 u"Sign Never Zero",
4709 u"sign-never",
4710 u"+_",
4711 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEVER),
4712 Locale::getEnglish(),
4713 0,
4714 u"0");
4715
4716 assertFormatSingle(
4717 u"Sign Accounting Positive",
4718 u"currency/USD sign-accounting",
4719 u"currency/USD ()",
4720 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD),
4721 Locale::getEnglish(),
4722 444444,
4723 u"$444,444.00");
4724
4725 assertFormatSingle(
4726 u"Sign Accounting Negative",
4727 u"currency/USD sign-accounting",
4728 u"currency/USD ()",
4729 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD),
4730 Locale::getEnglish(),
4731 -444444,
4732 u"($444,444.00)");
4733
4734 assertFormatSingle(
4735 u"Sign Accounting Zero",
4736 u"currency/USD sign-accounting",
4737 u"currency/USD ()",
4738 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING).unit(USD),
4739 Locale::getEnglish(),
4740 0,
4741 u"$0.00");
4742
4743 assertFormatSingle(
4744 u"Sign Accounting-Always Positive",
4745 u"currency/USD sign-accounting-always",
4746 u"currency/USD ()!",
4747 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD),
4748 Locale::getEnglish(),
4749 444444,
4750 u"+$444,444.00");
4751
4752 assertFormatSingle(
4753 u"Sign Accounting-Always Negative",
4754 u"currency/USD sign-accounting-always",
4755 u"currency/USD ()!",
4756 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD),
4757 Locale::getEnglish(),
4758 -444444,
4759 u"($444,444.00)");
4760
4761 assertFormatSingle(
4762 u"Sign Accounting-Always Zero",
4763 u"currency/USD sign-accounting-always",
4764 u"currency/USD ()!",
4765 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_ALWAYS).unit(USD),
4766 Locale::getEnglish(),
4767 0,
4768 u"+$0.00");
4769
4770 assertFormatSingle(
4771 u"Sign Except-Zero Positive",
4772 u"sign-except-zero",
4773 u"+?",
4774 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO),
4775 Locale::getEnglish(),
4776 444444,
4777 u"+444,444");
4778
4779 assertFormatSingle(
4780 u"Sign Except-Zero Negative",
4781 u"sign-except-zero",
4782 u"+?",
4783 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO),
4784 Locale::getEnglish(),
4785 -444444,
4786 u"-444,444");
4787
4788 assertFormatSingle(
4789 u"Sign Except-Zero Zero",
4790 u"sign-except-zero",
4791 u"+?",
4792 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_EXCEPT_ZERO),
4793 Locale::getEnglish(),
4794 0,
4795 u"0");
4796
4797 assertFormatSingle(
4798 u"Sign Accounting-Except-Zero Positive",
4799 u"currency/USD sign-accounting-except-zero",
4800 u"currency/USD ()?",
4801 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD),
4802 Locale::getEnglish(),
4803 444444,
4804 u"+$444,444.00");
4805
4806 assertFormatSingle(
4807 u"Sign Accounting-Except-Zero Negative",
4808 u"currency/USD sign-accounting-except-zero",
4809 u"currency/USD ()?",
4810 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD),
4811 Locale::getEnglish(),
4812 -444444,
4813 u"($444,444.00)");
4814
4815 assertFormatSingle(
4816 u"Sign Accounting-Except-Zero Zero",
4817 u"currency/USD sign-accounting-except-zero",
4818 u"currency/USD ()?",
4819 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_EXCEPT_ZERO).unit(USD),
4820 Locale::getEnglish(),
4821 0,
4822 u"$0.00");
4823
4824 assertFormatSingle(
4825 u"Sign Negative Positive",
4826 u"sign-negative",
4827 u"+-",
4828 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEGATIVE),
4829 Locale::getEnglish(),
4830 444444,
4831 u"444,444");
4832
4833 assertFormatSingle(
4834 u"Sign Negative Negative",
4835 u"sign-negative",
4836 u"+-",
4837 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEGATIVE),
4838 Locale::getEnglish(),
4839 -444444,
4840 u"-444,444");
4841
4842 assertFormatSingle(
4843 u"Sign Negative Negative Zero",
4844 u"sign-negative",
4845 u"+-",
4846 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_NEGATIVE),
4847 Locale::getEnglish(),
4848 -0.0000001,
4849 u"0");
4850
4851 assertFormatSingle(
4852 u"Sign Accounting-Negative Positive",
4853 u"currency/USD sign-accounting-negative",
4854 u"currency/USD ()-",
4855 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE).unit(USD),
4856 Locale::getEnglish(),
4857 444444,
4858 u"$444,444.00");
4859
4860 assertFormatSingle(
4861 u"Sign Accounting-Negative Negative",
4862 u"currency/USD sign-accounting-negative",
4863 u"currency/USD ()-",
4864 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE).unit(USD),
4865 Locale::getEnglish(),
4866 -444444,
4867 "($444,444.00)");
4868
4869 assertFormatSingle(
4870 u"Sign Accounting-Negative Negative Zero",
4871 u"currency/USD sign-accounting-negative",
4872 u"currency/USD ()-",
4873 NumberFormatter::with().sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING_NEGATIVE).unit(USD),
4874 Locale::getEnglish(),
4875 -0.0000001,
4876 u"$0.00");
4877
4878 assertFormatSingle(
4879 u"Sign Accounting Negative Hidden",
4880 u"currency/USD unit-width-hidden sign-accounting",
4881 u"currency/USD unit-width-hidden ()",
4882 NumberFormatter::with()
4883 .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING)
4884 .unit(USD)
4885 .unitWidth(UNUM_UNIT_WIDTH_HIDDEN),
4886 Locale::getEnglish(),
4887 -444444,
4888 u"(444,444.00)");
4889
4890 assertFormatSingle(
4891 u"Sign Accounting Negative Narrow",
4892 u"currency/USD unit-width-narrow sign-accounting",
4893 u"currency/USD unit-width-narrow ()",
4894 NumberFormatter::with()
4895 .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING)
4896 .unit(USD)
4897 .unitWidth(UNUM_UNIT_WIDTH_NARROW),
4898 Locale::getCanada(),
4899 -444444,
4900 u"($444,444.00)");
4901
4902 assertFormatSingle(
4903 u"Sign Accounting Negative Short",
4904 u"currency/USD sign-accounting",
4905 u"currency/USD ()",
4906 NumberFormatter::with()
4907 .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING)
4908 .unit(USD)
4909 .unitWidth(UNUM_UNIT_WIDTH_SHORT),
4910 Locale::getCanada(),
4911 -444444,
4912 u"(US$444,444.00)");
4913
4914 assertFormatSingle(
4915 u"Sign Accounting Negative Iso Code",
4916 u"currency/USD unit-width-iso-code sign-accounting",
4917 u"currency/USD unit-width-iso-code ()",
4918 NumberFormatter::with()
4919 .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING)
4920 .unit(USD)
4921 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE),
4922 Locale::getCanada(),
4923 -444444,
4924 u"(USD 444,444.00)");
4925
4926 // Note: CLDR does not provide an accounting pattern for long name currency.
4927 // We fall back to normal currency format. This may change in the future.
4928 assertFormatSingle(
4929 u"Sign Accounting Negative Full Name",
4930 u"currency/USD unit-width-full-name sign-accounting",
4931 u"currency/USD unit-width-full-name ()",
4932 NumberFormatter::with()
4933 .sign(UNumberSignDisplay::UNUM_SIGN_ACCOUNTING)
4934 .unit(USD)
4935 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
4936 Locale::getCanada(),
4937 -444444,
4938 u"-444,444.00 US dollars");
4939 }
4940
signNearZero()4941 void NumberFormatterApiTest::signNearZero() {
4942 // https://unicode-org.atlassian.net/browse/ICU-20709
4943 IcuTestErrorCode status(*this, "signNearZero");
4944 const struct TestCase {
4945 UNumberSignDisplay sign;
4946 double input;
4947 const char16_t* expected;
4948 } cases[] = {
4949 { UNUM_SIGN_AUTO, 1.1, u"1" },
4950 { UNUM_SIGN_AUTO, 0.9, u"1" },
4951 { UNUM_SIGN_AUTO, 0.1, u"0" },
4952 { UNUM_SIGN_AUTO, -0.1, u"-0" }, // interesting case
4953 { UNUM_SIGN_AUTO, -0.9, u"-1" },
4954 { UNUM_SIGN_AUTO, -1.1, u"-1" },
4955 { UNUM_SIGN_ALWAYS, 1.1, u"+1" },
4956 { UNUM_SIGN_ALWAYS, 0.9, u"+1" },
4957 { UNUM_SIGN_ALWAYS, 0.1, u"+0" },
4958 { UNUM_SIGN_ALWAYS, -0.1, u"-0" },
4959 { UNUM_SIGN_ALWAYS, -0.9, u"-1" },
4960 { UNUM_SIGN_ALWAYS, -1.1, u"-1" },
4961 { UNUM_SIGN_EXCEPT_ZERO, 1.1, u"+1" },
4962 { UNUM_SIGN_EXCEPT_ZERO, 0.9, u"+1" },
4963 { UNUM_SIGN_EXCEPT_ZERO, 0.1, u"0" }, // interesting case
4964 { UNUM_SIGN_EXCEPT_ZERO, -0.1, u"0" }, // interesting case
4965 { UNUM_SIGN_EXCEPT_ZERO, -0.9, u"-1" },
4966 { UNUM_SIGN_EXCEPT_ZERO, -1.1, u"-1" },
4967 { UNUM_SIGN_NEGATIVE, 1.1, u"1" },
4968 { UNUM_SIGN_NEGATIVE, 0.9, u"1" },
4969 { UNUM_SIGN_NEGATIVE, 0.1, u"0" },
4970 { UNUM_SIGN_NEGATIVE, -0.1, u"0" }, // interesting case
4971 { UNUM_SIGN_NEGATIVE, -0.9, u"-1" },
4972 { UNUM_SIGN_NEGATIVE, -1.1, u"-1" },
4973 };
4974 for (const auto& cas : cases) {
4975 auto sign = cas.sign;
4976 auto input = cas.input;
4977 const auto* expected = cas.expected;
4978 auto actual = NumberFormatter::with()
4979 .sign(sign)
4980 .precision(Precision::integer())
4981 .locale(Locale::getUS())
4982 .formatDouble(input, status)
4983 .toString(status);
4984 assertEquals(
4985 DoubleToUnicodeString(input) + " @ SignDisplay " + Int64ToUnicodeString(sign),
4986 expected, actual);
4987 }
4988 }
4989
signCoverage()4990 void NumberFormatterApiTest::signCoverage() {
4991 // https://unicode-org.atlassian.net/browse/ICU-20708
4992 IcuTestErrorCode status(*this, "signCoverage");
4993 const struct TestCase {
4994 UNumberSignDisplay sign;
4995 const char16_t* expectedStrings[8];
4996 } cases[] = {
4997 { UNUM_SIGN_AUTO, { u"-∞", u"-1", u"-0", u"0", u"1", u"∞", u"NaN", u"-NaN" } },
4998 { UNUM_SIGN_ALWAYS, { u"-∞", u"-1", u"-0", u"+0", u"+1", u"+∞", u"+NaN", u"-NaN" } },
4999 { UNUM_SIGN_NEVER, { u"∞", u"1", u"0", u"0", u"1", u"∞", u"NaN", u"NaN" } },
5000 { UNUM_SIGN_EXCEPT_ZERO, { u"-∞", u"-1", u"0", u"0", u"+1", u"+∞", u"NaN", u"NaN" } },
5001 };
5002 double negNaN = std::copysign(uprv_getNaN(), -0.0);
5003 const double inputs[] = {
5004 -uprv_getInfinity(), -1, -0.0, 0, 1, uprv_getInfinity(), uprv_getNaN(), negNaN
5005 };
5006 for (const auto& cas : cases) {
5007 auto sign = cas.sign;
5008 for (int32_t i = 0; i < UPRV_LENGTHOF(inputs); i++) {
5009 auto input = inputs[i];
5010 const auto* expected = cas.expectedStrings[i];
5011 auto actual = NumberFormatter::with()
5012 .sign(sign)
5013 .locale(Locale::getUS())
5014 .formatDouble(input, status)
5015 .toString(status);
5016 assertEquals(
5017 DoubleToUnicodeString(input) + " " + Int64ToUnicodeString(sign),
5018 expected, actual);
5019 }
5020 }
5021 }
5022
decimal()5023 void NumberFormatterApiTest::decimal() {
5024 assertFormatDescending(
5025 u"Decimal Default",
5026 u"decimal-auto",
5027 u"",
5028 NumberFormatter::with().decimal(UNumberDecimalSeparatorDisplay::UNUM_DECIMAL_SEPARATOR_AUTO),
5029 Locale::getEnglish(),
5030 u"87,650",
5031 u"8,765",
5032 u"876.5",
5033 u"87.65",
5034 u"8.765",
5035 u"0.8765",
5036 u"0.08765",
5037 u"0.008765",
5038 u"0");
5039
5040 assertFormatDescending(
5041 u"Decimal Always Shown",
5042 u"decimal-always",
5043 u"decimal-always",
5044 NumberFormatter::with().decimal(UNumberDecimalSeparatorDisplay::UNUM_DECIMAL_SEPARATOR_ALWAYS),
5045 Locale::getEnglish(),
5046 u"87,650.",
5047 u"8,765.",
5048 u"876.5",
5049 u"87.65",
5050 u"8.765",
5051 u"0.8765",
5052 u"0.08765",
5053 u"0.008765",
5054 u"0.");
5055 }
5056
scale()5057 void NumberFormatterApiTest::scale() {
5058 assertFormatDescending(
5059 u"Multiplier None",
5060 u"scale/1",
5061 u"",
5062 NumberFormatter::with().scale(Scale::none()),
5063 Locale::getEnglish(),
5064 u"87,650",
5065 u"8,765",
5066 u"876.5",
5067 u"87.65",
5068 u"8.765",
5069 u"0.8765",
5070 u"0.08765",
5071 u"0.008765",
5072 u"0");
5073
5074 assertFormatDescending(
5075 u"Multiplier Power of Ten",
5076 u"scale/1000000",
5077 u"scale/1E6",
5078 NumberFormatter::with().scale(Scale::powerOfTen(6)),
5079 Locale::getEnglish(),
5080 u"87,650,000,000",
5081 u"8,765,000,000",
5082 u"876,500,000",
5083 u"87,650,000",
5084 u"8,765,000",
5085 u"876,500",
5086 u"87,650",
5087 u"8,765",
5088 u"0");
5089
5090 assertFormatDescending(
5091 u"Multiplier Arbitrary Double",
5092 u"scale/5.2",
5093 u"scale/5.2",
5094 NumberFormatter::with().scale(Scale::byDouble(5.2)),
5095 Locale::getEnglish(),
5096 u"455,780",
5097 u"45,578",
5098 u"4,557.8",
5099 u"455.78",
5100 u"45.578",
5101 u"4.5578",
5102 u"0.45578",
5103 u"0.045578",
5104 u"0");
5105
5106 assertFormatDescending(
5107 u"Multiplier Arbitrary BigDecimal",
5108 u"scale/5.2",
5109 u"scale/5.2",
5110 NumberFormatter::with().scale(Scale::byDecimal({"5.2", -1})),
5111 Locale::getEnglish(),
5112 u"455,780",
5113 u"45,578",
5114 u"4,557.8",
5115 u"455.78",
5116 u"45.578",
5117 u"4.5578",
5118 u"0.45578",
5119 u"0.045578",
5120 u"0");
5121
5122 assertFormatDescending(
5123 u"Multiplier Arbitrary Double And Power Of Ten",
5124 u"scale/5200",
5125 u"scale/5200",
5126 NumberFormatter::with().scale(Scale::byDoubleAndPowerOfTen(5.2, 3)),
5127 Locale::getEnglish(),
5128 u"455,780,000",
5129 u"45,578,000",
5130 u"4,557,800",
5131 u"455,780",
5132 u"45,578",
5133 u"4,557.8",
5134 u"455.78",
5135 u"45.578",
5136 u"0");
5137
5138 assertFormatDescending(
5139 u"Multiplier Zero",
5140 u"scale/0",
5141 u"scale/0",
5142 NumberFormatter::with().scale(Scale::byDouble(0)),
5143 Locale::getEnglish(),
5144 u"0",
5145 u"0",
5146 u"0",
5147 u"0",
5148 u"0",
5149 u"0",
5150 u"0",
5151 u"0",
5152 u"0");
5153
5154 assertFormatSingle(
5155 u"Multiplier Skeleton Scientific Notation and Percent",
5156 u"percent scale/1E2",
5157 u"%x100",
5158 NumberFormatter::with().unit(NoUnit::percent()).scale(Scale::powerOfTen(2)),
5159 Locale::getEnglish(),
5160 0.5,
5161 u"50%");
5162
5163 assertFormatSingle(
5164 u"Negative Multiplier",
5165 u"scale/-5.2",
5166 u"scale/-5.2",
5167 NumberFormatter::with().scale(Scale::byDouble(-5.2)),
5168 Locale::getEnglish(),
5169 2,
5170 u"-10.4");
5171
5172 assertFormatSingle(
5173 u"Negative One Multiplier",
5174 u"scale/-1",
5175 u"scale/-1",
5176 NumberFormatter::with().scale(Scale::byDouble(-1)),
5177 Locale::getEnglish(),
5178 444444,
5179 u"-444,444");
5180
5181 assertFormatSingle(
5182 u"Two-Type Multiplier with Overlap",
5183 u"scale/10000",
5184 u"scale/1E4",
5185 NumberFormatter::with().scale(Scale::byDoubleAndPowerOfTen(100, 2)),
5186 Locale::getEnglish(),
5187 2,
5188 u"20,000");
5189 }
5190
locale()5191 void NumberFormatterApiTest::locale() {
5192 // Coverage for the locale setters.
5193 UErrorCode status = U_ZERO_ERROR;
5194 UnicodeString actual = NumberFormatter::withLocale(Locale::getFrench()).formatInt(1234, status)
5195 .toString(status);
5196 assertEquals("Locale withLocale()", u"1\u202f234", actual);
5197
5198 LocalizedNumberFormatter lnf1 = NumberFormatter::withLocale("en").unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
5199 .scale(Scale::powerOfTen(2));
5200 LocalizedNumberFormatter lnf2 = NumberFormatter::with()
5201 .notation(Notation::compactLong()).locale("fr").unitWidth(UNUM_UNIT_WIDTH_FULL_NAME);
5202 UnlocalizedNumberFormatter unf1 = lnf1.withoutLocale();
5203 UnlocalizedNumberFormatter unf2 = std::move(lnf2).withoutLocale();
5204
5205 assertFormatSingle(
5206 u"Formatter after withoutLocale A",
5207 u"unit/meter unit-width-full-name scale/100",
5208 u"unit/meter unit-width-full-name scale/100",
5209 unf1.unit(METER),
5210 "it-IT",
5211 2,
5212 u"200 metri");
5213
5214 assertFormatSingle(
5215 u"Formatter after withoutLocale B",
5216 u"compact-long unit/meter unit-width-full-name",
5217 u"compact-long unit/meter unit-width-full-name",
5218 unf2.unit(METER),
5219 "ja-JP",
5220 2,
5221 u"2 メートル");
5222 }
5223
skeletonUserGuideExamples()5224 void NumberFormatterApiTest::skeletonUserGuideExamples() {
5225 IcuTestErrorCode status(*this, "skeletonUserGuideExamples");
5226
5227 // Test the skeleton examples in userguide/format_parse/numbers/skeletons.md
5228 struct TestCase {
5229 const char16_t* skeleton;
5230 const char16_t* conciseSkeleton;
5231 double input;
5232 const char16_t* expected;
5233 } cases[] = {
5234 {u"percent", u"%", 25, u"25%"},
5235 {u".00", u".00", 25, u"25.00"},
5236 {u"percent .00", u"% .00", 25, u"25.00%"},
5237 {u"scale/100", u"scale/100", 0.3, u"30"},
5238 {u"percent scale/100", u"%x100", 0.3, u"30%"},
5239 {u"measure-unit/length-meter", u"unit/meter", 5, u"5 m"},
5240 {u"measure-unit/length-meter unit-width-full-name", u"unit/meter unit-width-full-name", 5, u"5 meters"},
5241 {u"currency/CAD", u"currency/CAD", 10, u"CA$10.00"},
5242 {u"currency/CAD unit-width-narrow", u"currency/CAD unit-width-narrow", 10, u"$10.00"},
5243 {u"compact-short", u"K", 5000, u"5K"},
5244 {u"compact-long", u"KK", 5000, u"5 thousand"},
5245 {u"compact-short currency/CAD", u"K currency/CAD", 5000, u"CA$5K"},
5246 {u"", u"", 5000, u"5,000"},
5247 {u"group-min2", u",?", 5000, u"5000"},
5248 {u"group-min2", u",?", 15000, u"15,000"},
5249 {u"sign-always", u"+!", 60, u"+60"},
5250 {u"sign-always", u"+!", 0, u"+0"},
5251 {u"sign-except-zero", u"+?", 60, u"+60"},
5252 {u"sign-except-zero", u"+?", 0, u"0"},
5253 {u"sign-accounting currency/CAD", u"() currency/CAD", -40, u"(CA$40.00)"}
5254 };
5255
5256 for (const auto& cas : cases) {
5257 status.setScope(cas.skeleton);
5258 FormattedNumber actual = NumberFormatter::forSkeleton(cas.skeleton, status)
5259 .locale("en-US")
5260 .formatDouble(cas.input, status);
5261 assertEquals(cas.skeleton, cas.expected, actual.toTempString(status));
5262 status.errIfFailureAndReset();
5263 FormattedNumber actualConcise = NumberFormatter::forSkeleton(cas.conciseSkeleton, status)
5264 .locale("en-US")
5265 .formatDouble(cas.input, status);
5266 assertEquals(cas.conciseSkeleton, cas.expected, actualConcise.toTempString(status));
5267 status.errIfFailureAndReset();
5268 }
5269 }
5270
formatTypes()5271 void NumberFormatterApiTest::formatTypes() {
5272 UErrorCode status = U_ZERO_ERROR;
5273 LocalizedNumberFormatter formatter = NumberFormatter::withLocale(Locale::getEnglish());
5274
5275 // Double
5276 assertEquals("Format double", "514.23", formatter.formatDouble(514.23, status).toString(status));
5277
5278 // Int64
5279 assertEquals("Format int64", "51,423", formatter.formatDouble(51423L, status).toString(status));
5280
5281 // decNumber
5282 UnicodeString actual = formatter.formatDecimal("98765432123456789E1", status).toString(status);
5283 assertEquals("Format decNumber", u"987,654,321,234,567,890", actual);
5284
5285 // Also test proper DecimalQuantity bytes storage when all digits are in the fraction.
5286 // The number needs to have exactly 40 digits, which is the size of the default buffer.
5287 // (issue discovered by the address sanitizer in C++)
5288 static const char* str = "0.009876543210987654321098765432109876543211";
5289 actual = formatter.precision(Precision::unlimited()).formatDecimal(str, status).toString(status);
5290 assertEquals("Format decNumber to 40 digits", str, actual);
5291 }
5292
fieldPositionLogic()5293 void NumberFormatterApiTest::fieldPositionLogic() {
5294 IcuTestErrorCode status(*this, "fieldPositionLogic");
5295
5296 const char16_t* message = u"Field position logic test";
5297
5298 FormattedNumber fmtd = assertFormatSingle(
5299 message,
5300 u"",
5301 u"",
5302 NumberFormatter::with(),
5303 Locale::getEnglish(),
5304 -9876543210.12,
5305 u"-9,876,543,210.12");
5306
5307 static const UFieldPosition expectedFieldPositions[] = {
5308 // field, begin index, end index
5309 {UNUM_SIGN_FIELD, 0, 1},
5310 {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3},
5311 {UNUM_GROUPING_SEPARATOR_FIELD, 6, 7},
5312 {UNUM_GROUPING_SEPARATOR_FIELD, 10, 11},
5313 {UNUM_INTEGER_FIELD, 1, 14},
5314 {UNUM_DECIMAL_SEPARATOR_FIELD, 14, 15},
5315 {UNUM_FRACTION_FIELD, 15, 17}};
5316
5317 assertNumberFieldPositions(
5318 message,
5319 fmtd,
5320 expectedFieldPositions,
5321 UPRV_LENGTHOF(expectedFieldPositions));
5322
5323 // Test the iteration functionality of nextFieldPosition
5324 ConstrainedFieldPosition actual;
5325 actual.constrainField(UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD);
5326 int32_t i = 1;
5327 while (fmtd.nextPosition(actual, status)) {
5328 UFieldPosition expected = expectedFieldPositions[i++];
5329 assertEquals(
5330 UnicodeString(u"Next for grouping, field, case #") + Int64ToUnicodeString(i),
5331 expected.field,
5332 actual.getField());
5333 assertEquals(
5334 UnicodeString(u"Next for grouping, begin index, case #") + Int64ToUnicodeString(i),
5335 expected.beginIndex,
5336 actual.getStart());
5337 assertEquals(
5338 UnicodeString(u"Next for grouping, end index, case #") + Int64ToUnicodeString(i),
5339 expected.endIndex,
5340 actual.getLimit());
5341 }
5342 assertEquals(u"Should have seen all grouping separators", 4, i);
5343
5344 // Make sure strings without fraction do not contain fraction field
5345 actual.reset();
5346 actual.constrainField(UFIELD_CATEGORY_NUMBER, UNUM_FRACTION_FIELD);
5347 fmtd = NumberFormatter::withLocale("en").formatInt(5, status);
5348 assertFalse(u"No fraction part in an integer", fmtd.nextPosition(actual, status));
5349 }
5350
fieldPositionCoverage()5351 void NumberFormatterApiTest::fieldPositionCoverage() {
5352 IcuTestErrorCode status(*this, "fieldPositionCoverage");
5353
5354 {
5355 const char16_t* message = u"Measure unit field position basic";
5356 FormattedNumber result = assertFormatSingle(
5357 message,
5358 u"measure-unit/temperature-fahrenheit",
5359 u"unit/fahrenheit",
5360 NumberFormatter::with().unit(FAHRENHEIT),
5361 Locale::getEnglish(),
5362 68,
5363 u"68°F");
5364 static const UFieldPosition expectedFieldPositions[] = {
5365 // field, begin index, end index
5366 {UNUM_INTEGER_FIELD, 0, 2},
5367 {UNUM_MEASURE_UNIT_FIELD, 2, 4}};
5368 assertNumberFieldPositions(
5369 message,
5370 result,
5371 expectedFieldPositions,
5372 UPRV_LENGTHOF(expectedFieldPositions));
5373 }
5374
5375 {
5376 const char16_t* message = u"Measure unit field position with compound unit";
5377 FormattedNumber result = assertFormatSingle(
5378 message,
5379 u"measure-unit/temperature-fahrenheit per-measure-unit/duration-day",
5380 u"unit/fahrenheit-per-day",
5381 NumberFormatter::with().unit(FAHRENHEIT).perUnit(DAY),
5382 Locale::getEnglish(),
5383 68,
5384 u"68°F/d");
5385 static const UFieldPosition expectedFieldPositions[] = {
5386 // field, begin index, end index
5387 {UNUM_INTEGER_FIELD, 0, 2},
5388 // coverage for old enum:
5389 {DecimalFormat::kMeasureUnitField, 2, 6}};
5390 assertNumberFieldPositions(
5391 message,
5392 result,
5393 expectedFieldPositions,
5394 UPRV_LENGTHOF(expectedFieldPositions));
5395 }
5396
5397 {
5398 const char16_t* message = u"Measure unit field position with spaces";
5399 FormattedNumber result = assertFormatSingle(
5400 message,
5401 u"measure-unit/length-meter unit-width-full-name",
5402 u"unit/meter unit-width-full-name",
5403 NumberFormatter::with().unit(METER).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
5404 Locale::getEnglish(),
5405 68,
5406 u"68 meters");
5407 static const UFieldPosition expectedFieldPositions[] = {
5408 // field, begin index, end index
5409 {UNUM_INTEGER_FIELD, 0, 2},
5410 // note: field starts after the space
5411 {UNUM_MEASURE_UNIT_FIELD, 3, 9}};
5412 assertNumberFieldPositions(
5413 message,
5414 result,
5415 expectedFieldPositions,
5416 UPRV_LENGTHOF(expectedFieldPositions));
5417 }
5418
5419 {
5420 const char16_t* message = u"Measure unit field position with prefix and suffix, composed m/s";
5421 FormattedNumber result = assertFormatSingle(
5422 message,
5423 u"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name",
5424 u"measure-unit/length-meter per-measure-unit/duration-second unit-width-full-name",
5425 NumberFormatter::with().unit(METER).perUnit(SECOND).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
5426 "ky", // locale with the interesting data
5427 68,
5428 u"секундасына 68 метр");
5429 static const UFieldPosition expectedFieldPositions[] = {
5430 // field, begin index, end index
5431 {UNUM_MEASURE_UNIT_FIELD, 0, 11},
5432 {UNUM_INTEGER_FIELD, 12, 14},
5433 {UNUM_MEASURE_UNIT_FIELD, 15, 19}};
5434 assertNumberFieldPositions(
5435 message,
5436 result,
5437 expectedFieldPositions,
5438 UPRV_LENGTHOF(expectedFieldPositions));
5439 }
5440
5441 {
5442 const char16_t* message = u"Measure unit field position with prefix and suffix, built-in m/s";
5443 FormattedNumber result = assertFormatSingle(
5444 message,
5445 u"measure-unit/speed-meter-per-second unit-width-full-name",
5446 u"unit/meter-per-second unit-width-full-name",
5447 NumberFormatter::with().unit(METER_PER_SECOND).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
5448 "ky", // locale with the interesting data
5449 68,
5450 u"секундасына 68 метр");
5451 static const UFieldPosition expectedFieldPositions[] = {
5452 // field, begin index, end index
5453 {UNUM_MEASURE_UNIT_FIELD, 0, 11},
5454 {UNUM_INTEGER_FIELD, 12, 14},
5455 {UNUM_MEASURE_UNIT_FIELD, 15, 19}};
5456 assertNumberFieldPositions(
5457 message,
5458 result,
5459 expectedFieldPositions,
5460 UPRV_LENGTHOF(expectedFieldPositions));
5461 }
5462
5463 {
5464 const char16_t* message = u"Measure unit field position with inner spaces";
5465 FormattedNumber result = assertFormatSingle(
5466 message,
5467 u"measure-unit/temperature-fahrenheit unit-width-full-name",
5468 u"unit/fahrenheit unit-width-full-name",
5469 NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
5470 "vi", // locale with the interesting data
5471 68,
5472 u"68 độ F");
5473 static const UFieldPosition expectedFieldPositions[] = {
5474 // field, begin index, end index
5475 {UNUM_INTEGER_FIELD, 0, 2},
5476 // Should trim leading/trailing spaces, but not inner spaces:
5477 {UNUM_MEASURE_UNIT_FIELD, 3, 7}};
5478 assertNumberFieldPositions(
5479 message,
5480 result,
5481 expectedFieldPositions,
5482 UPRV_LENGTHOF(expectedFieldPositions));
5483 }
5484
5485 {
5486 // Data: other{"{0} K"} == "\u200E{0} K"
5487 // If that data changes, try to find another example of a non-empty unit prefix/suffix
5488 // that is also all ignorables (whitespace and bidi control marks).
5489 const char16_t* message = u"Measure unit field position with fully ignorable prefix";
5490 FormattedNumber result = assertFormatSingle(
5491 message,
5492 u"measure-unit/temperature-kelvin",
5493 u"unit/kelvin",
5494 NumberFormatter::with().unit(KELVIN),
5495 "fa", // locale with the interesting data
5496 68,
5497 u"۶۸ K");
5498 static const UFieldPosition expectedFieldPositions[] = {
5499 // field, begin index, end index
5500 {UNUM_INTEGER_FIELD, 1, 3},
5501 {UNUM_MEASURE_UNIT_FIELD, 4, 5}};
5502 assertNumberFieldPositions(
5503 message,
5504 result,
5505 expectedFieldPositions,
5506 UPRV_LENGTHOF(expectedFieldPositions));
5507 }
5508
5509 {
5510 const char16_t* message = u"Compact field basic";
5511 FormattedNumber result = assertFormatSingle(
5512 message,
5513 u"compact-short",
5514 u"K",
5515 NumberFormatter::with().notation(Notation::compactShort()),
5516 Locale::getUS(),
5517 65000,
5518 u"65K");
5519 static const UFieldPosition expectedFieldPositions[] = {
5520 // field, begin index, end index
5521 {UNUM_INTEGER_FIELD, 0, 2},
5522 {UNUM_COMPACT_FIELD, 2, 3}};
5523 assertNumberFieldPositions(
5524 message,
5525 result,
5526 expectedFieldPositions,
5527 UPRV_LENGTHOF(expectedFieldPositions));
5528 }
5529
5530 {
5531 const char16_t* message = u"Compact field with spaces";
5532 FormattedNumber result = assertFormatSingle(
5533 message,
5534 u"compact-long",
5535 u"KK",
5536 NumberFormatter::with().notation(Notation::compactLong()),
5537 Locale::getUS(),
5538 65000,
5539 u"65 thousand");
5540 static const UFieldPosition expectedFieldPositions[] = {
5541 // field, begin index, end index
5542 {UNUM_INTEGER_FIELD, 0, 2},
5543 {UNUM_COMPACT_FIELD, 3, 11}};
5544 assertNumberFieldPositions(
5545 message,
5546 result,
5547 expectedFieldPositions,
5548 UPRV_LENGTHOF(expectedFieldPositions));
5549 }
5550
5551 {
5552 const char16_t* message = u"Compact field with inner space";
5553 FormattedNumber result = assertFormatSingle(
5554 message,
5555 u"compact-long",
5556 u"KK",
5557 NumberFormatter::with().notation(Notation::compactLong()),
5558 "fil", // locale with interesting data
5559 6000,
5560 u"6 na libo");
5561 static const UFieldPosition expectedFieldPositions[] = {
5562 // field, begin index, end index
5563 {UNUM_INTEGER_FIELD, 0, 1},
5564 {UNUM_COMPACT_FIELD, 2, 9}};
5565 assertNumberFieldPositions(
5566 message,
5567 result,
5568 expectedFieldPositions,
5569 UPRV_LENGTHOF(expectedFieldPositions));
5570 }
5571
5572 {
5573 const char16_t* message = u"Compact field with bidi mark";
5574 FormattedNumber result = assertFormatSingle(
5575 message,
5576 u"compact-long",
5577 u"KK",
5578 NumberFormatter::with().notation(Notation::compactLong()),
5579 "he", // locale with interesting data
5580 6000,
5581 u"\u200F6 אלף");
5582 static const UFieldPosition expectedFieldPositions[] = {
5583 // field, begin index, end index
5584 {UNUM_INTEGER_FIELD, 1, 2},
5585 {UNUM_COMPACT_FIELD, 3, 6}};
5586 assertNumberFieldPositions(
5587 message,
5588 result,
5589 expectedFieldPositions,
5590 UPRV_LENGTHOF(expectedFieldPositions));
5591 }
5592
5593 {
5594 const char16_t* message = u"Compact with currency fields";
5595 FormattedNumber result = assertFormatSingle(
5596 message,
5597 u"compact-short currency/USD",
5598 u"K currency/USD",
5599 NumberFormatter::with().notation(Notation::compactShort()).unit(USD),
5600 "sr_Latn", // locale with interesting data
5601 65000,
5602 u"65 hilj. US$");
5603 static const UFieldPosition expectedFieldPositions[] = {
5604 // field, begin index, end index
5605 {UNUM_INTEGER_FIELD, 0, 2},
5606 {UNUM_COMPACT_FIELD, 3, 8},
5607 {UNUM_CURRENCY_FIELD, 9, 12}};
5608 assertNumberFieldPositions(
5609 message,
5610 result,
5611 expectedFieldPositions,
5612 UPRV_LENGTHOF(expectedFieldPositions));
5613 }
5614
5615 {
5616 const char16_t* message = u"Currency long name fields";
5617 FormattedNumber result = assertFormatSingle(
5618 message,
5619 u"currency/USD unit-width-full-name",
5620 u"currency/USD unit-width-full-name",
5621 NumberFormatter::with().unit(USD)
5622 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
5623 "en",
5624 12345,
5625 u"12,345.00 US dollars");
5626 static const UFieldPosition expectedFieldPositions[] = {
5627 // field, begin index, end index
5628 {UNUM_GROUPING_SEPARATOR_FIELD, 2, 3},
5629 {UNUM_INTEGER_FIELD, 0, 6},
5630 {UNUM_DECIMAL_SEPARATOR_FIELD, 6, 7},
5631 {UNUM_FRACTION_FIELD, 7, 9},
5632 {UNUM_CURRENCY_FIELD, 10, 20}};
5633 assertNumberFieldPositions(
5634 message,
5635 result,
5636 expectedFieldPositions,
5637 UPRV_LENGTHOF(expectedFieldPositions));
5638 }
5639
5640 {
5641 const char16_t* message = u"Compact with measure unit fields";
5642 FormattedNumber result = assertFormatSingle(
5643 message,
5644 u"compact-long measure-unit/length-meter unit-width-full-name",
5645 u"KK unit/meter unit-width-full-name",
5646 NumberFormatter::with().notation(Notation::compactLong())
5647 .unit(METER)
5648 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME),
5649 Locale::getUS(),
5650 65000,
5651 u"65 thousand meters");
5652 static const UFieldPosition expectedFieldPositions[] = {
5653 // field, begin index, end index
5654 {UNUM_INTEGER_FIELD, 0, 2},
5655 {UNUM_COMPACT_FIELD, 3, 11},
5656 {UNUM_MEASURE_UNIT_FIELD, 12, 18}};
5657 assertNumberFieldPositions(
5658 message,
5659 result,
5660 expectedFieldPositions,
5661 UPRV_LENGTHOF(expectedFieldPositions));
5662 }
5663 }
5664
toFormat()5665 void NumberFormatterApiTest::toFormat() {
5666 IcuTestErrorCode status(*this, "icuFormat");
5667 LocalizedNumberFormatter lnf = NumberFormatter::withLocale("fr")
5668 .precision(Precision::fixedFraction(3));
5669 LocalPointer<Format> format(lnf.toFormat(status), status);
5670 FieldPosition fpos(UNUM_DECIMAL_SEPARATOR_FIELD);
5671 UnicodeString sb;
5672 format->format(514.23, sb, fpos, status);
5673 assertEquals("Should correctly format number", u"514,230", sb);
5674 assertEquals("Should find decimal separator", 3, fpos.getBeginIndex());
5675 assertEquals("Should find end of decimal separator", 4, fpos.getEndIndex());
5676 assertEquals(
5677 "ICU Format should round-trip",
5678 lnf.toSkeleton(status),
5679 dynamic_cast<LocalizedNumberFormatterAsFormat*>(format.getAlias())->getNumberFormatter()
5680 .toSkeleton(status));
5681
5682 UFormattedNumberData result;
5683 result.quantity.setToDouble(514.23);
5684 lnf.formatImpl(&result, status);
5685 FieldPositionIterator fpi1;
5686 {
5687 FieldPositionIteratorHandler fpih(&fpi1, status);
5688 result.getAllFieldPositions(fpih, status);
5689 }
5690
5691 FieldPositionIterator fpi2;
5692 format->format(514.23, sb.remove(), &fpi2, status);
5693
5694 assertTrue("Should produce same field position iterator", fpi1 == fpi2);
5695 }
5696
errors()5697 void NumberFormatterApiTest::errors() {
5698 LocalizedNumberFormatter lnf = NumberFormatter::withLocale(Locale::getEnglish()).precision(
5699 Precision::fixedFraction(
5700 -1));
5701
5702 // formatInt
5703 UErrorCode status = U_ZERO_ERROR;
5704 FormattedNumber fn = lnf.formatInt(1, status);
5705 assertEquals(
5706 "Should fail in formatInt method with error code for rounding",
5707 U_NUMBER_ARG_OUTOFBOUNDS_ERROR,
5708 status);
5709
5710 // formatDouble
5711 status = U_ZERO_ERROR;
5712 fn = lnf.formatDouble(1.0, status);
5713 assertEquals(
5714 "Should fail in formatDouble method with error code for rounding",
5715 U_NUMBER_ARG_OUTOFBOUNDS_ERROR,
5716 status);
5717
5718 // formatDecimal (decimal error)
5719 status = U_ZERO_ERROR;
5720 fn = NumberFormatter::withLocale("en").formatDecimal("1x2", status);
5721 assertEquals(
5722 "Should fail in formatDecimal method with error code for decimal number syntax",
5723 U_DECIMAL_NUMBER_SYNTAX_ERROR,
5724 status);
5725
5726 // formatDecimal (setting error)
5727 status = U_ZERO_ERROR;
5728 fn = lnf.formatDecimal("1.0", status);
5729 assertEquals(
5730 "Should fail in formatDecimal method with error code for rounding",
5731 U_NUMBER_ARG_OUTOFBOUNDS_ERROR,
5732 status);
5733
5734 // Skeleton string
5735 status = U_ZERO_ERROR;
5736 UnicodeString output = lnf.toSkeleton(status);
5737 assertEquals(
5738 "Should fail on toSkeleton terminal method with correct error code",
5739 U_NUMBER_ARG_OUTOFBOUNDS_ERROR,
5740 status);
5741 assertTrue(
5742 "Terminal toSkeleton on error object should be bogus",
5743 output.isBogus());
5744
5745 // FieldPosition (constrained category)
5746 status = U_ZERO_ERROR;
5747 ConstrainedFieldPosition fp;
5748 fp.constrainCategory(UFIELD_CATEGORY_NUMBER);
5749 fn.nextPosition(fp, status);
5750 assertEquals(
5751 "Should fail on FieldPosition terminal method with correct error code",
5752 U_NUMBER_ARG_OUTOFBOUNDS_ERROR,
5753 status);
5754
5755 // FieldPositionIterator (no constraints)
5756 status = U_ZERO_ERROR;
5757 fp.reset();
5758 fn.nextPosition(fp, status);
5759 assertEquals(
5760 "Should fail on FieldPositoinIterator terminal method with correct error code",
5761 U_NUMBER_ARG_OUTOFBOUNDS_ERROR,
5762 status);
5763
5764 // Appendable
5765 status = U_ZERO_ERROR;
5766 UnicodeStringAppendable appendable(output.remove());
5767 fn.appendTo(appendable, status);
5768 assertEquals(
5769 "Should fail on Appendable terminal method with correct error code",
5770 U_NUMBER_ARG_OUTOFBOUNDS_ERROR,
5771 status);
5772
5773 // UnicodeString
5774 status = U_ZERO_ERROR;
5775 output = fn.toString(status);
5776 assertEquals(
5777 "Should fail on UnicodeString terminal method with correct error code",
5778 U_NUMBER_ARG_OUTOFBOUNDS_ERROR,
5779 status);
5780 assertTrue(
5781 "Terminal UnicodeString on error object should be bogus",
5782 output.isBogus());
5783
5784 // CopyErrorTo
5785 status = U_ZERO_ERROR;
5786 lnf.copyErrorTo(status);
5787 assertEquals(
5788 "Should fail since rounder is not legal with correct error code",
5789 U_NUMBER_ARG_OUTOFBOUNDS_ERROR,
5790 status);
5791 }
5792
validRanges()5793 void NumberFormatterApiTest::validRanges() {
5794
5795 #define EXPECTED_MAX_INT_FRAC_SIG 999
5796
5797 #define VALID_RANGE_ASSERT(status, method, lowerBound, argument) UPRV_BLOCK_MACRO_BEGIN { \
5798 UErrorCode expectedStatus = ((lowerBound <= argument) && (argument <= EXPECTED_MAX_INT_FRAC_SIG)) \
5799 ? U_ZERO_ERROR \
5800 : U_NUMBER_ARG_OUTOFBOUNDS_ERROR; \
5801 assertEquals( \
5802 UnicodeString(u"Incorrect status for " #method " on input ") \
5803 + Int64ToUnicodeString(argument), \
5804 expectedStatus, \
5805 status); \
5806 } UPRV_BLOCK_MACRO_END
5807
5808 #define VALID_RANGE_ONEARG(setting, method, lowerBound) UPRV_BLOCK_MACRO_BEGIN { \
5809 for (int32_t argument = -2; argument <= EXPECTED_MAX_INT_FRAC_SIG + 2; argument++) { \
5810 UErrorCode status = U_ZERO_ERROR; \
5811 NumberFormatter::with().setting(method(argument)).copyErrorTo(status); \
5812 VALID_RANGE_ASSERT(status, method, lowerBound, argument); \
5813 } \
5814 } UPRV_BLOCK_MACRO_END
5815
5816 #define VALID_RANGE_TWOARGS(setting, method, lowerBound) UPRV_BLOCK_MACRO_BEGIN { \
5817 for (int32_t argument = -2; argument <= EXPECTED_MAX_INT_FRAC_SIG + 2; argument++) { \
5818 UErrorCode status = U_ZERO_ERROR; \
5819 /* Pass EXPECTED_MAX_INT_FRAC_SIG as the second argument so arg1 <= arg2 in expected cases */ \
5820 NumberFormatter::with().setting(method(argument, EXPECTED_MAX_INT_FRAC_SIG)).copyErrorTo(status); \
5821 VALID_RANGE_ASSERT(status, method, lowerBound, argument); \
5822 status = U_ZERO_ERROR; \
5823 /* Pass lowerBound as the first argument so arg1 <= arg2 in expected cases */ \
5824 NumberFormatter::with().setting(method(lowerBound, argument)).copyErrorTo(status); \
5825 VALID_RANGE_ASSERT(status, method, lowerBound, argument); \
5826 /* Check that first argument must be less than or equal to second argument */ \
5827 NumberFormatter::with().setting(method(argument, argument - 1)).copyErrorTo(status); \
5828 assertEquals("Incorrect status for " #method " on max < min input", \
5829 U_NUMBER_ARG_OUTOFBOUNDS_ERROR, \
5830 status); \
5831 } \
5832 } UPRV_BLOCK_MACRO_END
5833
5834 VALID_RANGE_ONEARG(precision, Precision::fixedFraction, 0);
5835 VALID_RANGE_ONEARG(precision, Precision::minFraction, 0);
5836 VALID_RANGE_ONEARG(precision, Precision::maxFraction, 0);
5837 VALID_RANGE_TWOARGS(precision, Precision::minMaxFraction, 0);
5838 VALID_RANGE_ONEARG(precision, Precision::fixedSignificantDigits, 1);
5839 VALID_RANGE_ONEARG(precision, Precision::minSignificantDigits, 1);
5840 VALID_RANGE_ONEARG(precision, Precision::maxSignificantDigits, 1);
5841 VALID_RANGE_TWOARGS(precision, Precision::minMaxSignificantDigits, 1);
5842 VALID_RANGE_ONEARG(precision, Precision::fixedFraction(1).withMinDigits, 1);
5843 VALID_RANGE_ONEARG(precision, Precision::fixedFraction(1).withMaxDigits, 1);
5844 VALID_RANGE_ONEARG(notation, Notation::scientific().withMinExponentDigits, 1);
5845 VALID_RANGE_ONEARG(integerWidth, IntegerWidth::zeroFillTo, 0);
5846 VALID_RANGE_ONEARG(integerWidth, IntegerWidth::zeroFillTo(0).truncateAt, -1);
5847 }
5848
copyMove()5849 void NumberFormatterApiTest::copyMove() {
5850 IcuTestErrorCode status(*this, "copyMove");
5851
5852 // Default constructors
5853 LocalizedNumberFormatter l1;
5854 assertEquals("Initial behavior", u"10", l1.formatInt(10, status).toString(status), true);
5855 if (status.errDataIfFailureAndReset()) { return; }
5856 assertEquals("Initial call count", 1, l1.getCallCount());
5857 assertTrue("Initial compiled", l1.getCompiled() == nullptr);
5858
5859 // Setup
5860 l1 = NumberFormatter::withLocale("en").unit(NoUnit::percent()).threshold(3);
5861 assertEquals("Initial behavior", u"10%", l1.formatInt(10, status).toString(status));
5862 assertEquals("Initial call count", 1, l1.getCallCount());
5863 assertTrue("Initial compiled", l1.getCompiled() == nullptr);
5864 l1.formatInt(123, status);
5865 assertEquals("Still not compiled", 2, l1.getCallCount());
5866 assertTrue("Still not compiled", l1.getCompiled() == nullptr);
5867 l1.formatInt(123, status);
5868 assertEquals("Compiled", u"10%", l1.formatInt(10, status).toString(status));
5869 assertEquals("Compiled", INT32_MIN, l1.getCallCount());
5870 assertTrue("Compiled", l1.getCompiled() != nullptr);
5871
5872 // Copy constructor
5873 LocalizedNumberFormatter l2 = l1;
5874 assertEquals("[constructor] Copy behavior", u"10%", l2.formatInt(10, status).toString(status));
5875 assertEquals("[constructor] Copy should not have compiled state", 1, l2.getCallCount());
5876 assertTrue("[constructor] Copy should not have compiled state", l2.getCompiled() == nullptr);
5877
5878 // Move constructor
5879 LocalizedNumberFormatter l3 = std::move(l1);
5880 assertEquals("[constructor] Move behavior", u"10%", l3.formatInt(10, status).toString(status));
5881 assertEquals("[constructor] Move *should* have compiled state", INT32_MIN, l3.getCallCount());
5882 assertTrue("[constructor] Move *should* have compiled state", l3.getCompiled() != nullptr);
5883 assertEquals("[constructor] Source should be reset after move", 0, l1.getCallCount());
5884 assertTrue("[constructor] Source should be reset after move", l1.getCompiled() == nullptr);
5885
5886 // Reset l1 and l2 to check for macro-props copying for behavior testing
5887 // Make the test more interesting: also warm them up with a compiled formatter.
5888 l1 = NumberFormatter::withLocale("en");
5889 l1.formatInt(1, status);
5890 l1.formatInt(1, status);
5891 l1.formatInt(1, status);
5892 l2 = NumberFormatter::withLocale("en");
5893 l2.formatInt(1, status);
5894 l2.formatInt(1, status);
5895 l2.formatInt(1, status);
5896
5897 // Copy assignment
5898 l1 = l3;
5899 assertEquals("[assignment] Copy behavior", u"10%", l1.formatInt(10, status).toString(status));
5900 assertEquals("[assignment] Copy should not have compiled state", 1, l1.getCallCount());
5901 assertTrue("[assignment] Copy should not have compiled state", l1.getCompiled() == nullptr);
5902
5903 // Move assignment
5904 l2 = std::move(l3);
5905 assertEquals("[assignment] Move behavior", u"10%", l2.formatInt(10, status).toString(status));
5906 assertEquals("[assignment] Move *should* have compiled state", INT32_MIN, l2.getCallCount());
5907 assertTrue("[assignment] Move *should* have compiled state", l2.getCompiled() != nullptr);
5908 assertEquals("[assignment] Source should be reset after move", 0, l3.getCallCount());
5909 assertTrue("[assignment] Source should be reset after move", l3.getCompiled() == nullptr);
5910
5911 // Coverage tests for UnlocalizedNumberFormatter
5912 UnlocalizedNumberFormatter u1;
5913 assertEquals("Default behavior", u"10", u1.locale("en").formatInt(10, status).toString(status));
5914 u1 = u1.unit(NoUnit::percent());
5915 assertEquals("Copy assignment", u"10%", u1.locale("en").formatInt(10, status).toString(status));
5916 UnlocalizedNumberFormatter u2 = u1;
5917 assertEquals("Copy constructor", u"10%", u2.locale("en").formatInt(10, status).toString(status));
5918 UnlocalizedNumberFormatter u3 = std::move(u1);
5919 assertEquals("Move constructor", u"10%", u3.locale("en").formatInt(10, status).toString(status));
5920 u1 = NumberFormatter::with();
5921 u1 = std::move(u2);
5922 assertEquals("Move assignment", u"10%", u1.locale("en").formatInt(10, status).toString(status));
5923
5924 // FormattedNumber move operators
5925 FormattedNumber result = l1.formatInt(10, status);
5926 assertEquals("FormattedNumber move constructor", u"10%", result.toString(status));
5927 result = l1.formatInt(20, status);
5928 assertEquals("FormattedNumber move assignment", u"20%", result.toString(status));
5929 }
5930
localPointerCAPI()5931 void NumberFormatterApiTest::localPointerCAPI() {
5932 // NOTE: This is also the sample code in unumberformatter.h
5933 UErrorCode ec = U_ZERO_ERROR;
5934
5935 // Setup:
5936 LocalUNumberFormatterPointer uformatter(unumf_openForSkeletonAndLocale(u"percent", -1, "en", &ec));
5937 LocalUFormattedNumberPointer uresult(unumf_openResult(&ec));
5938 if (!assertSuccess("", ec, true, __FILE__, __LINE__)) { return; }
5939
5940 // Format a decimal number:
5941 unumf_formatDecimal(uformatter.getAlias(), "9.87E-3", -1, uresult.getAlias(), &ec);
5942 if (!assertSuccess("", ec, true, __FILE__, __LINE__)) { return; }
5943
5944 // Get the location of the percent sign:
5945 UFieldPosition ufpos = {UNUM_PERCENT_FIELD, 0, 0};
5946 unumf_resultNextFieldPosition(uresult.getAlias(), &ufpos, &ec);
5947 assertEquals("Percent sign location within '0.00987%'", 7, ufpos.beginIndex);
5948 assertEquals("Percent sign location within '0.00987%'", 8, ufpos.endIndex);
5949
5950 // No need to do any cleanup since we are using LocalPointer.
5951 }
5952
toObject()5953 void NumberFormatterApiTest::toObject() {
5954 IcuTestErrorCode status(*this, "toObject");
5955
5956 // const lvalue version
5957 {
5958 LocalizedNumberFormatter lnf = NumberFormatter::withLocale("en")
5959 .precision(Precision::fixedFraction(2));
5960 LocalPointer<LocalizedNumberFormatter> lnf2(lnf.clone());
5961 assertFalse("should create successfully, const lvalue", lnf2.isNull());
5962 assertEquals("object API test, const lvalue", u"1,000.00",
5963 lnf2->formatDouble(1000, status).toString(status));
5964 }
5965
5966 // rvalue reference version
5967 {
5968 LocalPointer<LocalizedNumberFormatter> lnf(
5969 NumberFormatter::withLocale("en")
5970 .precision(Precision::fixedFraction(2))
5971 .clone());
5972 assertFalse("should create successfully, rvalue reference", lnf.isNull());
5973 assertEquals("object API test, rvalue reference", u"1,000.00",
5974 lnf->formatDouble(1000, status).toString(status));
5975 }
5976
5977 // to std::unique_ptr via constructor
5978 {
5979 std::unique_ptr<LocalizedNumberFormatter> lnf(
5980 NumberFormatter::withLocale("en")
5981 .precision(Precision::fixedFraction(2))
5982 .clone());
5983 assertTrue("should create successfully, unique_ptr", static_cast<bool>(lnf));
5984 assertEquals("object API test, unique_ptr", u"1,000.00",
5985 lnf->formatDouble(1000, status).toString(status));
5986 }
5987
5988 // to std::unique_ptr via assignment
5989 {
5990 std::unique_ptr<LocalizedNumberFormatter> lnf =
5991 NumberFormatter::withLocale("en")
5992 .precision(Precision::fixedFraction(2))
5993 .clone();
5994 assertTrue("should create successfully, unique_ptr B", static_cast<bool>(lnf));
5995 assertEquals("object API test, unique_ptr B", u"1,000.00",
5996 lnf->formatDouble(1000, status).toString(status));
5997 }
5998
5999 // to LocalPointer via assignment
6000 {
6001 LocalPointer<UnlocalizedNumberFormatter> f =
6002 NumberFormatter::with().clone();
6003 }
6004
6005 // make sure no memory leaks
6006 {
6007 NumberFormatter::with().clone();
6008 }
6009 }
6010
toDecimalNumber()6011 void NumberFormatterApiTest::toDecimalNumber() {
6012 IcuTestErrorCode status(*this, "toDecimalNumber");
6013 FormattedNumber fn = NumberFormatter::withLocale("bn-BD")
6014 .scale(Scale::powerOfTen(2))
6015 .precision(Precision::maxSignificantDigits(5))
6016 .formatDouble(9.87654321e12, status);
6017 assertEquals("Should have expected localized string result",
6018 u"৯৮,৭৬,৫০,০০,০০,০০,০০০", fn.toString(status));
6019 assertEquals(u"Should have expected toDecimalNumber string result",
6020 "9.8765E+14", fn.toDecimalNumber<std::string>(status).c_str());
6021
6022 fn = NumberFormatter::withLocale("bn-BD").formatDouble(0, status);
6023 assertEquals("Should have expected localized string result",
6024 u"০", fn.toString(status));
6025 assertEquals(u"Should have expected toDecimalNumber string result",
6026 "0", fn.toDecimalNumber<std::string>(status).c_str());
6027 }
6028
microPropsInternals()6029 void NumberFormatterApiTest::microPropsInternals() {
6030 // Verify copy construction and assignment operators.
6031 int64_t testValues[2] = {4, 61};
6032
6033 MicroProps mp;
6034 assertEquals("capacity", 2, mp.mixedMeasures.getCapacity());
6035 mp.mixedMeasures[0] = testValues[0];
6036 mp.mixedMeasures[1] = testValues[1];
6037 MicroProps copyConstructed(mp);
6038 MicroProps copyAssigned;
6039 int64_t *resizeResult = mp.mixedMeasures.resize(4, 4);
6040 assertTrue("Resize success", resizeResult != nullptr);
6041 copyAssigned = mp;
6042
6043 assertTrue("MicroProps success status", U_SUCCESS(mp.mixedMeasures.status));
6044 assertTrue("Copy Constructed success status", U_SUCCESS(copyConstructed.mixedMeasures.status));
6045 assertTrue("Copy Assigned success status", U_SUCCESS(copyAssigned.mixedMeasures.status));
6046 assertEquals("Original values[0]", testValues[0], mp.mixedMeasures[0]);
6047 assertEquals("Original values[1]", testValues[1], mp.mixedMeasures[1]);
6048 assertEquals("Copy Constructed[0]", testValues[0], copyConstructed.mixedMeasures[0]);
6049 assertEquals("Copy Constructed[1]", testValues[1], copyConstructed.mixedMeasures[1]);
6050 assertEquals("Copy Assigned[0]", testValues[0], copyAssigned.mixedMeasures[0]);
6051 assertEquals("Copy Assigned[1]", testValues[1], copyAssigned.mixedMeasures[1]);
6052 assertEquals("Original capacity", 4, mp.mixedMeasures.getCapacity());
6053 assertEquals("Copy Constructed capacity", 2, copyConstructed.mixedMeasures.getCapacity());
6054 assertEquals("Copy Assigned capacity", 4, copyAssigned.mixedMeasures.getCapacity());
6055 }
6056
formatUnitsAliases()6057 void NumberFormatterApiTest::formatUnitsAliases() {
6058 IcuTestErrorCode status(*this, "formatUnitsAliases");
6059
6060 struct TestCase {
6061 const MeasureUnit measureUnit;
6062 const UnicodeString expectedFormat;
6063 } testCases[]{
6064 // Aliases
6065 {MeasureUnit::getMilligramPerDeciliter(), u"2 milligrams per deciliter"},
6066 {MeasureUnit::getLiterPer100Kilometers(), u"2 liters per 100 kilometers"},
6067 {MeasureUnit::getPartPerMillion(), u"2 parts per million"},
6068 {MeasureUnit::getMillimeterOfMercury(), u"2 millimeters of mercury"},
6069
6070 // Replacements
6071 {MeasureUnit::getMilligramOfglucosePerDeciliter(), u"2 milligrams per deciliter"},
6072 {MeasureUnit::forIdentifier("millimeter-ofhg", status), u"2 millimeters of mercury"},
6073 {MeasureUnit::forIdentifier("liter-per-100-kilometer", status), u"2 liters per 100 kilometers"},
6074 {MeasureUnit::forIdentifier("permillion", status), u"2 parts per million"},
6075 };
6076
6077 for (const auto &testCase : testCases) {
6078 UnicodeString actualFormat = NumberFormatter::withLocale(icu::Locale::getEnglish())
6079 .unit(testCase.measureUnit)
6080 .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME)
6081 .formatDouble(2.0, status)
6082 .toString(status);
6083
6084 assertEquals("test unit aliases", testCase.expectedFormat, actualFormat);
6085 }
6086 }
6087
testIssue22378()6088 void NumberFormatterApiTest::testIssue22378() {
6089 IcuTestErrorCode status(*this, "testIssue22378");
6090
6091 // I checked the results before the fix and everything works the same except
6092 // "fr-FR-u-mu-fahrenhe" and "fr_FR@mu=fahrenhe"
6093 struct TestCase {
6094 const std::string localeId;
6095 const UnicodeString expectedFormat;
6096 } testCases[]{
6097 {"en-US", u"73\u00B0F"},
6098 {"en-US-u-mu-fahrenhe", u"73\u00B0F"},
6099 // Unlike ULocale, forLanguageTag fails wih U_ILLEGAL_ARGUMENT_ERROR
6100 // because fahrenheit is not valid value for -u-mu-
6101 // {"en-US-u-mu-fahrenheit", u"73\u00B0F"},
6102 {"en-US-u-mu-celsius", u"23\u00B0C"},
6103 {"en-US-u-mu-badvalue", u"73\u00B0F"},
6104 {"en_US@mu=fahrenhe", u"73\u00B0F"},
6105 {"en_US@mu=fahrenheit", u"73\u00B0F"},
6106 {"en_US@mu=celsius", u"23\u00B0C"},
6107 {"en_US@mu=badvalue", u"73\u00B0F"},
6108
6109 {"fr-FR", u"23\u202F\u00B0C"},
6110 {"fr-FR-u-mu-fahrenhe", u"73\u202F\u00B0F"},
6111 // Unlike ULocale, forLanguageTag fails wih U_ILLEGAL_ARGUMENT_ERROR
6112 // because fahrenheit is not valid value for -u-mu-
6113 // {"fr-FR-u-mu-fahrenheit", u"23\u202F\u00B0C"},
6114 {"fr-FR-u-mu-celsius", u"23\u202F\u00B0C"},
6115 {"fr-FR-u-mu-badvalue", u"23\u202F\u00B0C"},
6116 {"fr_FR@mu=fahrenhe", u"73\u202F\u00B0F"},
6117 {"fr_FR@mu=fahrenheit", u"73\u202F\u00B0F"},
6118 {"fr_FR@mu=celsius", u"23\u202F\u00B0C"},
6119 {"fr_FR@mu=badvalue", u"23\u202F\u00B0C"},
6120 };
6121
6122 UnlocalizedNumberFormatter formatter = NumberFormatter::with()
6123 .usage("weather")
6124 .unit(MeasureUnit::getCelsius());
6125 double value = 23.0;
6126
6127 for (const auto &testCase : testCases) {
6128 std::string localeId = testCase.localeId;
6129 const Locale locale = (localeId.find("@") != std::string::npos)
6130 ? Locale(localeId.c_str())
6131 : Locale::forLanguageTag(localeId, status);
6132 UnicodeString actualFormat = formatter.locale(locale)
6133 .formatDouble(value, status)
6134 .toString(status);
6135 assertEquals(u"-u-mu- honored (" + UnicodeString(localeId.c_str()) + u")",
6136 testCase.expectedFormat, actualFormat);
6137 }
6138
6139 UnicodeString result = formatter.locale("en-US").formatDouble(value, status).getOutputUnit(status).getIdentifier();
6140 assertEquals("Testing default -u-mu- for en-US", MeasureUnit::getFahrenheit().getIdentifier(), result);
6141 result = formatter.locale("fr-FR").formatDouble(value, status).getOutputUnit(status).getIdentifier();
6142 assertEquals("Testing default -u-mu- for fr-FR", MeasureUnit::getCelsius().getIdentifier(), result);
6143 }
6144
6145 /* For skeleton comparisons: this checks the toSkeleton output for `f` and for
6146 * `conciseSkeleton` against the normalized version of `uskeleton` - this does
6147 * not round-trip uskeleton itself.
6148 *
6149 * If `conciseSkeleton` starts with a "~", its round-trip check is skipped.
6150 *
6151 * If `uskeleton` is nullptr, toSkeleton is expected to return an
6152 * U_UNSUPPORTED_ERROR.
6153 */
assertFormatDescending(const char16_t * umessage,const char16_t * uskeleton,const char16_t * conciseSkeleton,const UnlocalizedNumberFormatter & f,Locale locale,...)6154 void NumberFormatterApiTest::assertFormatDescending(
6155 const char16_t* umessage,
6156 const char16_t* uskeleton,
6157 const char16_t* conciseSkeleton,
6158 const UnlocalizedNumberFormatter& f,
6159 Locale locale,
6160 ...) {
6161 va_list args;
6162 va_start(args, locale);
6163 UnicodeString message(true, umessage, -1);
6164 static double inputs[] = {87650, 8765, 876.5, 87.65, 8.765, 0.8765, 0.08765, 0.008765, 0};
6165 const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation
6166 const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation
6167 IcuTestErrorCode status(*this, "assertFormatDescending");
6168 status.setScope(message);
6169 UnicodeString expecteds[10];
6170 for (int16_t i = 0; i < 9; i++) {
6171 char16_t caseNumber = u'0' + i;
6172 double d = inputs[i];
6173 UnicodeString expected = va_arg(args, const char16_t*);
6174 expecteds[i] = expected;
6175 UnicodeString actual1 = l1.formatDouble(d, status).toString(status);
6176 assertSuccess(message + u": Unsafe Path: " + caseNumber, status);
6177 assertEquals(message + u": Unsafe Path: " + caseNumber, expected, actual1);
6178 UnicodeString actual2 = l2.formatDouble(d, status).toString(status);
6179 assertSuccess(message + u": Safe Path: " + caseNumber, status);
6180 assertEquals(message + u": Safe Path: " + caseNumber, expected, actual2);
6181 }
6182 if (uskeleton != nullptr) { // if null, skeleton is declared as undefined.
6183 UnicodeString skeleton(true, uskeleton, -1);
6184 // Only compare normalized skeletons: the tests need not provide the normalized forms.
6185 // Use the normalized form to construct the testing formatter to guarantee no loss of info.
6186 UnicodeString normalized = NumberFormatter::forSkeleton(skeleton, status).toSkeleton(status);
6187 assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status));
6188 LocalizedNumberFormatter l3 = NumberFormatter::forSkeleton(normalized, status).locale(locale);
6189 for (int32_t i = 0; i < 9; i++) {
6190 double d = inputs[i];
6191 UnicodeString actual3 = l3.formatDouble(d, status).toString(status);
6192 assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3);
6193 }
6194 // Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
6195 // If the concise skeleton starts with '~', disable the round-trip check.
6196 bool shouldRoundTrip = true;
6197 if (conciseSkeleton[0] == u'~') {
6198 conciseSkeleton++;
6199 shouldRoundTrip = false;
6200 }
6201 LocalizedNumberFormatter l4 = NumberFormatter::forSkeleton(conciseSkeleton, status).locale(locale);
6202 if (shouldRoundTrip) {
6203 assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton(status));
6204 }
6205 for (int32_t i = 0; i < 9; i++) {
6206 double d = inputs[i];
6207 UnicodeString actual4 = l4.formatDouble(d, status).toString(status);
6208 assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual4);
6209 }
6210 } else {
6211 assertUndefinedSkeleton(f);
6212 }
6213 }
6214
6215 /* For skeleton comparisons: this checks the toSkeleton output for `f` and for
6216 * `conciseSkeleton` against the normalized version of `uskeleton` - this does
6217 * not round-trip uskeleton itself.
6218 *
6219 * If `conciseSkeleton` starts with a "~", its round-trip check is skipped.
6220 *
6221 * If `uskeleton` is nullptr, toSkeleton is expected to return an
6222 * U_UNSUPPORTED_ERROR.
6223 */
assertFormatDescendingBig(const char16_t * umessage,const char16_t * uskeleton,const char16_t * conciseSkeleton,const UnlocalizedNumberFormatter & f,Locale locale,...)6224 void NumberFormatterApiTest::assertFormatDescendingBig(
6225 const char16_t* umessage,
6226 const char16_t* uskeleton,
6227 const char16_t* conciseSkeleton,
6228 const UnlocalizedNumberFormatter& f,
6229 Locale locale,
6230 ...) {
6231 va_list args;
6232 va_start(args, locale);
6233 UnicodeString message(true, umessage, -1);
6234 static double inputs[] = {87650000, 8765000, 876500, 87650, 8765, 876.5, 87.65, 8.765, 0};
6235 const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation
6236 const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation
6237 IcuTestErrorCode status(*this, "assertFormatDescendingBig");
6238 status.setScope(message);
6239 UnicodeString expecteds[10];
6240 for (int16_t i = 0; i < 9; i++) {
6241 char16_t caseNumber = u'0' + i;
6242 double d = inputs[i];
6243 UnicodeString expected = va_arg(args, const char16_t*);
6244 expecteds[i] = expected;
6245 UnicodeString actual1 = l1.formatDouble(d, status).toString(status);
6246 assertSuccess(message + u": Unsafe Path: " + caseNumber, status);
6247 assertEquals(message + u": Unsafe Path: " + caseNumber, expected, actual1);
6248 UnicodeString actual2 = l2.formatDouble(d, status).toString(status);
6249 assertSuccess(message + u": Safe Path: " + caseNumber, status);
6250 assertEquals(message + u": Safe Path: " + caseNumber, expected, actual2);
6251 }
6252 if (uskeleton != nullptr) { // if null, skeleton is declared as undefined.
6253 UnicodeString skeleton(true, uskeleton, -1);
6254 // Only compare normalized skeletons: the tests need not provide the normalized forms.
6255 // Use the normalized form to construct the testing formatter to guarantee no loss of info.
6256 UnicodeString normalized = NumberFormatter::forSkeleton(skeleton, status).toSkeleton(status);
6257 assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status));
6258 LocalizedNumberFormatter l3 = NumberFormatter::forSkeleton(normalized, status).locale(locale);
6259 for (int32_t i = 0; i < 9; i++) {
6260 double d = inputs[i];
6261 UnicodeString actual3 = l3.formatDouble(d, status).toString(status);
6262 assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3);
6263 }
6264 // Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
6265 // If the concise skeleton starts with '~', disable the round-trip check.
6266 bool shouldRoundTrip = true;
6267 if (conciseSkeleton[0] == u'~') {
6268 conciseSkeleton++;
6269 shouldRoundTrip = false;
6270 }
6271 LocalizedNumberFormatter l4 = NumberFormatter::forSkeleton(conciseSkeleton, status).locale(locale);
6272 if (shouldRoundTrip) {
6273 assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton(status));
6274 }
6275 for (int32_t i = 0; i < 9; i++) {
6276 double d = inputs[i];
6277 UnicodeString actual4 = l4.formatDouble(d, status).toString(status);
6278 assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual4);
6279 }
6280 } else {
6281 assertUndefinedSkeleton(f);
6282 }
6283 }
6284
6285 /* For skeleton comparisons: this checks the toSkeleton output for `f` and for
6286 * `conciseSkeleton` against the normalized version of `uskeleton` - this does
6287 * not round-trip uskeleton itself.
6288 *
6289 * If `conciseSkeleton` starts with a "~", its round-trip check is skipped.
6290 *
6291 * If `uskeleton` is nullptr, toSkeleton is expected to return an
6292 * U_UNSUPPORTED_ERROR.
6293 */
6294 FormattedNumber
assertFormatSingle(const char16_t * umessage,const char16_t * uskeleton,const char16_t * conciseSkeleton,const UnlocalizedNumberFormatter & f,Locale locale,double input,const UnicodeString & expected)6295 NumberFormatterApiTest::assertFormatSingle(
6296 const char16_t* umessage,
6297 const char16_t* uskeleton,
6298 const char16_t* conciseSkeleton,
6299 const UnlocalizedNumberFormatter& f,
6300 Locale locale,
6301 double input,
6302 const UnicodeString& expected) {
6303 UnicodeString message(true, umessage, -1);
6304 const LocalizedNumberFormatter l1 = f.threshold(0).locale(locale); // no self-regulation
6305 const LocalizedNumberFormatter l2 = f.threshold(1).locale(locale); // all self-regulation
6306 IcuTestErrorCode status(*this, "assertFormatSingle");
6307 status.setScope(message);
6308 FormattedNumber result1 = l1.formatDouble(input, status);
6309 UnicodeString actual1 = result1.toString(status);
6310 assertSuccess(message + u": Unsafe Path", status);
6311 assertEquals(message + u": Unsafe Path", expected, actual1);
6312 UnicodeString actual2 = l2.formatDouble(input, status).toString(status);
6313 assertSuccess(message + u": Safe Path", status);
6314 assertEquals(message + u": Safe Path", expected, actual2);
6315 if (uskeleton != nullptr) { // if null, skeleton is declared as undefined.
6316 UnicodeString skeleton(true, uskeleton, -1);
6317 // Only compare normalized skeletons: the tests need not provide the normalized forms.
6318 // Use the normalized form to construct the testing formatter to ensure no loss of info.
6319 UnicodeString normalized = NumberFormatter::forSkeleton(skeleton, status).toSkeleton(status);
6320 assertEquals(message + ": Skeleton", normalized, f.toSkeleton(status));
6321 LocalizedNumberFormatter l3 = NumberFormatter::forSkeleton(normalized, status).locale(locale);
6322 UnicodeString actual3 = l3.formatDouble(input, status).toString(status);
6323 assertEquals(message + ": Skeleton Path: '" + normalized + "': " + input, expected, actual3);
6324 // Concise skeletons should have same output, and usually round-trip to the normalized skeleton.
6325 // If the concise skeleton starts with '~', disable the round-trip check.
6326 bool shouldRoundTrip = true;
6327 if (conciseSkeleton[0] == u'~') {
6328 conciseSkeleton++;
6329 shouldRoundTrip = false;
6330 }
6331 LocalizedNumberFormatter l4 = NumberFormatter::forSkeleton(conciseSkeleton, status).locale(locale);
6332 if (shouldRoundTrip) {
6333 assertEquals(message + ": Concise Skeleton:", normalized, l4.toSkeleton(status));
6334 }
6335 UnicodeString actual4 = l4.formatDouble(input, status).toString(status);
6336 assertEquals(message + ": Concise Skeleton Path: '" + normalized + "': " + input, expected, actual4);
6337 } else {
6338 assertUndefinedSkeleton(f);
6339 }
6340 return result1;
6341 }
6342
assertUndefinedSkeleton(const UnlocalizedNumberFormatter & f)6343 void NumberFormatterApiTest::assertUndefinedSkeleton(const UnlocalizedNumberFormatter& f) {
6344 UErrorCode status = U_ZERO_ERROR;
6345 UnicodeString skeleton = f.toSkeleton(status);
6346 assertEquals(
6347 u"Expect toSkeleton to fail, but passed, producing: " + skeleton,
6348 U_UNSUPPORTED_ERROR,
6349 status);
6350 }
6351
assertNumberFieldPositions(const char16_t * message,const FormattedNumber & formattedNumber,const UFieldPosition * expectedFieldPositions,int32_t length)6352 void NumberFormatterApiTest::assertNumberFieldPositions(
6353 const char16_t* message,
6354 const FormattedNumber& formattedNumber,
6355 const UFieldPosition* expectedFieldPositions,
6356 int32_t length) {
6357 IcuTestErrorCode status(*this, "assertNumberFieldPositions");
6358
6359 // Check FormattedValue functions
6360 checkFormattedValue(
6361 message,
6362 static_cast<const FormattedValue&>(formattedNumber),
6363 formattedNumber.toString(status),
6364 UFIELD_CATEGORY_NUMBER,
6365 expectedFieldPositions,
6366 length);
6367 }
6368
6369
6370 #endif /* #if !UCONFIG_NO_FORMATTING */
6371