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