blob: a47c675c97489dbde147668266e68d77ec22aa97 [file] [log] [blame]
Frank Tang3e05d9d2021-11-08 14:04:04 -08001// © 2018 and later: Unicode, Inc. and others.
2// License & terms of use: http://www.unicode.org/copyright.html
3
4#include "unicode/utypes.h"
5
6#if !UCONFIG_NO_FORMATTING
7
8#include "numbertest.h"
9#include "unicode/numberrangeformatter.h"
10
11#include <cmath>
12#include <numparse_affixes.h>
13
14// Horrible workaround for the lack of a status code in the constructor...
15// (Also affects numbertest_api.cpp)
16UErrorCode globalNumberRangeFormatterTestStatus = U_ZERO_ERROR;
17
18NumberRangeFormatterTest::NumberRangeFormatterTest()
19 : NumberRangeFormatterTest(globalNumberRangeFormatterTestStatus) {
20}
21
22NumberRangeFormatterTest::NumberRangeFormatterTest(UErrorCode& status)
23 : USD(u"USD", status),
24 CHF(u"CHF", status),
25 GBP(u"GBP", status),
26 PTE(u"PTE", status) {
27
28 // Check for error on the first MeasureUnit in case there is no data
29 LocalPointer<MeasureUnit> unit(MeasureUnit::createMeter(status));
30 if (U_FAILURE(status)) {
31 dataerrln("%s %d status = %s", __FILE__, __LINE__, u_errorName(status));
32 return;
33 }
34 METER = *unit;
35
36 KILOMETER = *LocalPointer<MeasureUnit>(MeasureUnit::createKilometer(status));
37 FAHRENHEIT = *LocalPointer<MeasureUnit>(MeasureUnit::createFahrenheit(status));
38 KELVIN = *LocalPointer<MeasureUnit>(MeasureUnit::createKelvin(status));
39}
40
41void NumberRangeFormatterTest::runIndexedTest(int32_t index, UBool exec, const char*& name, char*) {
42 if (exec) {
43 logln("TestSuite NumberRangeFormatterTest: ");
44 }
45 TESTCASE_AUTO_BEGIN;
46 TESTCASE_AUTO(testSanity);
47 TESTCASE_AUTO(testBasic);
48 TESTCASE_AUTO(testCollapse);
49 TESTCASE_AUTO(testIdentity);
50 TESTCASE_AUTO(testDifferentFormatters);
51 TESTCASE_AUTO(testNaNInfinity);
52 TESTCASE_AUTO(testPlurals);
53 TESTCASE_AUTO(testFieldPositions);
54 TESTCASE_AUTO(testCopyMove);
55 TESTCASE_AUTO(toObject);
56 TESTCASE_AUTO(testGetDecimalNumbers);
57 TESTCASE_AUTO(test21684_Performance);
58 TESTCASE_AUTO(test21358_SignPosition);
59 TESTCASE_AUTO(test21683_StateLeak);
60 TESTCASE_AUTO_END;
61}
62
63void NumberRangeFormatterTest::testSanity() {
64 IcuTestErrorCode status(*this, "testSanity");
65 LocalizedNumberRangeFormatter lnrf1 = NumberRangeFormatter::withLocale("en-us");
66 LocalizedNumberRangeFormatter lnrf2 = NumberRangeFormatter::with().locale("en-us");
67 assertEquals("Formatters should have same behavior 1",
68 lnrf1.formatFormattableRange(4, 6, status).toString(status),
69 lnrf2.formatFormattableRange(4, 6, status).toString(status));
70}
71
72void NumberRangeFormatterTest::testBasic() {
73 assertFormatRange(
74 u"Basic",
75 NumberRangeFormatter::with(),
76 Locale("en-us"),
77 u"1–5",
78 u"~5",
79 u"~5",
80 u"0–3",
81 u"~0",
82 u"3–3,000",
83 u"3,000–5,000",
84 u"4,999–5,001",
85 u"~5,000",
86 u"5,000–5,000,000");
87
88 assertFormatRange(
89 u"Basic with units",
90 NumberRangeFormatter::with()
91 .numberFormatterBoth(NumberFormatter::with().unit(METER)),
92 Locale("en-us"),
93 u"1–5 m",
94 u"~5 m",
95 u"~5 m",
96 u"0–3 m",
97 u"~0 m",
98 u"3–3,000 m",
99 u"3,000–5,000 m",
100 u"4,999–5,001 m",
101 u"~5,000 m",
102 u"5,000–5,000,000 m");
103
104 assertFormatRange(
105 u"Basic with different units",
106 NumberRangeFormatter::with()
107 .numberFormatterFirst(NumberFormatter::with().unit(METER))
108 .numberFormatterSecond(NumberFormatter::with().unit(KILOMETER)),
109 Locale("en-us"),
110 u"1 m – 5 km",
111 u"5 m – 5 km",
112 u"5 m – 5 km",
113 u"0 m – 3 km",
114 u"0 m – 0 km",
115 u"3 m – 3,000 km",
116 u"3,000 m – 5,000 km",
117 u"4,999 m – 5,001 km",
118 u"5,000 m – 5,000 km",
119 u"5,000 m – 5,000,000 km");
120
121 assertFormatRange(
122 u"Basic long unit",
123 NumberRangeFormatter::with()
124 .numberFormatterBoth(NumberFormatter::with().unit(METER).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
125 Locale("en-us"),
126 u"1–5 meters",
127 u"~5 meters",
128 u"~5 meters",
129 u"0–3 meters",
130 u"~0 meters",
131 u"3–3,000 meters",
132 u"3,000–5,000 meters",
133 u"4,999–5,001 meters",
134 u"~5,000 meters",
135 u"5,000–5,000,000 meters");
136
137 assertFormatRange(
138 u"Non-English locale and unit",
139 NumberRangeFormatter::with()
140 .numberFormatterBoth(NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
141 Locale("fr-FR"),
142 u"1–5\u00A0degrés Fahrenheit",
143 u"≃5\u00A0degrés Fahrenheit",
144 u"≃5\u00A0degrés Fahrenheit",
145 u"0–3\u00A0degrés Fahrenheit",
146 u"≃0\u00A0degré Fahrenheit",
147 u"3–3\u202F000\u00A0degrés Fahrenheit",
148 u"3\u202F000–5\u202F000\u00A0degrés Fahrenheit",
149 u"4\u202F999–5\u202F001\u00A0degrés Fahrenheit",
150 u"≃5\u202F000\u00A0degrés Fahrenheit",
151 u"5\u202F000–5\u202F000\u202F000\u00A0degrés Fahrenheit");
152
153 assertFormatRange(
154 u"Locale with custom range separator",
155 NumberRangeFormatter::with(),
156 Locale("ja"),
157 u"1~5",
158 u"約5",
159 u"約5",
160 u"0~3",
161 u"約0",
162 u"3~3,000",
163 u"3,000~5,000",
164 u"4,999~5,001",
165 u"約5,000",
166 u"5,000~5,000,000");
167
168 assertFormatRange(
169 u"Locale that already has spaces around range separator",
170 NumberRangeFormatter::with()
171 .collapse(UNUM_RANGE_COLLAPSE_NONE)
172 .numberFormatterBoth(NumberFormatter::with().unit(KELVIN)),
173 Locale("hr"),
174 u"1 K – 5 K",
175 u"~5 K",
176 u"~5 K",
177 u"0 K – 3 K",
178 u"~0 K",
179 u"3 K – 3.000 K",
180 u"3.000 K – 5.000 K",
181 u"4.999 K – 5.001 K",
182 u"~5.000 K",
183 u"5.000 K – 5.000.000 K");
184
185 assertFormatRange(
186 u"Locale with custom numbering system and no plural ranges data",
187 NumberRangeFormatter::with(),
188 Locale("shn@numbers=beng"),
189 // 012459 = ০১৩৪৫৯
190 u"১–৫",
191 u"~৫",
192 u"~৫",
193 u"০–৩",
194 u"~০",
195 u"৩–৩,০০০",
196 u"৩,০০০–৫,০০০",
197 u"৪,৯৯৯–৫,০০১",
198 u"~৫,০০০",
199 u"৫,০০০–৫,০০০,০০০");
200
201 assertFormatRange(
202 u"Portuguese currency",
203 NumberRangeFormatter::with()
204 .numberFormatterBoth(NumberFormatter::with().unit(PTE)),
205 Locale("pt-PT"),
206 u"1$00 - 5$00 \u200B",
207 u"~5$00 \u200B",
208 u"~5$00 \u200B",
209 u"0$00 - 3$00 \u200B",
210 u"~0$00 \u200B",
211 u"3$00 - 3000$00 \u200B",
212 u"3000$00 - 5000$00 \u200B",
213 u"4999$00 - 5001$00 \u200B",
214 u"~5000$00 \u200B",
215 u"5000$00 - 5,000,000$00 \u200B");
216}
217
218void NumberRangeFormatterTest::testCollapse() {
219 assertFormatRange(
220 u"Default collapse on currency (default rounding)",
221 NumberRangeFormatter::with()
222 .numberFormatterBoth(NumberFormatter::with().unit(USD)),
223 Locale("en-us"),
224 u"$1.00 – $5.00",
225 u"~$5.00",
226 u"~$5.00",
227 u"$0.00 – $3.00",
228 u"~$0.00",
229 u"$3.00 – $3,000.00",
230 u"$3,000.00 – $5,000.00",
231 u"$4,999.00 – $5,001.00",
232 u"~$5,000.00",
233 u"$5,000.00 – $5,000,000.00");
234
235 assertFormatRange(
236 u"Default collapse on currency",
237 NumberRangeFormatter::with()
238 .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
239 Locale("en-us"),
240 u"$1 – $5",
241 u"~$5",
242 u"~$5",
243 u"$0 – $3",
244 u"~$0",
245 u"$3 – $3,000",
246 u"$3,000 – $5,000",
247 u"$4,999 – $5,001",
248 u"~$5,000",
249 u"$5,000 – $5,000,000");
250
251 assertFormatRange(
252 u"No collapse on currency",
253 NumberRangeFormatter::with()
254 .collapse(UNUM_RANGE_COLLAPSE_NONE)
255 .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
256 Locale("en-us"),
257 u"$1 – $5",
258 u"~$5",
259 u"~$5",
260 u"$0 – $3",
261 u"~$0",
262 u"$3 – $3,000",
263 u"$3,000 – $5,000",
264 u"$4,999 – $5,001",
265 u"~$5,000",
266 u"$5,000 – $5,000,000");
267
268 assertFormatRange(
269 u"Unit collapse on currency",
270 NumberRangeFormatter::with()
271 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
272 .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
273 Locale("en-us"),
274 u"$1–5",
275 u"~$5",
276 u"~$5",
277 u"$0–3",
278 u"~$0",
279 u"$3–3,000",
280 u"$3,000–5,000",
281 u"$4,999–5,001",
282 u"~$5,000",
283 u"$5,000–5,000,000");
284
285 assertFormatRange(
286 u"All collapse on currency",
287 NumberRangeFormatter::with()
288 .collapse(UNUM_RANGE_COLLAPSE_ALL)
289 .numberFormatterBoth(NumberFormatter::with().unit(USD).precision(Precision::integer())),
290 Locale("en-us"),
291 u"$1–5",
292 u"~$5",
293 u"~$5",
294 u"$0–3",
295 u"~$0",
296 u"$3–3,000",
297 u"$3,000–5,000",
298 u"$4,999–5,001",
299 u"~$5,000",
300 u"$5,000–5,000,000");
301
302 assertFormatRange(
303 u"Default collapse on currency ISO code",
304 NumberRangeFormatter::with()
305 .numberFormatterBoth(NumberFormatter::with()
306 .unit(GBP)
307 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
308 .precision(Precision::integer())),
309 Locale("en-us"),
310 u"GBP 1–5",
311 u"~GBP 5", // TODO: Fix this at some point
312 u"~GBP 5",
313 u"GBP 0–3",
314 u"~GBP 0",
315 u"GBP 3–3,000",
316 u"GBP 3,000–5,000",
317 u"GBP 4,999–5,001",
318 u"~GBP 5,000",
319 u"GBP 5,000–5,000,000");
320
321 assertFormatRange(
322 u"No collapse on currency ISO code",
323 NumberRangeFormatter::with()
324 .collapse(UNUM_RANGE_COLLAPSE_NONE)
325 .numberFormatterBoth(NumberFormatter::with()
326 .unit(GBP)
327 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
328 .precision(Precision::integer())),
329 Locale("en-us"),
330 u"GBP 1 – GBP 5",
331 u"~GBP 5", // TODO: Fix this at some point
332 u"~GBP 5",
333 u"GBP 0 – GBP 3",
334 u"~GBP 0",
335 u"GBP 3 – GBP 3,000",
336 u"GBP 3,000 – GBP 5,000",
337 u"GBP 4,999 – GBP 5,001",
338 u"~GBP 5,000",
339 u"GBP 5,000 – GBP 5,000,000");
340
341 assertFormatRange(
342 u"Unit collapse on currency ISO code",
343 NumberRangeFormatter::with()
344 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
345 .numberFormatterBoth(NumberFormatter::with()
346 .unit(GBP)
347 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
348 .precision(Precision::integer())),
349 Locale("en-us"),
350 u"GBP 1–5",
351 u"~GBP 5", // TODO: Fix this at some point
352 u"~GBP 5",
353 u"GBP 0–3",
354 u"~GBP 0",
355 u"GBP 3–3,000",
356 u"GBP 3,000–5,000",
357 u"GBP 4,999–5,001",
358 u"~GBP 5,000",
359 u"GBP 5,000–5,000,000");
360
361 assertFormatRange(
362 u"All collapse on currency ISO code",
363 NumberRangeFormatter::with()
364 .collapse(UNUM_RANGE_COLLAPSE_ALL)
365 .numberFormatterBoth(NumberFormatter::with()
366 .unit(GBP)
367 .unitWidth(UNUM_UNIT_WIDTH_ISO_CODE)
368 .precision(Precision::integer())),
369 Locale("en-us"),
370 u"GBP 1–5",
371 u"~GBP 5", // TODO: Fix this at some point
372 u"~GBP 5",
373 u"GBP 0–3",
374 u"~GBP 0",
375 u"GBP 3–3,000",
376 u"GBP 3,000–5,000",
377 u"GBP 4,999–5,001",
378 u"~GBP 5,000",
379 u"GBP 5,000–5,000,000");
380
381 // Default collapse on measurement unit is in testBasic()
382
383 assertFormatRange(
384 u"No collapse on measurement unit",
385 NumberRangeFormatter::with()
386 .collapse(UNUM_RANGE_COLLAPSE_NONE)
387 .numberFormatterBoth(NumberFormatter::with().unit(METER)),
388 Locale("en-us"),
389 u"1 m – 5 m",
390 u"~5 m",
391 u"~5 m",
392 u"0 m – 3 m",
393 u"~0 m",
394 u"3 m – 3,000 m",
395 u"3,000 m – 5,000 m",
396 u"4,999 m – 5,001 m",
397 u"~5,000 m",
398 u"5,000 m – 5,000,000 m");
399
400 assertFormatRange(
401 u"Unit collapse on measurement unit",
402 NumberRangeFormatter::with()
403 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
404 .numberFormatterBoth(NumberFormatter::with().unit(METER)),
405 Locale("en-us"),
406 u"1–5 m",
407 u"~5 m",
408 u"~5 m",
409 u"0–3 m",
410 u"~0 m",
411 u"3–3,000 m",
412 u"3,000–5,000 m",
413 u"4,999–5,001 m",
414 u"~5,000 m",
415 u"5,000–5,000,000 m");
416
417 assertFormatRange(
418 u"All collapse on measurement unit",
419 NumberRangeFormatter::with()
420 .collapse(UNUM_RANGE_COLLAPSE_ALL)
421 .numberFormatterBoth(NumberFormatter::with().unit(METER)),
422 Locale("en-us"),
423 u"1–5 m",
424 u"~5 m",
425 u"~5 m",
426 u"0–3 m",
427 u"~0 m",
428 u"3–3,000 m",
429 u"3,000–5,000 m",
430 u"4,999–5,001 m",
431 u"~5,000 m",
432 u"5,000–5,000,000 m");
433
434 assertFormatRange(
435 u"Default collapse, long-form compact notation",
436 NumberRangeFormatter::with()
437 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
438 Locale("de-CH"),
439 u"1–5",
440 u"≈5",
441 u"≈5",
442 u"0–3",
443 u"≈0",
444 u"3–3 Tausend",
445 u"3–5 Tausend",
446 u"≈5 Tausend",
447 u"≈5 Tausend",
448 u"5 Tausend – 5 Millionen");
449
450 assertFormatRange(
451 u"Unit collapse, long-form compact notation",
452 NumberRangeFormatter::with()
453 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
454 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactLong())),
455 Locale("de-CH"),
456 u"1–5",
457 u"≈5",
458 u"≈5",
459 u"0–3",
460 u"≈0",
461 u"3–3 Tausend",
462 u"3 Tausend – 5 Tausend",
463 u"≈5 Tausend",
464 u"≈5 Tausend",
465 u"5 Tausend – 5 Millionen");
466
467 assertFormatRange(
468 u"Default collapse on measurement unit with compact-short notation",
469 NumberRangeFormatter::with()
470 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
471 Locale("en-us"),
472 u"1–5 m",
473 u"~5 m",
474 u"~5 m",
475 u"0–3 m",
476 u"~0 m",
477 u"3–3K m",
478 u"3K – 5K m",
479 u"~5K m",
480 u"~5K m",
481 u"5K – 5M m");
482
483 assertFormatRange(
484 u"No collapse on measurement unit with compact-short notation",
485 NumberRangeFormatter::with()
486 .collapse(UNUM_RANGE_COLLAPSE_NONE)
487 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
488 Locale("en-us"),
489 u"1 m – 5 m",
490 u"~5 m",
491 u"~5 m",
492 u"0 m – 3 m",
493 u"~0 m",
494 u"3 m – 3K m",
495 u"3K m – 5K m",
496 u"~5K m",
497 u"~5K m",
498 u"5K m – 5M m");
499
500 assertFormatRange(
501 u"Unit collapse on measurement unit with compact-short notation",
502 NumberRangeFormatter::with()
503 .collapse(UNUM_RANGE_COLLAPSE_UNIT)
504 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
505 Locale("en-us"),
506 u"1–5 m",
507 u"~5 m",
508 u"~5 m",
509 u"0–3 m",
510 u"~0 m",
511 u"3–3K m",
512 u"3K – 5K m",
513 u"~5K m",
514 u"~5K m",
515 u"5K – 5M m");
516
517 assertFormatRange(
518 u"All collapse on measurement unit with compact-short notation",
519 NumberRangeFormatter::with()
520 .collapse(UNUM_RANGE_COLLAPSE_ALL)
521 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort()).unit(METER)),
522 Locale("en-us"),
523 u"1–5 m",
524 u"~5 m",
525 u"~5 m",
526 u"0–3 m",
527 u"~0 m",
528 u"3–3K m",
529 u"3–5K m", // this one is the key use case for ALL
530 u"~5K m",
531 u"~5K m",
532 u"5K – 5M m");
533
534 assertFormatRange(
535 u"No collapse on scientific notation",
536 NumberRangeFormatter::with()
537 .collapse(UNUM_RANGE_COLLAPSE_NONE)
538 .numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
539 Locale("en-us"),
540 u"1E0 – 5E0",
541 u"~5E0",
542 u"~5E0",
543 u"0E0 – 3E0",
544 u"~0E0",
545 u"3E0 – 3E3",
546 u"3E3 – 5E3",
547 u"4.999E3 – 5.001E3",
548 u"~5E3",
549 u"5E3 – 5E6");
550
551 assertFormatRange(
552 u"All collapse on scientific notation",
553 NumberRangeFormatter::with()
554 .collapse(UNUM_RANGE_COLLAPSE_ALL)
555 .numberFormatterBoth(NumberFormatter::with().notation(Notation::scientific())),
556 Locale("en-us"),
557 u"1–5E0",
558 u"~5E0",
559 u"~5E0",
560 u"0–3E0",
561 u"~0E0",
562 u"3E0 – 3E3",
563 u"3–5E3",
564 u"4.999–5.001E3",
565 u"~5E3",
566 u"5E3 – 5E6");
567
568 // TODO: Test compact currency?
569 // The code is not smart enough to differentiate the notation from the unit.
570}
571
572void NumberRangeFormatterTest::testIdentity() {
573 assertFormatRange(
574 u"Identity fallback Range",
575 NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_RANGE),
576 Locale("en-us"),
577 u"1–5",
578 u"5–5",
579 u"5–5",
580 u"0–3",
581 u"0–0",
582 u"3–3,000",
583 u"3,000–5,000",
584 u"4,999–5,001",
585 u"5,000–5,000",
586 u"5,000–5,000,000");
587
588 assertFormatRange(
589 u"Identity fallback Approximately or Single Value",
590 NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE),
591 Locale("en-us"),
592 u"1–5",
593 u"~5",
594 u"5",
595 u"0–3",
596 u"0",
597 u"3–3,000",
598 u"3,000–5,000",
599 u"4,999–5,001",
600 u"5,000",
601 u"5,000–5,000,000");
602
603 assertFormatRange(
604 u"Identity fallback Single Value",
605 NumberRangeFormatter::with().identityFallback(UNUM_IDENTITY_FALLBACK_SINGLE_VALUE),
606 Locale("en-us"),
607 u"1–5",
608 u"5",
609 u"5",
610 u"0–3",
611 u"0",
612 u"3–3,000",
613 u"3,000–5,000",
614 u"4,999–5,001",
615 u"5,000",
616 u"5,000–5,000,000");
617
618 assertFormatRange(
619 u"Identity fallback Approximately or Single Value with compact notation",
620 NumberRangeFormatter::with()
621 .identityFallback(UNUM_IDENTITY_FALLBACK_APPROXIMATELY_OR_SINGLE_VALUE)
622 .numberFormatterBoth(NumberFormatter::with().notation(Notation::compactShort())),
623 Locale("en-us"),
624 u"1–5",
625 u"~5",
626 u"5",
627 u"0–3",
628 u"0",
629 u"3–3K",
630 u"3K – 5K",
631 u"~5K",
632 u"5K",
633 u"5K – 5M");
634
635 assertFormatRange(
636 u"Approximately in middle of unit string",
637 NumberRangeFormatter::with().numberFormatterBoth(
638 NumberFormatter::with().unit(FAHRENHEIT).unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)),
639 Locale("zh-Hant"),
640 u"華氏 1-5 度",
641 u"華氏 ~5 度",
642 u"華氏 ~5 度",
643 u"華氏 0-3 度",
644 u"華氏 ~0 度",
645 u"華氏 3-3,000 度",
646 u"華氏 3,000-5,000 度",
647 u"華氏 4,999-5,001 度",
648 u"華氏 ~5,000 度",
649 u"華氏 5,000-5,000,000 度");
650}
651
652void NumberRangeFormatterTest::testDifferentFormatters() {
653 assertFormatRange(
654 u"Different rounding rules",
655 NumberRangeFormatter::with()
656 .numberFormatterFirst(NumberFormatter::with().precision(Precision::integer()))
657 .numberFormatterSecond(NumberFormatter::with().precision(Precision::fixedSignificantDigits(2))),
658 Locale("en-us"),
659 u"1–5.0",
660 u"5–5.0",
661 u"5–5.0",
662 u"0–3.0",
663 u"0–0.0",
664 u"3–3,000",
665 u"3,000–5,000",
666 u"4,999–5,000",
667 u"5,000–5,000", // TODO: Should this one be ~5,000?
668 u"5,000–5,000,000");
669}
670
671void NumberRangeFormatterTest::testNaNInfinity() {
672 IcuTestErrorCode status(*this, "testNaNInfinity");
673
674 auto lnf = NumberRangeFormatter::withLocale("en");
675 auto result1 = lnf.formatFormattableRange(-uprv_getInfinity(), 0, status);
676 auto result2 = lnf.formatFormattableRange(0, uprv_getInfinity(), status);
677 auto result3 = lnf.formatFormattableRange(-uprv_getInfinity(), uprv_getInfinity(), status);
678 auto result4 = lnf.formatFormattableRange(uprv_getNaN(), 0, status);
679 auto result5 = lnf.formatFormattableRange(0, uprv_getNaN(), status);
680 auto result6 = lnf.formatFormattableRange(uprv_getNaN(), uprv_getNaN(), status);
681 auto result7 = lnf.formatFormattableRange({"1000", status}, {"Infinity", status}, status);
682 auto result8 = lnf.formatFormattableRange({"-Infinity", status}, {"NaN", status}, status);
683
684 assertEquals("0 - inf", u"-∞ – 0", result1.toTempString(status));
685 assertEquals("-inf - 0", u"0–∞", result2.toTempString(status));
686 assertEquals("-inf - inf", u"-∞ – ∞", result3.toTempString(status));
687 assertEquals("NaN - 0", u"NaN–0", result4.toTempString(status));
688 assertEquals("0 - NaN", u"0–NaN", result5.toTempString(status));
689 assertEquals("NaN - NaN", u"~NaN", result6.toTempString(status));
690 assertEquals("1000 - inf", u"1,000–∞", result7.toTempString(status));
691 assertEquals("-inf - NaN", u"-∞ – NaN", result8.toTempString(status));
692}
693
694void NumberRangeFormatterTest::testPlurals() {
695 IcuTestErrorCode status(*this, "testPlurals");
696
697 // Locale sl has interesting plural forms:
698 // GBP{
699 // one{"britanski funt"}
700 // two{"britanska funta"}
701 // few{"britanski funti"}
702 // other{"britanskih funtov"}
703 // }
704 Locale locale("sl");
705
706 UnlocalizedNumberFormatter unf = NumberFormatter::with()
707 .unit(GBP)
708 .unitWidth(UNUM_UNIT_WIDTH_FULL_NAME)
709 .precision(Precision::integer());
710 LocalizedNumberFormatter lnf = unf.locale(locale);
711
712 // For comparison, run the non-range version of the formatter
713 assertEquals(Int64ToUnicodeString(1), u"1 britanski funt", lnf.formatDouble(1, status).toString(status));
714 assertEquals(Int64ToUnicodeString(2), u"2 britanska funta", lnf.formatDouble(2, status).toString(status));
715 assertEquals(Int64ToUnicodeString(3), u"3 britanski funti", lnf.formatDouble(3, status).toString(status));
716 assertEquals(Int64ToUnicodeString(5), u"5 britanskih funtov", lnf.formatDouble(5, status).toString(status));
717 if (status.errIfFailureAndReset()) { return; }
718
719 LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::with()
720 .numberFormatterBoth(unf)
721 .identityFallback(UNUM_IDENTITY_FALLBACK_RANGE)
722 .locale(locale);
723
724 struct TestCase {
725 double first;
726 double second;
727 const char16_t* expected;
728 } cases[] = {
729 {1, 1, u"1–1 britanski funti"}, // one + one -> few
730 {1, 2, u"1–2 britanska funta"}, // one + two -> two
731 {1, 3, u"1–3 britanski funti"}, // one + few -> few
732 {1, 5, u"1–5 britanskih funtov"}, // one + other -> other
733 {2, 1, u"2–1 britanski funti"}, // two + one -> few
734 {2, 2, u"2–2 britanska funta"}, // two + two -> two
735 {2, 3, u"2–3 britanski funti"}, // two + few -> few
736 {2, 5, u"2–5 britanskih funtov"}, // two + other -> other
737 {3, 1, u"3–1 britanski funti"}, // few + one -> few
738 {3, 2, u"3–2 britanska funta"}, // few + two -> two
739 {3, 3, u"3–3 britanski funti"}, // few + few -> few
740 {3, 5, u"3–5 britanskih funtov"}, // few + other -> other
741 {5, 1, u"5–1 britanski funti"}, // other + one -> few
742 {5, 2, u"5–2 britanska funta"}, // other + two -> two
743 {5, 3, u"5–3 britanski funti"}, // other + few -> few
744 {5, 5, u"5–5 britanskih funtov"}, // other + other -> other
745 };
746 for (auto& cas : cases) {
747 UnicodeString message = Int64ToUnicodeString(static_cast<int64_t>(cas.first));
748 message += u" ";
749 message += Int64ToUnicodeString(static_cast<int64_t>(cas.second));
750 status.setScope(message);
751 UnicodeString actual = lnrf.formatFormattableRange(cas.first, cas.second, status).toString(status);
752 assertEquals(message, cas.expected, actual);
753 status.errIfFailureAndReset();
754 }
755}
756
757void NumberRangeFormatterTest::testFieldPositions() {
758 {
759 const char16_t* message = u"Field position test 1";
760 const char16_t* expectedString = u"3K – 5K m";
761 FormattedNumberRange result = assertFormattedRangeEquals(
762 message,
763 NumberRangeFormatter::with()
764 .numberFormatterBoth(NumberFormatter::with()
765 .unit(METER)
766 .notation(Notation::compactShort()))
767 .locale("en-us"),
768 3000,
769 5000,
770 expectedString);
771 static const UFieldPositionWithCategory expectedFieldPositions[] = {
772 // category, field, begin index, end index
773 {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, 0, 2},
774 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 1},
775 {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 1, 2},
776 {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, 5, 7},
777 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 5, 6},
778 {UFIELD_CATEGORY_NUMBER, UNUM_COMPACT_FIELD, 6, 7},
779 {UFIELD_CATEGORY_NUMBER, UNUM_MEASURE_UNIT_FIELD, 8, 9}};
780 checkMixedFormattedValue(
781 message,
782 result,
783 expectedString,
784 expectedFieldPositions,
785 UPRV_LENGTHOF(expectedFieldPositions));
786 }
787
788 {
789 const char16_t* message = u"Field position test 2";
790 const char16_t* expectedString = u"87,654,321–98,765,432";
791 FormattedNumberRange result = assertFormattedRangeEquals(
792 message,
793 NumberRangeFormatter::withLocale("en-us"),
794 87654321,
795 98765432,
796 expectedString);
797 static const UFieldPositionWithCategory expectedFieldPositions[] = {
798 // category, field, begin index, end index
799 {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 0, 0, 10},
800 {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 2, 3},
801 {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 6, 7},
802 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 0, 10},
803 {UFIELD_CATEGORY_NUMBER_RANGE_SPAN, 1, 11, 21},
804 {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 13, 14},
805 {UFIELD_CATEGORY_NUMBER, UNUM_GROUPING_SEPARATOR_FIELD, 17, 18},
806 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 11, 21}};
807 checkMixedFormattedValue(
808 message,
809 result,
810 expectedString,
811 expectedFieldPositions,
812 UPRV_LENGTHOF(expectedFieldPositions));
813 }
Frank Tangd2858cb2022-04-08 20:34:12 -0700814
815 {
816 const char16_t* message = u"Field position with approximately sign";
817 const char16_t* expectedString = u"~-100";
818 FormattedNumberRange result = assertFormattedRangeEquals(
819 message,
820 NumberRangeFormatter::withLocale("en-us"),
821 -100,
822 -100,
823 expectedString);
824 static const UFieldPositionWithCategory expectedFieldPositions[] = {
825 // category, field, begin index, end index
826 {UFIELD_CATEGORY_NUMBER, UNUM_APPROXIMATELY_SIGN_FIELD, 0, 1},
827 {UFIELD_CATEGORY_NUMBER, UNUM_SIGN_FIELD, 1, 2},
828 {UFIELD_CATEGORY_NUMBER, UNUM_INTEGER_FIELD, 2, 5}};
829 checkMixedFormattedValue(
830 message,
831 result,
832 expectedString,
833 expectedFieldPositions,
834 UPRV_LENGTHOF(expectedFieldPositions));
835 }
Frank Tang3e05d9d2021-11-08 14:04:04 -0800836}
837
838void NumberRangeFormatterTest::testCopyMove() {
839 IcuTestErrorCode status(*this, "testCopyMove");
840
841 // Default constructors
842 LocalizedNumberRangeFormatter l1;
843 assertEquals("Initial behavior", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status));
844 if (status.errDataIfFailureAndReset()) { return; }
845
846 // Setup
847 l1 = NumberRangeFormatter::withLocale("fr-FR")
848 .numberFormatterBoth(NumberFormatter::with().unit(USD));
849 assertEquals("Currency behavior", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
850
851 // Copy constructor
852 LocalizedNumberRangeFormatter l2 = l1;
853 assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
854
855 // Move constructor
856 LocalizedNumberRangeFormatter l3 = std::move(l1);
857 assertEquals("Move constructor", u"1,00–5,00 $US", l3.formatFormattableRange(1, 5, status).toString(status));
858
859 // Reset objects for assignment tests
860 l1 = NumberRangeFormatter::withLocale("en-us");
861 l2 = NumberRangeFormatter::withLocale("en-us");
862 assertEquals("Rest behavior, l1", u"1–5", l1.formatFormattableRange(1, 5, status).toString(status));
863 assertEquals("Rest behavior, l2", u"1–5", l2.formatFormattableRange(1, 5, status).toString(status));
864
865 // Copy assignment
866 l1 = l3;
867 assertEquals("Copy constructor", u"1,00–5,00 $US", l1.formatFormattableRange(1, 5, status).toString(status));
868
869 // Move assignment
870 l2 = std::move(l3);
871 assertEquals("Copy constructor", u"1,00–5,00 $US", l2.formatFormattableRange(1, 5, status).toString(status));
872
873 // FormattedNumberRange
874 FormattedNumberRange result = l1.formatFormattableRange(1, 5, status);
875 assertEquals("FormattedNumberRange move constructor", u"1,00–5,00 $US", result.toString(status));
876 result = l1.formatFormattableRange(3, 6, status);
877 assertEquals("FormattedNumberRange move assignment", u"3,00–6,00 $US", result.toString(status));
878 FormattedNumberRange fnrdefault;
879 fnrdefault.toString(status);
880 status.expectErrorAndReset(U_INVALID_STATE_ERROR);
881}
882
883void NumberRangeFormatterTest::toObject() {
884 IcuTestErrorCode status(*this, "toObject");
885
886 // const lvalue version
887 {
888 LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en");
889 LocalPointer<LocalizedNumberRangeFormatter> lnf2(lnf.clone());
890 assertFalse("should create successfully, const lvalue", lnf2.isNull());
891 assertEquals("object API test, const lvalue", u"5–7",
892 lnf2->formatFormattableRange(5, 7, status).toString(status));
893 }
894
895 // rvalue reference version
896 {
897 LocalPointer<LocalizedNumberRangeFormatter> lnf(
898 NumberRangeFormatter::withLocale("en").clone());
899 assertFalse("should create successfully, rvalue reference", lnf.isNull());
900 assertEquals("object API test, rvalue reference", u"5–7",
901 lnf->formatFormattableRange(5, 7, status).toString(status));
902 }
903
904 // to std::unique_ptr via assignment
905 {
906 std::unique_ptr<LocalizedNumberRangeFormatter> lnf =
907 NumberRangeFormatter::withLocale("en").clone();
908 assertTrue("should create successfully, unique_ptr B", static_cast<bool>(lnf));
909 assertEquals("object API test, unique_ptr B", u"5–7",
910 lnf->formatFormattableRange(5, 7, status).toString(status));
911 }
912
913 // make sure no memory leaks
914 {
915 NumberRangeFormatter::with().clone();
916 }
917}
918
919void NumberRangeFormatterTest::testGetDecimalNumbers() {
920 IcuTestErrorCode status(*this, "testGetDecimalNumbers");
921
922 LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en")
923 .numberFormatterBoth(NumberFormatter::with().unit(USD));
924
925 // Range of numbers
926 {
927 FormattedNumberRange range = lnf.formatFormattableRange(1, 5, status);
928 assertEquals("Range: Formatted string should be as expected",
929 u"$1.00 \u2013 $5.00",
930 range.toString(status));
931 auto decimalNumbers = range.getDecimalNumbers<std::string>(status);
932 // TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem?
933 if (logKnownIssue("ICU-21281")) {
934 assertEquals("First decimal number", "1", decimalNumbers.first.c_str());
935 assertEquals("Second decimal number", "5", decimalNumbers.second.c_str());
936 } else {
937 assertEquals("First decimal number", "1.00", decimalNumbers.first.c_str());
938 assertEquals("Second decimal number", "5.00", decimalNumbers.second.c_str());
939 }
940 }
941
942 // Identity fallback
943 {
944 FormattedNumberRange range = lnf.formatFormattableRange(3, 3, status);
945 assertEquals("Identity: Formatted string should be as expected",
946 u"~$3.00",
947 range.toString(status));
948 auto decimalNumbers = range.getDecimalNumbers<std::string>(status);
949 // NOTE: DecNum doesn't retain trailing zeros. Is that a problem?
950 // TODO(ICU-21281): DecNum doesn't retain trailing zeros. Is that a problem?
951 if (logKnownIssue("ICU-21281")) {
952 assertEquals("First decimal number", "3", decimalNumbers.first.c_str());
953 assertEquals("Second decimal number", "3", decimalNumbers.second.c_str());
954 } else {
955 assertEquals("First decimal number", "3.00", decimalNumbers.first.c_str());
956 assertEquals("Second decimal number", "3.00", decimalNumbers.second.c_str());
957 }
958 }
959}
960
961void NumberRangeFormatterTest::test21684_Performance() {
962 IcuTestErrorCode status(*this, "test21684_Performance");
963 LocalizedNumberRangeFormatter lnf = NumberRangeFormatter::withLocale("en");
964 // The following two lines of code should finish quickly.
965 lnf.formatFormattableRange({"-1e99999", status}, {"0", status}, status);
966 lnf.formatFormattableRange({"0", status}, {"1e99999", status}, status);
967}
968
969void NumberRangeFormatterTest::test21358_SignPosition() {
970 IcuTestErrorCode status(*this, "test21358_SignPosition");
971
972 // de-CH has currency pattern "¤ #,##0.00;¤-#,##0.00"
973 assertFormatRange(
974 u"Approximately sign position with spacing from pattern",
975 NumberRangeFormatter::with()
976 .numberFormatterBoth(NumberFormatter::with().unit(CHF)),
977 Locale("de-CH"),
978 u"CHF 1.00–5.00",
979 u"CHF≈5.00",
980 u"CHF≈5.00",
981 u"CHF 0.00–3.00",
982 u"CHF≈0.00",
983 u"CHF 3.00–3’000.00",
984 u"CHF 3’000.00–5’000.00",
985 u"CHF 4’999.00–5’001.00",
986 u"CHF≈5’000.00",
987 u"CHF 5’000.00–5’000’000.00");
988
989 // TODO(CLDR-13044): Move the sign to the inside of the number
990 assertFormatRange(
991 u"Approximately sign position with currency spacing",
992 NumberRangeFormatter::with()
993 .numberFormatterBoth(NumberFormatter::with().unit(CHF)),
994 Locale("en-US"),
995 u"CHF 1.00–5.00",
996 u"~CHF 5.00",
997 u"~CHF 5.00",
998 u"CHF 0.00–3.00",
999 u"~CHF 0.00",
1000 u"CHF 3.00–3,000.00",
1001 u"CHF 3,000.00–5,000.00",
1002 u"CHF 4,999.00–5,001.00",
1003 u"~CHF 5,000.00",
1004 u"CHF 5,000.00–5,000,000.00");
1005
1006 {
1007 LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("de-CH");
1008 UnicodeString actual = lnrf.formatFormattableRange(-2, 3, status).toString(status);
1009 assertEquals("Negative to positive range", u"-2 – 3", actual);
1010 }
1011
1012 {
1013 LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("de-CH")
1014 .numberFormatterBoth(NumberFormatter::forSkeleton(u"%", status));
1015 UnicodeString actual = lnrf.formatFormattableRange(-2, 3, status).toString(status);
1016 assertEquals("Negative to positive percent", u"-2% – 3%", actual);
1017 }
1018
1019 {
1020 // TODO(CLDR-14111): Add spacing between range separator and sign
1021 LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("de-CH");
1022 UnicodeString actual = lnrf.formatFormattableRange(2, -3, status).toString(status);
1023 assertEquals("Positive to negative range", u"2–-3", actual);
1024 }
1025
1026 {
1027 LocalizedNumberRangeFormatter lnrf = NumberRangeFormatter::withLocale("de-CH")
1028 .numberFormatterBoth(NumberFormatter::forSkeleton(u"%", status));
1029 UnicodeString actual = lnrf.formatFormattableRange(2, -3, status).toString(status);
1030 assertEquals("Positive to negative percent", u"2% – -3%", actual);
1031 }
1032}
1033
1034void NumberRangeFormatterTest::test21683_StateLeak() {
1035 IcuTestErrorCode status(*this, "test21683_StateLeak");
1036 UNumberRangeFormatter* nrf = nullptr;
1037 UFormattedNumberRange* result = nullptr;
1038 UConstrainedFieldPosition* fpos = nullptr;
1039
1040 struct Range {
1041 double start;
1042 double end;
1043 const char16_t* expected;
1044 int numFields;
1045 } ranges[] = {
1046 {1, 2, u"1\u20132", 4},
1047 {1, 1, u"~1", 2},
1048 };
1049
1050 UParseError* perror = nullptr;
1051 nrf = unumrf_openForSkeletonWithCollapseAndIdentityFallback(
1052 u"", -1,
1053 UNUM_RANGE_COLLAPSE_AUTO,
1054 UNUM_IDENTITY_FALLBACK_APPROXIMATELY,
1055 "en", perror, status);
1056 if (status.errIfFailureAndReset("unumrf_openForSkeletonWithCollapseAndIdentityFallback")) {
1057 goto cleanup;
1058 }
1059
1060 result = unumrf_openResult(status);
1061 if (status.errIfFailureAndReset("unumrf_openResult")) { goto cleanup; }
1062
1063 for (auto range : ranges) {
1064 unumrf_formatDoubleRange(nrf, range.start, range.end, result, status);
1065 if (status.errIfFailureAndReset("unumrf_formatDoubleRange")) { goto cleanup; }
1066
1067 auto* formattedValue = unumrf_resultAsValue(result, status);
1068 if (status.errIfFailureAndReset("unumrf_resultAsValue")) { goto cleanup; }
1069
1070 int32_t utf16Length;
1071 const char16_t* utf16Str = ufmtval_getString(formattedValue, &utf16Length, status);
1072 if (status.errIfFailureAndReset("ufmtval_getString")) { goto cleanup; }
1073
1074 assertEquals("Format", range.expected, utf16Str);
1075
1076 ucfpos_close(fpos);
1077 fpos = ucfpos_open(status);
1078 if (status.errIfFailureAndReset("ucfpos_open")) { goto cleanup; }
1079
1080 int numFields = 0;
1081 while (true) {
1082 bool hasMore = ufmtval_nextPosition(formattedValue, fpos, status);
1083 if (status.errIfFailureAndReset("ufmtval_nextPosition")) { goto cleanup; }
1084 if (!hasMore) {
1085 break;
1086 }
1087 numFields++;
1088 }
1089 assertEquals("numFields", range.numFields, numFields);
1090 }
1091
1092cleanup:
1093 unumrf_close(nrf);
1094 unumrf_closeResult(result);
1095 ucfpos_close(fpos);
1096}
1097
1098void NumberRangeFormatterTest::assertFormatRange(
1099 const char16_t* message,
1100 const UnlocalizedNumberRangeFormatter& f,
1101 Locale locale,
1102 const char16_t* expected_10_50,
1103 const char16_t* expected_49_51,
1104 const char16_t* expected_50_50,
1105 const char16_t* expected_00_30,
1106 const char16_t* expected_00_00,
1107 const char16_t* expected_30_3K,
1108 const char16_t* expected_30K_50K,
1109 const char16_t* expected_49K_51K,
1110 const char16_t* expected_50K_50K,
1111 const char16_t* expected_50K_50M) {
1112 LocalizedNumberRangeFormatter l = f.locale(locale);
1113 assertFormattedRangeEquals(message, l, 1, 5, expected_10_50);
1114 assertFormattedRangeEquals(message, l, 4.9999999, 5.0000001, expected_49_51);
1115 assertFormattedRangeEquals(message, l, 5, 5, expected_50_50);
1116 assertFormattedRangeEquals(message, l, 0, 3, expected_00_30);
1117 assertFormattedRangeEquals(message, l, 0, 0, expected_00_00);
1118 assertFormattedRangeEquals(message, l, 3, 3000, expected_30_3K);
1119 assertFormattedRangeEquals(message, l, 3000, 5000, expected_30K_50K);
1120 assertFormattedRangeEquals(message, l, 4999, 5001, expected_49K_51K);
1121 assertFormattedRangeEquals(message, l, 5000, 5000, expected_50K_50K);
1122 assertFormattedRangeEquals(message, l, 5e3, 5e6, expected_50K_50M);
1123}
1124
1125FormattedNumberRange NumberRangeFormatterTest::assertFormattedRangeEquals(
1126 const char16_t* message,
1127 const LocalizedNumberRangeFormatter& l,
1128 double first,
1129 double second,
1130 const char16_t* expected) {
1131 IcuTestErrorCode status(*this, "assertFormattedRangeEquals");
1132 UnicodeString fullMessage = UnicodeString(message) + u": " + DoubleToUnicodeString(first) + u", " + DoubleToUnicodeString(second);
1133 status.setScope(fullMessage);
1134 FormattedNumberRange fnr = l.formatFormattableRange(first, second, status);
1135 UnicodeString actual = fnr.toString(status);
1136 assertEquals(fullMessage, expected, actual);
1137 return fnr;
1138}
1139
1140
1141#endif