• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /* GENERATED SOURCE. DO NOT MODIFY. */
2 // © 2017 and later: Unicode, Inc. and others.
3 // License & terms of use: http://www.unicode.org/copyright.html#License
4 package ohos.global.icu.dev.test.number;
5 
6 import java.math.BigDecimal;
7 import java.math.BigInteger;
8 import java.math.MathContext;
9 import java.math.RoundingMode;
10 import java.text.ParseException;
11 import java.util.ArrayList;
12 import java.util.List;
13 
14 import org.junit.Ignore;
15 import org.junit.Test;
16 import org.junit.runner.RunWith;
17 import org.junit.runners.JUnit4;
18 
19 import ohos.global.icu.dev.impl.number.DecimalQuantity_64BitBCD;
20 import ohos.global.icu.dev.impl.number.DecimalQuantity_ByteArrayBCD;
21 import ohos.global.icu.dev.impl.number.DecimalQuantity_SimpleStorage;
22 import ohos.global.icu.dev.test.TestFmwk;
23 import ohos.global.icu.impl.FormattedStringBuilder;
24 import ohos.global.icu.impl.number.DecimalFormatProperties;
25 import ohos.global.icu.impl.number.DecimalQuantity;
26 import ohos.global.icu.impl.number.DecimalQuantity_DualStorageBCD;
27 import ohos.global.icu.impl.number.RoundingUtils;
28 import ohos.global.icu.number.FormattedNumber;
29 import ohos.global.icu.number.LocalizedNumberFormatter;
30 import ohos.global.icu.number.Notation;
31 import ohos.global.icu.number.NumberFormatter;
32 import ohos.global.icu.number.Precision;
33 import ohos.global.icu.number.Scale;
34 import ohos.global.icu.text.CompactDecimalFormat.CompactStyle;
35 import ohos.global.icu.text.DecimalFormatSymbols;
36 import ohos.global.icu.text.PluralRules.Operand;
37 import ohos.global.icu.util.ULocale;
38 
39 
40 
41 @RunWith(JUnit4.class)
42 public class DecimalQuantityTest extends TestFmwk {
43 
44     @Ignore
45     @Test
testBehavior()46     public void testBehavior() throws ParseException {
47 
48         // Make a list of several formatters to test the behavior of DecimalQuantity.
49         List<LocalizedNumberFormatter> formats = new ArrayList<>();
50 
51         DecimalFormatSymbols symbols = DecimalFormatSymbols.getInstance(ULocale.ENGLISH);
52 
53         DecimalFormatProperties properties = new DecimalFormatProperties();
54         formats.add(
55                 NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
56 
57         properties = new DecimalFormatProperties().setMinimumSignificantDigits(3)
58                 .setMaximumSignificantDigits(3).setCompactStyle(CompactStyle.LONG);
59         formats.add(
60                 NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
61 
62         properties = new DecimalFormatProperties().setMinimumExponentDigits(1).setMaximumIntegerDigits(3)
63                 .setMaximumFractionDigits(1);
64         formats.add(
65                 NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
66 
67         properties = new DecimalFormatProperties().setRoundingIncrement(new BigDecimal("0.5"));
68         formats.add(
69                 NumberFormatter.fromDecimalFormat(properties, symbols, null).locale(ULocale.ENGLISH));
70 
71         String[] cases = {
72                 "1.0",
73                 "2.01",
74                 "1234.56",
75                 "3000.0",
76                 "0.00026418",
77                 "0.01789261",
78                 "468160.0",
79                 "999000.0",
80                 "999900.0",
81                 "999990.0",
82                 "0.0",
83                 "12345678901.0",
84                 "-5193.48", };
85 
86         String[] hardCases = {
87                 "9999999999999900.0",
88                 "789000000000000000000000.0",
89                 "789123123567853156372158.0",
90                 "987654321987654321987654321987654321987654311987654321.0", };
91 
92         String[] doubleCases = {
93                 "512.0000000000017",
94                 "4095.9999999999977",
95                 "4095.999999999998",
96                 "4095.9999999999986",
97                 "4095.999999999999",
98                 "4095.9999999999995",
99                 "4096.000000000001",
100                 "4096.000000000002",
101                 "4096.000000000003",
102                 "4096.000000000004",
103                 "4096.000000000005",
104                 "4096.0000000000055",
105                 "4096.000000000006",
106                 "4096.000000000007", };
107 
108         int i = 0;
109         for (String str : cases) {
110             testDecimalQuantity(i++, str, formats, 0);
111         }
112 
113         i = 0;
114         for (String str : hardCases) {
115             testDecimalQuantity(i++, str, formats, 1);
116         }
117 
118         i = 0;
119         for (String str : doubleCases) {
120             testDecimalQuantity(i++, str, formats, 2);
121         }
122     }
123 
testDecimalQuantity( int t, String str, List<LocalizedNumberFormatter> formats, int mode)124     static void testDecimalQuantity(
125             int t,
126             String str,
127             List<LocalizedNumberFormatter> formats,
128             int mode) {
129         if (mode == 2) {
130             assertEquals("Double is not valid", Double.toString(Double.parseDouble(str)), str);
131         }
132 
133         List<DecimalQuantity> qs = new ArrayList<>();
134         BigDecimal d = new BigDecimal(str);
135         qs.add(new DecimalQuantity_SimpleStorage(d));
136         if (mode == 0)
137             qs.add(new DecimalQuantity_64BitBCD(d));
138         qs.add(new DecimalQuantity_ByteArrayBCD(d));
139         qs.add(new DecimalQuantity_DualStorageBCD(d));
140 
141         if (new BigDecimal(Double.toString(d.doubleValue())).compareTo(d) == 0) {
142             double dv = d.doubleValue();
143             qs.add(new DecimalQuantity_SimpleStorage(dv));
144             if (mode == 0)
145                 qs.add(new DecimalQuantity_64BitBCD(dv));
146             qs.add(new DecimalQuantity_ByteArrayBCD(dv));
147             qs.add(new DecimalQuantity_DualStorageBCD(dv));
148         }
149 
150         if (new BigDecimal(Long.toString(d.longValue())).compareTo(d) == 0) {
151             double lv = d.longValue();
152             qs.add(new DecimalQuantity_SimpleStorage(lv));
153             if (mode == 0)
154                 qs.add(new DecimalQuantity_64BitBCD(lv));
155             qs.add(new DecimalQuantity_ByteArrayBCD(lv));
156             qs.add(new DecimalQuantity_DualStorageBCD(lv));
157         }
158 
159         testDecimalQuantityExpectedOutput(qs.get(0), str);
160 
161         if (qs.size() == 1) {
162             return;
163         }
164 
165         for (int i = 1; i < qs.size(); i++) {
166             DecimalQuantity q0 = qs.get(0);
167             DecimalQuantity q1 = qs.get(i);
168             testDecimalQuantityExpectedOutput(q1, str);
169             testDecimalQuantityRounding(q0, q1);
170             testDecimalQuantityRoundingInterval(q0, q1);
171             testDecimalQuantityMath(q0, q1);
172             testDecimalQuantityWithFormats(q0, q1, formats);
173         }
174     }
175 
testDecimalQuantityExpectedOutput(DecimalQuantity rq, String expected)176     private static void testDecimalQuantityExpectedOutput(DecimalQuantity rq, String expected) {
177         DecimalQuantity q0 = rq.createCopy();
178         // Force an accurate double
179         q0.roundToInfinity();
180         q0.setMinInteger(1);
181         q0.setMinFraction(1);
182         String actual = q0.toPlainString();
183         assertEquals("Unexpected output from simple string conversion (" + q0 + ")", expected, actual);
184     }
185 
186     private static final MathContext MATH_CONTEXT_HALF_EVEN = new MathContext(0, RoundingMode.HALF_EVEN);
187     private static final MathContext MATH_CONTEXT_CEILING = new MathContext(0, RoundingMode.CEILING);
188     @SuppressWarnings("unused")
189     private static final MathContext MATH_CONTEXT_FLOOR = new MathContext(0, RoundingMode.FLOOR);
190     private static final MathContext MATH_CONTEXT_PRECISION = new MathContext(3, RoundingMode.HALF_UP);
191 
testDecimalQuantityRounding(DecimalQuantity rq0, DecimalQuantity rq1)192     private static void testDecimalQuantityRounding(DecimalQuantity rq0, DecimalQuantity rq1) {
193         DecimalQuantity q0 = rq0.createCopy();
194         DecimalQuantity q1 = rq1.createCopy();
195         q0.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
196         q1.roundToMagnitude(-1, MATH_CONTEXT_HALF_EVEN);
197         testDecimalQuantityBehavior(q0, q1);
198 
199         q0 = rq0.createCopy();
200         q1 = rq1.createCopy();
201         q0.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
202         q1.roundToMagnitude(-1, MATH_CONTEXT_CEILING);
203         testDecimalQuantityBehavior(q0, q1);
204 
205         q0 = rq0.createCopy();
206         q1 = rq1.createCopy();
207         q0.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
208         q1.roundToMagnitude(-1, MATH_CONTEXT_PRECISION);
209         testDecimalQuantityBehavior(q0, q1);
210     }
211 
testDecimalQuantityRoundingInterval(DecimalQuantity rq0, DecimalQuantity rq1)212     private static void testDecimalQuantityRoundingInterval(DecimalQuantity rq0, DecimalQuantity rq1) {
213         DecimalQuantity q0 = rq0.createCopy();
214         DecimalQuantity q1 = rq1.createCopy();
215         q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
216         q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_HALF_EVEN);
217         testDecimalQuantityBehavior(q0, q1);
218 
219         q0 = rq0.createCopy();
220         q1 = rq1.createCopy();
221         q0.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
222         q1.roundToIncrement(new BigDecimal("0.05"), MATH_CONTEXT_CEILING);
223         testDecimalQuantityBehavior(q0, q1);
224     }
225 
testDecimalQuantityMath(DecimalQuantity rq0, DecimalQuantity rq1)226     private static void testDecimalQuantityMath(DecimalQuantity rq0, DecimalQuantity rq1) {
227         DecimalQuantity q0 = rq0.createCopy();
228         DecimalQuantity q1 = rq1.createCopy();
229         q0.adjustMagnitude(-3);
230         q1.adjustMagnitude(-3);
231         testDecimalQuantityBehavior(q0, q1);
232 
233         q0 = rq0.createCopy();
234         q1 = rq1.createCopy();
235         q0.multiplyBy(new BigDecimal("3.14159"));
236         q1.multiplyBy(new BigDecimal("3.14159"));
237         testDecimalQuantityBehavior(q0, q1);
238     }
239 
testDecimalQuantityWithFormats( DecimalQuantity rq0, DecimalQuantity rq1, List<LocalizedNumberFormatter> formats)240     private static void testDecimalQuantityWithFormats(
241             DecimalQuantity rq0,
242             DecimalQuantity rq1,
243             List<LocalizedNumberFormatter> formats) {
244         for (LocalizedNumberFormatter format : formats) {
245             DecimalQuantity q0 = rq0.createCopy();
246             DecimalQuantity q1 = rq1.createCopy();
247             FormattedStringBuilder nsb1 = new FormattedStringBuilder();
248             FormattedStringBuilder nsb2 = new FormattedStringBuilder();
249             format.formatImpl(q0, nsb1);
250             format.formatImpl(q1, nsb2);
251             String s1 = nsb1.toString();
252             String s2 = nsb2.toString();
253             assertEquals("Different output from formatter (" + q0 + ", " + q1 + ")", s1, s2);
254         }
255     }
256 
testDecimalQuantityBehavior(DecimalQuantity rq0, DecimalQuantity rq1)257     private static void testDecimalQuantityBehavior(DecimalQuantity rq0, DecimalQuantity rq1) {
258         DecimalQuantity q0 = rq0.createCopy();
259         DecimalQuantity q1 = rq1.createCopy();
260 
261         assertEquals("Different sign (" + q0 + ", " + q1 + ")", q0.isNegative(), q1.isNegative());
262 
263         assertEquals("Different fingerprint (" + q0 + ", " + q1 + ")",
264                 q0.getPositionFingerprint(),
265                 q1.getPositionFingerprint());
266 
267         assertDoubleEquals("Different double values (" + q0 + ", " + q1 + ")",
268                 q0.toDouble(),
269                 q1.toDouble());
270 
271         assertBigDecimalEquals("Different BigDecimal values (" + q0 + ", " + q1 + ")",
272                 q0.toBigDecimal(),
273                 q1.toBigDecimal());
274 
275         q0.roundToInfinity();
276         q1.roundToInfinity();
277 
278         assertEquals("Different lower display magnitude",
279                 q0.getLowerDisplayMagnitude(),
280                 q1.getLowerDisplayMagnitude());
281         assertEquals("Different upper display magnitude",
282                 q0.getUpperDisplayMagnitude(),
283                 q1.getUpperDisplayMagnitude());
284 
285         for (int m = q0.getUpperDisplayMagnitude(); m >= q0.getLowerDisplayMagnitude(); m--) {
286             assertEquals("Different digit at magnitude " + m + " (" + q0 + ", " + q1 + ")",
287                     q0.getDigit(m),
288                     q1.getDigit(m));
289         }
290 
291         if (rq0 instanceof DecimalQuantity_DualStorageBCD) {
292             String message = ((DecimalQuantity_DualStorageBCD) rq0).checkHealth();
293             if (message != null)
294                 errln(message);
295         }
296         if (rq1 instanceof DecimalQuantity_DualStorageBCD) {
297             String message = ((DecimalQuantity_DualStorageBCD) rq1).checkHealth();
298             if (message != null)
299                 errln(message);
300         }
301     }
302 
303     @Test
testSwitchStorage()304     public void testSwitchStorage() {
305         DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
306 
307         fq.setToLong(1234123412341234L);
308         assertFalse("Should not be using byte array", fq.isUsingBytes());
309         assertEquals("Failed on initialize", "1.234123412341234E+15", fq.toScientificString());
310         assertNull("Failed health check", fq.checkHealth());
311         // Long -> Bytes
312         fq.appendDigit((byte) 5, 0, true);
313         assertTrue("Should be using byte array", fq.isUsingBytes());
314         assertEquals("Failed on multiply", "1.2341234123412345E+16", fq.toScientificString());
315         assertNull("Failed health check", fq.checkHealth());
316         // Bytes -> Long
317         fq.roundToMagnitude(5, MATH_CONTEXT_HALF_EVEN);
318         assertFalse("Should not be using byte array", fq.isUsingBytes());
319         assertEquals("Failed on round", "1.23412341234E+16", fq.toScientificString());
320         assertNull("Failed health check", fq.checkHealth());
321         // Bytes with popFromLeft
322         fq.setToBigDecimal(new BigDecimal("999999999999999999"));
323         assertToStringAndHealth(fq, "<DecimalQuantity 0:0 bytes 999999999999999999E0>");
324         fq.applyMaxInteger(17);
325         assertToStringAndHealth(fq, "<DecimalQuantity 0:0 bytes 99999999999999999E0>");
326         fq.applyMaxInteger(16);
327         assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 9999999999999999E0>");
328         fq.applyMaxInteger(15);
329         assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 999999999999999E0>");
330     }
331 
332     @Test
testAppend()333     public void testAppend() {
334         DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
335         fq.appendDigit((byte) 1, 0, true);
336         assertEquals("Failed on append", "1E+0", fq.toScientificString());
337         assertNull("Failed health check", fq.checkHealth());
338         fq.appendDigit((byte) 2, 0, true);
339         assertEquals("Failed on append", "1.2E+1", fq.toScientificString());
340         assertNull("Failed health check", fq.checkHealth());
341         fq.appendDigit((byte) 3, 1, true);
342         assertEquals("Failed on append", "1.203E+3", fq.toScientificString());
343         assertNull("Failed health check", fq.checkHealth());
344         fq.appendDigit((byte) 0, 1, true);
345         assertEquals("Failed on append", "1.203E+5", fq.toScientificString());
346         assertNull("Failed health check", fq.checkHealth());
347         fq.appendDigit((byte) 4, 0, true);
348         assertEquals("Failed on append", "1.203004E+6", fq.toScientificString());
349         assertNull("Failed health check", fq.checkHealth());
350         fq.appendDigit((byte) 0, 0, true);
351         assertEquals("Failed on append", "1.203004E+7", fq.toScientificString());
352         assertNull("Failed health check", fq.checkHealth());
353         fq.appendDigit((byte) 5, 0, false);
354         assertEquals("Failed on append", "1.20300405E+7", fq.toScientificString());
355         assertNull("Failed health check", fq.checkHealth());
356         fq.appendDigit((byte) 6, 0, false);
357         assertEquals("Failed on append", "1.203004056E+7", fq.toScientificString());
358         assertNull("Failed health check", fq.checkHealth());
359         fq.appendDigit((byte) 7, 3, false);
360         assertEquals("Failed on append", "1.2030040560007E+7", fq.toScientificString());
361         assertNull("Failed health check", fq.checkHealth());
362         StringBuilder baseExpected = new StringBuilder("1.2030040560007");
363         for (int i = 0; i < 10; i++) {
364             fq.appendDigit((byte) 8, 0, false);
365             baseExpected.append('8');
366             StringBuilder expected = new StringBuilder(baseExpected);
367             expected.append("E+7");
368             assertEquals("Failed on append", expected.toString(), fq.toScientificString());
369             assertNull("Failed health check", fq.checkHealth());
370         }
371         fq.appendDigit((byte) 9, 2, false);
372         baseExpected.append("009");
373         StringBuilder expected = new StringBuilder(baseExpected);
374         expected.append("E+7");
375         assertEquals("Failed on append", expected.toString(), fq.toScientificString());
376         assertNull("Failed health check", fq.checkHealth());
377     }
378 
379     @Test
testUseApproximateDoubleWhenAble()380     public void testUseApproximateDoubleWhenAble() {
381         Object[][] cases = {
382                 { 1.2345678, 1, MATH_CONTEXT_HALF_EVEN, false },
383                 { 1.2345678, 7, MATH_CONTEXT_HALF_EVEN, false },
384                 { 1.2345678, 12, MATH_CONTEXT_HALF_EVEN, false },
385                 { 1.2345678, 13, MATH_CONTEXT_HALF_EVEN, true },
386                 { 1.235, 1, MATH_CONTEXT_HALF_EVEN, false },
387                 { 1.235, 2, MATH_CONTEXT_HALF_EVEN, true },
388                 { 1.235, 3, MATH_CONTEXT_HALF_EVEN, false },
389                 { 1.000000000000001, 0, MATH_CONTEXT_HALF_EVEN, false },
390                 { 1.000000000000001, 0, MATH_CONTEXT_CEILING, true },
391                 { 1.235, 1, MATH_CONTEXT_CEILING, false },
392                 { 1.235, 2, MATH_CONTEXT_CEILING, false },
393                 { 1.235, 3, MATH_CONTEXT_CEILING, true } };
394 
395         for (Object[] cas : cases) {
396             double d = (Double) cas[0];
397             int maxFrac = (Integer) cas[1];
398             MathContext mc = (MathContext) cas[2];
399             boolean usesExact = (Boolean) cas[3];
400 
401             DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD(d);
402             assertTrue("Should be using approximate double", !fq.explicitExactDouble);
403             fq.roundToMagnitude(-maxFrac, mc);
404             assertEquals(
405                     "Using approximate double after rounding: " + d + " maxFrac=" + maxFrac + " " + mc,
406                     usesExact,
407                     fq.explicitExactDouble);
408         }
409     }
410 
411     @Test
testDecimalQuantityBehaviorStandalone()412     public void testDecimalQuantityBehaviorStandalone() {
413         DecimalQuantity_DualStorageBCD fq = new DecimalQuantity_DualStorageBCD();
414         assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 0E0>");
415         fq.setToInt(51423);
416         assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 51423E0>");
417         fq.adjustMagnitude(-3);
418         assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 51423E-3>");
419         fq.setToLong(90909090909000L);
420         assertToStringAndHealth(fq, "<DecimalQuantity 0:0 long 90909090909E3>");
421         fq.setMinInteger(2);
422         fq.applyMaxInteger(5);
423         assertToStringAndHealth(fq, "<DecimalQuantity 2:0 long 9E3>");
424         fq.setMinFraction(3);
425         assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 9E3>");
426         fq.setToDouble(987.654321);
427         assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 987654321E-6>");
428         fq.roundToInfinity();
429         assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 987654321E-6>");
430         fq.roundToIncrement(new BigDecimal("0.005"), MATH_CONTEXT_HALF_EVEN);
431         assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 987655E-3>");
432         fq.roundToMagnitude(-2, MATH_CONTEXT_HALF_EVEN);
433         assertToStringAndHealth(fq, "<DecimalQuantity 2:-3 long 98766E-2>");
434     }
435 
436     @Test
testFitsInLong()437     public void testFitsInLong() {
438         DecimalQuantity_DualStorageBCD quantity = new DecimalQuantity_DualStorageBCD();
439         quantity.setToInt(0);
440         assertTrue("Zero should fit", quantity.fitsInLong());
441         quantity.setToInt(42);
442         assertTrue("Small int should fit", quantity.fitsInLong());
443         quantity.setToDouble(0.1);
444         assertFalse("Fraction should not fit", quantity.fitsInLong());
445         quantity.setToDouble(42.1);
446         assertFalse("Fraction should not fit", quantity.fitsInLong());
447         quantity.setToLong(1000000);
448         assertTrue("Large low-precision int should fit", quantity.fitsInLong());
449         quantity.setToLong(1000000000000000000L);
450         assertTrue("10^19 should fit", quantity.fitsInLong());
451         quantity.setToLong(1234567890123456789L);
452         assertTrue("A number between 10^19 and max long should fit", quantity.fitsInLong());
453         quantity.setToLong(1234567890000000000L);
454         assertTrue("A number with trailing zeros less than max long should fit", quantity.fitsInLong());
455         quantity.setToLong(9223372026854775808L);
456         assertTrue("A number less than max long but with similar digits should fit",
457                 quantity.fitsInLong());
458         quantity.setToLong(9223372036854775806L);
459         assertTrue("One less than max long should fit", quantity.fitsInLong());
460         quantity.setToLong(9223372036854775807L);
461         assertTrue("Max long should fit", quantity.fitsInLong());
462         quantity.setToBigInteger(new BigInteger("9223372036854775808"));
463         assertFalse("One greater than max long long should not fit", quantity.fitsInLong());
464         quantity.setToBigInteger(new BigInteger("9223372046854775806"));
465         assertFalse("A number between max long and 10^20 should not fit", quantity.fitsInLong());
466         quantity.setToBigInteger(new BigInteger("9223372046800000000"));
467         assertFalse("A large 10^19 number with trailing zeros should not fit", quantity.fitsInLong());
468         quantity.setToBigInteger(new BigInteger("10000000000000000000"));
469         assertFalse("10^20 should not fit", quantity.fitsInLong());
470     }
471 
472     @Test
testHardDoubleConversion()473     public void testHardDoubleConversion() {
474         // This test is somewhat duplicated from previous tests, but it is needed
475         // for ICU4C compatibility.
476         Object[][] cases = {
477                 { 512.0000000000017, "512.0000000000017" },
478                 { 4095.9999999999977, "4095.9999999999977" },
479                 { 4095.999999999998, "4095.999999999998" },
480                 { 4095.9999999999986, "4095.9999999999986" },
481                 { 4095.999999999999, "4095.999999999999" },
482                 { 4095.9999999999995, "4095.9999999999995" },
483                 { 4096.000000000001, "4096.000000000001" },
484                 { 4096.000000000002, "4096.000000000002" },
485                 { 4096.000000000003, "4096.000000000003" },
486                 { 4096.000000000004, "4096.000000000004" },
487                 { 4096.000000000005, "4096.000000000005" },
488                 { 4096.0000000000055, "4096.0000000000055" },
489                 { 4096.000000000006, "4096.000000000006" },
490                 { 4096.000000000007, "4096.000000000007" } };
491 
492         for (Object[] cas : cases) {
493             double input = (Double) cas[0];
494             String expectedOutput = (String) cas[1];
495 
496             DecimalQuantity q = new DecimalQuantity_DualStorageBCD(input);
497             q.roundToInfinity();
498             String actualOutput = q.toPlainString();
499             assertEquals("", expectedOutput, actualOutput);
500         }
501     }
502 
503     @Test
testToDouble()504     public void testToDouble() {
505         Object[][] cases = new Object[][] {
506             { "0", 0.0 },
507             { "514.23", 514.23 },
508             { "-3.142E-271", -3.142e-271 }
509         };
510 
511         for (Object[] cas : cases) {
512             String input = (String) cas[0];
513             double expected = (Double) cas[1];
514 
515             DecimalQuantity q = new DecimalQuantity_DualStorageBCD();
516             q.setToBigDecimal(new BigDecimal(input));
517             double actual = q.toDouble();
518             assertEquals("Doubles should exactly equal", expected, actual);
519         }
520     }
521 
522     @Test
testMaxDigits()523     public void testMaxDigits() {
524         DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(876.543);
525         dq.roundToInfinity();
526         dq.setMinInteger(0);
527         dq.applyMaxInteger(2);
528         dq.setMinFraction(0);
529         dq.roundToMagnitude(-2, RoundingUtils.mathContextUnlimited(RoundingMode.FLOOR));
530         assertEquals("Should trim, toPlainString", "76.54", dq.toPlainString());
531         assertEquals("Should trim, toScientificString", "7.654E+1", dq.toScientificString());
532         assertEquals("Should trim, toLong", 76, dq.toLong(true));
533         assertEquals("Should trim, toFractionLong", 54, dq.toFractionLong(false));
534         assertEquals("Should trim, toDouble", 76.54, dq.toDouble());
535         assertEquals("Should trim, toBigDecimal", new BigDecimal("76.54"), dq.toBigDecimal());
536     }
537 
538     @Test
testNickelRounding()539     public void testNickelRounding() {
540         Object[][] cases = new Object[][] {
541             {1.000, -2, RoundingMode.HALF_EVEN, "1"},
542             {1.001, -2, RoundingMode.HALF_EVEN, "1"},
543             {1.010, -2, RoundingMode.HALF_EVEN, "1"},
544             {1.020, -2, RoundingMode.HALF_EVEN, "1"},
545             {1.024, -2, RoundingMode.HALF_EVEN, "1"},
546             {1.025, -2, RoundingMode.HALF_EVEN, "1"},
547             {1.025, -2, RoundingMode.HALF_DOWN, "1"},
548             {1.025, -2, RoundingMode.HALF_UP,   "1.05"},
549             {1.026, -2, RoundingMode.HALF_EVEN, "1.05"},
550             {1.030, -2, RoundingMode.HALF_EVEN, "1.05"},
551             {1.040, -2, RoundingMode.HALF_EVEN, "1.05"},
552             {1.050, -2, RoundingMode.HALF_EVEN, "1.05"},
553             {1.060, -2, RoundingMode.HALF_EVEN, "1.05"},
554             {1.070, -2, RoundingMode.HALF_EVEN, "1.05"},
555             {1.074, -2, RoundingMode.HALF_EVEN, "1.05"},
556             {1.075, -2, RoundingMode.HALF_DOWN, "1.05"},
557             {1.075, -2, RoundingMode.HALF_UP,   "1.1"},
558             {1.075, -2, RoundingMode.HALF_EVEN, "1.1"},
559             {1.076, -2, RoundingMode.HALF_EVEN, "1.1"},
560             {1.080, -2, RoundingMode.HALF_EVEN, "1.1"},
561             {1.090, -2, RoundingMode.HALF_EVEN, "1.1"},
562             {1.099, -2, RoundingMode.HALF_EVEN, "1.1"},
563             {1.999, -2, RoundingMode.HALF_EVEN, "2"},
564             {2.25, -1, RoundingMode.HALF_EVEN, "2"},
565             {2.25, -1, RoundingMode.HALF_UP,   "2.5"},
566             {2.75, -1, RoundingMode.HALF_DOWN, "2.5"},
567             {2.75, -1, RoundingMode.HALF_EVEN, "3"},
568             {3.00, -1, RoundingMode.CEILING, "3"},
569             {3.25, -1, RoundingMode.CEILING, "3.5"},
570             {3.50, -1, RoundingMode.CEILING, "3.5"},
571             {3.75, -1, RoundingMode.CEILING, "4"},
572             {4.00, -1, RoundingMode.FLOOR, "4"},
573             {4.25, -1, RoundingMode.FLOOR, "4"},
574             {4.50, -1, RoundingMode.FLOOR, "4.5"},
575             {4.75, -1, RoundingMode.FLOOR, "4.5"},
576             {5.00, -1, RoundingMode.UP, "5"},
577             {5.25, -1, RoundingMode.UP, "5.5"},
578             {5.50, -1, RoundingMode.UP, "5.5"},
579             {5.75, -1, RoundingMode.UP, "6"},
580             {6.00, -1, RoundingMode.DOWN, "6"},
581             {6.25, -1, RoundingMode.DOWN, "6"},
582             {6.50, -1, RoundingMode.DOWN, "6.5"},
583             {6.75, -1, RoundingMode.DOWN, "6.5"},
584             {7.00, -1, RoundingMode.UNNECESSARY, "7"},
585             {7.50, -1, RoundingMode.UNNECESSARY, "7.5"},
586         };
587         for (Object[] cas : cases) {
588             double input = (Double) cas[0];
589             int magnitude = (Integer) cas[1];
590             RoundingMode roundingMode = (RoundingMode) cas[2];
591             String expected = (String) cas[3];
592             String message = input + " @ " + magnitude + " / " + roundingMode;
593             for (int i=0; i<2; i++) {
594                 DecimalQuantity dq;
595                 if (i == 0) {
596                     dq = new DecimalQuantity_DualStorageBCD(input);
597                 } else {
598                     dq = new DecimalQuantity_SimpleStorage(input);
599                 }
600                 dq.roundToNickel(magnitude, RoundingUtils.mathContextUnlimited(roundingMode));
601                 String actual = dq.toPlainString();
602                 assertEquals(message, expected, actual);
603             }
604         }
605         try {
606             DecimalQuantity_DualStorageBCD dq = new DecimalQuantity_DualStorageBCD(7.1);
607             dq.roundToNickel(-1, RoundingUtils.mathContextUnlimited(RoundingMode.UNNECESSARY));
608             fail("Expected ArithmeticException");
609         } catch (ArithmeticException expected) {
610             // pass
611         }
612     }
613 
614     @Test
testCompactDecimalSuppressedExponent()615     public void testCompactDecimalSuppressedExponent() {
616         ULocale locale = new ULocale("fr-FR");
617 
618         Object[][] casesData = {
619                 // unlocalized formatter skeleton, input, string output, long output, double output, BigDecimal output, plain string, suppressed exponent
620                 {"",              123456789, "123 456 789",  123456789L, 123456789.0, new BigDecimal("123456789"), "123456789", 0},
621                 {"compact-long",  123456789, "123 millions", 123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6},
622                 {"compact-short", 123456789, "123 M",        123000000L, 123000000.0, new BigDecimal("123000000"), "123000000", 6},
623                 {"scientific",    123456789, "1,234568E8",   123456800L, 123456800.0, new BigDecimal("123456800"), "123456800", 8},
624 
625                 {"",              1234567, "1 234 567",   1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 0},
626                 {"compact-long",  1234567, "1,2 million", 1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6},
627                 {"compact-short", 1234567, "1,2 M",       1200000L, 1200000.0, new BigDecimal("1200000"), "1200000", 6},
628                 {"scientific",    1234567, "1,234567E6",  1234567L, 1234567.0, new BigDecimal("1234567"), "1234567", 6},
629 
630                 {"",              123456, "123 456",   123456L, 123456.0, new BigDecimal("123456"), "123456", 0},
631                 {"compact-long",  123456, "123 mille", 123000L, 123000.0, new BigDecimal("123000"), "123000", 3},
632                 {"compact-short", 123456, "123 k",     123000L, 123000.0, new BigDecimal("123000"), "123000", 3},
633                 {"scientific",    123456, "1,23456E5", 123456L, 123456.0, new BigDecimal("123456"), "123456", 5},
634 
635                 {"",              123, "123",    123L, 123.0, new BigDecimal("123"), "123", 0},
636                 {"compact-long",  123, "123",    123L, 123.0, new BigDecimal("123"), "123", 0},
637                 {"compact-short", 123, "123",    123L, 123.0, new BigDecimal("123"), "123", 0},
638                 {"scientific",    123, "1,23E2", 123L, 123.0, new BigDecimal("123"), "123", 2},
639 
640                 {"",              1.2, "1,2",   1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
641                 {"compact-long",  1.2, "1,2",   1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
642                 {"compact-short", 1.2, "1,2",   1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
643                 {"scientific",    1.2, "1,2E0", 1L, 1.2, new BigDecimal("1.2"), "1.2", 0},
644 
645                 {"",              0.12, "0,12",   0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
646                 {"compact-long",  0.12, "0,12",   0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
647                 {"compact-short", 0.12, "0,12",   0L, 0.12, new BigDecimal("0.12"), "0.12", 0},
648                 {"scientific",    0.12, "1,2E-1", 0L, 0.12, new BigDecimal("0.12"), "0.12", -1},
649 
650                 {"",              0.012, "0,012",   0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
651                 {"compact-long",  0.012, "0,012",   0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
652                 {"compact-short", 0.012, "0,012",   0L, 0.012, new BigDecimal("0.012"), "0.012", 0},
653                 {"scientific",    0.012, "1,2E-2",  0L, 0.012, new BigDecimal("0.012"), "0.012", -2},
654 
655                 {"",              999.9, "999,9",     999L,  999.9,  new BigDecimal("999.9"), "999.9", 0},
656                 {"compact-long",  999.9, "1 millier", 1000L, 1000.0, new BigDecimal("1000"),  "1000",  3},
657                 {"compact-short", 999.9, "1 k",       1000L, 1000.0, new BigDecimal("1000"),  "1000",  3},
658                 {"scientific",    999.9, "9,999E2",   999L,  999.9,  new BigDecimal("999.9"), "999.9", 2},
659 
660                 {"",              1000.0, "1 000",     1000L, 1000.0, new BigDecimal("1000"), "1000", 0},
661                 {"compact-long",  1000.0, "1 millier", 1000L, 1000.0, new BigDecimal("1000"), "1000", 3},
662                 {"compact-short", 1000.0, "1 k",       1000L, 1000.0, new BigDecimal("1000"), "1000", 3},
663                 {"scientific",    1000.0, "1E3",       1000L, 1000.0, new BigDecimal("1000"), "1000", 3},
664         };
665 
666         for (Object[] caseDatum : casesData) {
667             // test the helper methods used to compute plural operand values
668 
669             String skeleton = (String) caseDatum[0];
670             LocalizedNumberFormatter formatter =
671                     NumberFormatter.forSkeleton(skeleton)
672                         .locale(locale);
673             double input = ((Number) caseDatum[1]).doubleValue();
674             String expectedString = (String) caseDatum[2];
675             long expectedLong = (long) caseDatum[3];
676             double expectedDouble = (double) caseDatum[4];
677             BigDecimal expectedBigDecimal = (BigDecimal) caseDatum[5];
678             String expectedPlainString = (String) caseDatum[6];
679             int expectedSuppressedExponent = (int) caseDatum[7];
680 
681             FormattedNumber fn = formatter.format(input);
682             DecimalQuantity_DualStorageBCD dq = (DecimalQuantity_DualStorageBCD)
683                     fn.getFixedDecimal();
684             String actualString = fn.toString();
685             long actualLong = dq.toLong(true);
686             double actualDouble = dq.toDouble();
687             BigDecimal actualBigDecimal = dq.toBigDecimal();
688             String actualPlainString = dq.toPlainString();
689             int actualSuppressedExponent = dq.getExponent();
690 
691             assertEquals(
692                     String.format("formatted number %s toString: %f", skeleton, input),
693                     expectedString,
694                     actualString);
695             assertEquals(
696                     String.format("compact decimal %s toLong: %f", skeleton, input),
697                     expectedLong,
698                     actualLong);
699             assertDoubleEquals(
700                     String.format("compact decimal %s toDouble: %f", skeleton, input),
701                     expectedDouble,
702                     actualDouble);
703             assertBigDecimalEquals(
704                     String.format("compact decimal %s toBigDecimal: %f", skeleton, input),
705                     expectedBigDecimal,
706                     actualBigDecimal);
707             assertEquals(
708                     String.format("formatted number %s toPlainString: %f", skeleton, input),
709                     expectedPlainString,
710                     actualPlainString);
711             assertEquals(
712                     String.format("compact decimal %s suppressed exponent: %f", skeleton, input),
713                     expectedSuppressedExponent,
714                     actualSuppressedExponent);
715 
716             // test the actual computed values of the plural operands
717 
718             double expectedNOperand = expectedDouble;
719             double expectedIOperand = expectedLong;
720             double expectedEOperand = expectedSuppressedExponent;
721             double actualNOperand = dq.getPluralOperand(Operand.n);
722             double actualIOperand = dq.getPluralOperand(Operand.i);
723             double actualEOperand = dq.getPluralOperand(Operand.e);
724 
725             assertEquals(
726                     String.format("formatted number %s toString: %s", skeleton, input),
727                     expectedString,
728                     actualString);
729             assertDoubleEquals(
730                     String.format("compact decimal %s n operand: %f", skeleton, input),
731                     expectedNOperand,
732                     actualNOperand);
733             assertDoubleEquals(
734                     String.format("compact decimal %s i operand: %f", skeleton, input),
735                     expectedIOperand,
736                     actualIOperand);
737             assertDoubleEquals(
738                     String.format("compact decimal %s e operand: %f", skeleton, input),
739                     expectedEOperand,
740                     actualEOperand);
741         }
742     }
743 
744 
745     @Test
testCompactNotationFractionPluralOperands()746     public void testCompactNotationFractionPluralOperands() {
747         ULocale locale = new ULocale("fr-FR");
748         LocalizedNumberFormatter formatter =
749                 NumberFormatter.withLocale(locale)
750                     .notation(Notation.compactLong())
751                     .precision(Precision.fixedFraction(5))
752                     .scale(Scale.powerOfTen(-1));
753         double formatterInput = 12345;
754         double inputVal = 1234.5;
755         FormattedNumber fn = formatter.format(formatterInput);
756         DecimalQuantity_DualStorageBCD dq = (DecimalQuantity_DualStorageBCD)
757                 fn.getFixedDecimal();
758 
759         double expectedNOperand = 1234.5;
760         double expectedIOperand = 1234;
761         double expectedFOperand = 50;
762         double expectedTOperand = 5;
763         double expectedVOperand = 2;
764         double expectedWOperand = 1;
765         double expectedEOperand = 3;
766         String expectedString = "1,23450 millier";
767         double actualNOperand = dq.getPluralOperand(Operand.n);
768         double actualIOperand = dq.getPluralOperand(Operand.i);
769         double actualFOperand = dq.getPluralOperand(Operand.f);
770         double actualTOperand = dq.getPluralOperand(Operand.t);
771         double actualVOperand = dq.getPluralOperand(Operand.v);
772         double actualWOperand = dq.getPluralOperand(Operand.w);
773         double actualEOperand = dq.getPluralOperand(Operand.e);
774         String actualString = fn.toString();
775 
776         assertDoubleEquals(
777                 String.format("compact decimal fraction n operand: %f", inputVal),
778                 expectedNOperand,
779                 actualNOperand);
780         assertDoubleEquals(
781                 String.format("compact decimal fraction i operand: %f", inputVal),
782                 expectedIOperand,
783                 actualIOperand);
784         assertDoubleEquals(
785                 String.format("compact decimal fraction f operand: %f", inputVal),
786                 expectedFOperand,
787                 actualFOperand);
788         assertDoubleEquals(
789                 String.format("compact decimal fraction t operand: %f", inputVal),
790                 expectedTOperand,
791                 actualTOperand);
792         assertDoubleEquals(
793                 String.format("compact decimal fraction v operand: %f", inputVal),
794                 expectedVOperand,
795                 actualVOperand);
796         assertDoubleEquals(
797                 String.format("compact decimal fraction w operand: %f", inputVal),
798                 expectedWOperand,
799                 actualWOperand);
800         assertDoubleEquals(
801                 String.format("compact decimal fraction e operand: %f", inputVal),
802                 expectedEOperand,
803                 actualEOperand);
804         assertEquals(
805                 String.format("compact decimal fraction toString: %f", inputVal),
806                 expectedString,
807                 actualString);
808     }
809 
810     @Test
testSuppressedExponentUnchangedByInitialScaling()811     public void testSuppressedExponentUnchangedByInitialScaling() {
812         ULocale locale = new ULocale("fr-FR");
813         LocalizedNumberFormatter withLocale = NumberFormatter.withLocale(locale);
814         LocalizedNumberFormatter compactLong =
815                 withLocale.notation(Notation.compactLong());
816         LocalizedNumberFormatter compactScaled =
817                 compactLong.scale(Scale.powerOfTen(3));
818 
819         Object[][] casesData = {
820                 // input, compact long string output,
821                 // compact n operand, compact i operand, compact e operand
822                 {123456789, "123 millions", 123000000.0, 123000000.0, 6.0},
823                 {1234567,   "1,2 million",  1200000.0,   1200000.0,   6.0},
824                 {123456,    "123 mille",    123000.0,    123000.0,    3.0},
825                 {123,       "123",          123.0,       123.0,       0.0},
826         };
827 
828         for (Object[] caseDatum : casesData) {
829             int input = (int) caseDatum[0];
830             String expectedString = (String) caseDatum[1];
831             double expectedNOperand = (double) caseDatum[2];
832             double expectedIOperand = (double) caseDatum[3];
833             double expectedEOperand = (double) caseDatum[4];
834 
835             FormattedNumber fnCompactScaled = compactScaled.format(input);
836             DecimalQuantity_DualStorageBCD dqCompactScaled =
837                     (DecimalQuantity_DualStorageBCD) fnCompactScaled.getFixedDecimal();
838             double compactScaledEOperand = dqCompactScaled.getPluralOperand(Operand.e);
839 
840             FormattedNumber fnCompact = compactLong.format(input);
841             DecimalQuantity_DualStorageBCD dqCompact =
842                     (DecimalQuantity_DualStorageBCD) fnCompact.getFixedDecimal();
843             String actualString = fnCompact.toString();
844             double compactNOperand = dqCompact.getPluralOperand(Operand.n);
845             double compactIOperand = dqCompact.getPluralOperand(Operand.i);
846             double compactEOperand = dqCompact.getPluralOperand(Operand.e);
847             assertEquals(
848                     String.format("formatted number compactLong toString: %s", input),
849                     expectedString,
850                     actualString);
851             assertDoubleEquals(
852                     String.format("compact decimal %d, n operand vs. expected", input),
853                     expectedNOperand,
854                     compactNOperand);
855             assertDoubleEquals(
856                     String.format("compact decimal %d, i operand vs. expected", input),
857                     expectedIOperand,
858                     compactIOperand);
859             assertDoubleEquals(
860                     String.format("compact decimal %d, e operand vs. expected", input),
861                     expectedEOperand,
862                     compactEOperand);
863 
864             // By scaling by 10^3 in a locale that has words / compact notation
865             // based on powers of 10^3, we guarantee that the suppressed
866             // exponent will differ by 3.
867             assertDoubleEquals(
868                     String.format("decimal %d, e operand for compact vs. compact scaled", input),
869                     compactEOperand + 3,
870                     compactScaledEOperand);
871         }
872     }
873 
doubleEquals(double d1, double d2)874     static boolean doubleEquals(double d1, double d2) {
875         return (Math.abs(d1 - d2) < 1e-6) || (Math.abs((d1 - d2) / d1) < 1e-6);
876     }
877 
assertDoubleEquals(String message, double d1, double d2)878     static void assertDoubleEquals(String message, double d1, double d2) {
879         boolean equal = doubleEquals(d1, d2);
880         handleAssert(equal, message, d1, d2, null, false);
881     }
882 
assertBigDecimalEquals(String message, String d1, BigDecimal d2)883     static void assertBigDecimalEquals(String message, String d1, BigDecimal d2) {
884         assertBigDecimalEquals(message, new BigDecimal(d1), d2);
885     }
886 
assertBigDecimalEquals(String message, BigDecimal d1, BigDecimal d2)887     static void assertBigDecimalEquals(String message, BigDecimal d1, BigDecimal d2) {
888         boolean equal = d1.compareTo(d2) == 0;
889         handleAssert(equal, message, d1, d2, null, false);
890     }
891 
assertToStringAndHealth(DecimalQuantity_DualStorageBCD fq, String expected)892     static void assertToStringAndHealth(DecimalQuantity_DualStorageBCD fq, String expected) {
893         String actual = fq.toString();
894         assertEquals("DecimalQuantity toString", expected, actual);
895         String health = fq.checkHealth();
896         assertNull("DecimalQuantity health", health);
897     }
898 }
899