1 /*
   2  * Copyright (c) 1997, 2016, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 /**
  25  * @test
  26  * @bug 4122840 4135202 4408066 4838107 8008577
  27  * @summary test NumberFormat
  28  * @library /java/text/testlib
  29  * @modules java.base/sun.util.resources
  30  * @compile -XDignore.symbol.file NumberTest.java
  31  * @run main/othervm -Djava.locale.providers=COMPAT,SPI NumberTest
  32  */
  33 
  34 import java.util.*;
  35 import java.text.*;
  36 import sun.util.resources.LocaleData;
  37 
  38 public class NumberTest extends IntlTest
  39 {
  40     public static void main(String[] args) throws Exception {
  41         new NumberTest().run(args);
  42     }
  43 
  44     // Test pattern handling
  45     public void TestPatterns()
  46     {
  47     DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance(Locale.US);
  48     String pat[]    = { "#.#", "#.", ".#", "#" };
  49     String newpat[] = { "#0.#", "#0.", "#.0", "#" };
  50     String num[]    = { "0",   "0.", ".0", "0" };
  51     for (int i=0; i<pat.length; ++i)
  52     {
  53         DecimalFormat fmt = new DecimalFormat(pat[i], sym);
  54         String newp = fmt.toPattern();
  55         if (!newp.equals(newpat[i]))
  56         errln("FAIL: Pattern " + pat[i] + " should transmute to " + newpat[i] +
  57               "; " + newp + " seen instead");
  58 
  59         String s = fmt.format(0);
  60         if (!s.equals(num[i]))
  61         {
  62         errln("FAIL: Pattern " + pat[i] + " should format zero as " + num[i] +
  63               "; " + s + " seen instead");
  64         logln("Min integer digits = " + fmt.getMinimumIntegerDigits());
  65         }
  66     }
  67     }
  68 
  69     // Test exponential pattern
  70     public void TestExponential() {
  71         DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance(Locale.US);
  72         String pat[] = { "0.####E0", "00.000E00", "##0.####E000", "0.###E0;[0.###E0]"  };
  73         double val[] = { 0.01234, 123456789, 1.23e300, -3.141592653e-271 };
  74         long lval[] = { 0, -1, 1, 123456789 };
  75         String valFormat[] = {
  76                 "1.234E-2", "1.2346E8", "1.23E300", "-3.1416E-271",
  77                 "12.340E-03", "12.346E07", "12.300E299", "-31.416E-272",
  78                 "12.34E-003", "123.4568E006", "1.23E300", "-314.1593E-273",
  79                 "1.234E-2", "1.235E8", "1.23E300", "[3.142E-271]"
  80         };
  81         String lvalFormat[] = {
  82                 "0E0", "-1E0", "1E0", "1.2346E8",
  83                 "00.000E00", "-10.000E-01", "10.000E-01", "12.346E07",
  84                 "0E000", "-1E000", "1E000", "123.4568E006",
  85                 "0E0", "[1E0]", "1E0", "1.235E8"
  86         };
  87         double valParse[] = {
  88                 0.01234, 123460000, 1.23E300, -3.1416E-271,
  89                 0.01234, 123460000, 1.23E300, -3.1416E-271,
  90                 0.01234, 123456800, 1.23E300, -3.141593E-271,
  91                 0.01234, 123500000, 1.23E300, -3.142E-271,
  92         };
  93         long lvalParse[] = {
  94                 0, -1, 1, 123460000,
  95                 0, -1, 1, 123460000,
  96                 0, -1, 1, 123456800,
  97                 0, -1, 1, 123500000,
  98         };
  99         int ival = 0, ilval = 0;
 100         for (int p=0; p<pat.length; ++p) {
 101             DecimalFormat fmt = new DecimalFormat(pat[p], sym);
 102             logln("Pattern \"" + pat[p] + "\" -toPattern-> \"" +
 103                   fmt.toPattern() + '"');
 104 
 105             for (int v=0; v<val.length; ++v) {
 106                 String s = fmt.format(val[v]);
 107                 logln(" Format " + val[v] + " -> " + escape(s));
 108                 if (!s.equals(valFormat[v+ival])) {
 109                     errln("FAIL: Expected " + valFormat[v+ival] +
 110                           ", got " + s +
 111                           ", pattern=" + fmt.toPattern());
 112                 }
 113 
 114                 ParsePosition pos = new ParsePosition(0);
 115                 Number a = fmt.parse(s, pos);
 116                 if (pos.getIndex() == s.length()) {
 117                     logln(" Parse -> " + a);
 118                     if (a.doubleValue() != valParse[v+ival]) {
 119                         errln("FAIL: Expected " + valParse[v+ival] +
 120                               ", got " + a.doubleValue() +
 121                               ", pattern=" + fmt.toPattern());
 122                     }
 123                 } else {
 124                     errln(" FAIL: Partial parse (" + pos.getIndex() +
 125                           " chars) -> " + a);
 126                 }
 127             }
 128             for (int v=0; v<lval.length; ++v) {
 129                 String s = fmt.format(lval[v]);
 130                 logln(" Format " + lval[v] + "L -> " + escape(s));
 131                 if (!s.equals(lvalFormat[v+ilval])) {
 132                     errln("ERROR: Expected " + lvalFormat[v+ilval] +
 133                           ", got " + s +
 134                           ", pattern=" + fmt.toPattern());
 135                 }
 136 
 137                 ParsePosition pos = new ParsePosition(0);
 138                 Number a = fmt.parse(s, pos);
 139                 if (pos.getIndex() == s.length()) {
 140                     logln(" Parse -> " + a);
 141                     if (a.longValue() != lvalParse[v+ilval]) {
 142                         errln("FAIL: Expected " + lvalParse[v+ilval] +
 143                               ", got " + a +
 144                               ", pattern=" + fmt.toPattern());
 145                     }
 146                 } else {
 147                     errln(" FAIL: Partial parse (" + pos.getIndex() +
 148                           " chars) -> " + a);
 149                 }
 150             }
 151             ival += val.length;
 152             ilval += lval.length;
 153         }
 154     }
 155 
 156     // Test the handling of quotes
 157     public void TestQuotes()
 158     {
 159     String pat;
 160     DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance(Locale.US);
 161     DecimalFormat fmt = new DecimalFormat(pat = "a'fo''o'b#", sym);
 162     String s = fmt.format(123);
 163     logln("Pattern \"" + pat + "\"");
 164     logln(" Format 123 -> " + escape(s));
 165     if (!s.equals("afo'ob123")) errln("FAIL: Expected afo'ob123");
 166 
 167     fmt = new DecimalFormat(pat = "a''b#", sym);
 168     s = fmt.format(123);
 169     logln("Pattern \"" + pat + "\"");
 170     logln(" Format 123 -> " + escape(s));
 171     if (!s.equals("a'b123")) errln("FAIL: Expected a'b123");
 172     }
 173 
 174     // Test the use of the currency sign
 175     public void TestCurrencySign()
 176     {
 177     DecimalFormatSymbols sym = DecimalFormatSymbols.getInstance(Locale.US);
 178     DecimalFormat fmt = new DecimalFormat("\u00A4#,##0.00;-\u00A4#,##0.00", sym);
 179     // Can't test this properly until currency API goes public
 180     // DecimalFormatSymbols sym = fmt.getDecimalFormatSymbols();
 181 
 182     String s = fmt.format(1234.56);
 183     logln("Pattern \"" + fmt.toPattern() + "\"");
 184     logln(" Format " + 1234.56 + " -> " + escape(s));
 185     if (!s.equals("$1,234.56")) errln("FAIL: Expected $1,234.56");
 186     s = fmt.format(-1234.56);
 187     logln(" Format " + -1234.56 + " -> " + escape(s));
 188     if (!s.equals("-$1,234.56")) errln("FAIL: Expected -$1,234.56");
 189 
 190     fmt = new DecimalFormat("\u00A4\u00A4 #,##0.00;\u00A4\u00A4 -#,##0.00", sym);
 191     s = fmt.format(1234.56);
 192     logln("Pattern \"" + fmt.toPattern() + "\"");
 193     logln(" Format " + 1234.56 + " -> " + escape(s));
 194     if (!s.equals("USD 1,234.56")) errln("FAIL: Expected USD 1,234.56");
 195     s = fmt.format(-1234.56);
 196     logln(" Format " + -1234.56 + " -> " + escape(s));
 197     if (!s.equals("USD -1,234.56")) errln("FAIL: Expected USD -1,234.56");
 198     }
 199     static String escape(String s)
 200     {
 201     StringBuffer buf = new StringBuffer();
 202     char HEX[] = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F' };
 203     for (int i=0; i<s.length(); ++i)
 204     {
 205         char c = s.charAt(i);
 206         if (c <= (char)0x7F) buf.append(c);
 207         else
 208         {
 209         buf.append("\\U");
 210         buf.append(HEX[(c & 0xF000) >> 12]);
 211         buf.append(HEX[(c & 0x0F00) >> 8]);
 212         buf.append(HEX[(c & 0x00F0) >> 4]);
 213         buf.append(HEX[c & 0x000F]);
 214         }
 215     }
 216     return buf.toString();
 217     }
 218 
 219     // Test simple currency format
 220     // Bug 4024941; this code used to throw a NumberFormat exception
 221     public void TestCurrency() {
 222         NumberFormat currencyFmt =
 223                 NumberFormat.getCurrencyInstance(Locale.CANADA_FRENCH);
 224         String s = currencyFmt.format(1.50);
 225         logln("Un pauvre ici a..........." + s);
 226         if (!s.equals("1,50 $")) {
 227             errln("FAIL: Expected 1,50 $; got " + s + "; "+ dumpFmt(currencyFmt));
 228         }
 229         currencyFmt = NumberFormat.getCurrencyInstance(Locale.GERMANY);
 230         s = currencyFmt.format(1.50);
 231         logln("Un pauvre en Allemagne a.." + s);
 232         if (!s.equals("1,50 \u20AC")) {
 233             errln("FAIL: Expected 1,50 \u20AC; got " + s + "; " + dumpFmt(currencyFmt));
 234         }
 235         currencyFmt = NumberFormat.getCurrencyInstance(Locale.FRANCE);
 236         s = currencyFmt.format(1.50);
 237         logln("Un pauvre en France a....." + s);
 238         if (!s.equals("1,50 \u20AC")) {
 239             errln("FAIL: Expected 1,50 \u20AC; got " + s + "; " + dumpFmt(currencyFmt));
 240         }
 241     }
 242 
 243     String dumpFmt(NumberFormat numfmt) {
 244         DecimalFormat fmt = (DecimalFormat)numfmt;
 245         StringBuffer buf = new StringBuffer();
 246         buf.append("pattern \"");
 247         buf.append(fmt.toPattern());
 248         buf.append("\", currency \"");
 249         buf.append(fmt.getDecimalFormatSymbols().getCurrencySymbol());
 250         buf.append("\"");
 251         return buf.toString();
 252     }
 253 
 254     // Test numeric parsing
 255     // Bug 4059870
 256     public void TestParse()
 257     {
 258     String arg = "0";
 259     java.text.DecimalFormat format = new java.text.DecimalFormat("00");
 260     try {
 261         Number n = format.parse(arg);
 262         logln("parse(" + arg + ") = " + n);
 263         if (n.doubleValue() != 0.0) errln("FAIL: Expected 0");
 264     } catch (Exception e) { errln("Exception caught: " + e); }
 265     }
 266 
 267     // Test rounding
 268     public void TestRounding487() {
 269         NumberFormat nf = NumberFormat.getInstance(Locale.US);
 270         roundingTest(nf, 0.00159999, 4, "0.0016");
 271         roundingTest(nf, 0.00995,  4, "0.01");
 272         roundingTest(nf, 12.7995,  3, "12.8");
 273         roundingTest(nf, 12.4999,  0, "12");
 274         roundingTest(nf, -19.5,  0, "-20");
 275     }
 276 
 277     void roundingTest(NumberFormat nf, double x, int maxFractionDigits, String expected) {
 278         nf.setMaximumFractionDigits(maxFractionDigits);
 279         String out = nf.format(x);
 280         logln("" + x + " formats with " + maxFractionDigits + " fractional digits to " + out);
 281         if (!out.equals(expected)) {
 282             errln("FAIL: Expected " + expected + ", got " + out);
 283         }
 284     }
 285 
 286     /**
 287      * Bug 4135202
 288      * DecimalFormat should recognize not only Latin digits 0-9 (\u0030-\u0039)
 289      * but also various other ranges of Unicode digits, such as Arabic
 290      * digits \u0660-\u0669 and Devanagari digits \u0966-\u096F, to name
 291      * a couple.
 292      * @see java.lang.Character#isDigit(char)
 293      */
 294     public void TestUnicodeDigits() {
 295         char[] zeros = {
 296             0x0030, // ISO-LATIN-1 digits ('0' through '9')
 297             0x0660, // Arabic-Indic digits
 298             0x06F0, // Extended Arabic-Indic digits
 299             0x0966, // Devanagari digits
 300             0x09E6, // Bengali digits
 301             0x0A66, // Gurmukhi digits
 302             0x0AE6, // Gujarati digits
 303             0x0B66, // Oriya digits
 304             0x0BE6, // Tamil digits
 305             0x0C66, // Telugu digits
 306             0x0CE6, // Kannada digits
 307             0x0D66, // Malayalam digits
 308             0x0E50, // Thai digits
 309             0x0ED0, // Lao digits
 310             0x0F20, // Tibetan digits
 311             0xFF10, // Fullwidth digits
 312         };
 313         NumberFormat format = NumberFormat.getInstance();
 314         for (int i=0; i<zeros.length; ++i) {
 315             char zero = zeros[i];
 316             StringBuffer buf = new StringBuffer();
 317             buf.append((char)(zero+3));
 318             buf.append((char)(zero+1));
 319             buf.append((char)(zero+4));
 320             int n = -1;
 321             try {
 322                 n = format.parse(buf.toString()).intValue();
 323             }
 324             catch (ParseException e) { n = -2; }
 325             if (n != 314)
 326                 errln("Can't parse Unicode " + Integer.toHexString(zero) + " as digit (" + n + ")");
 327             else
 328                 logln("Parse digit " + Integer.toHexString(zero) + " ok");
 329         }
 330     }
 331 
 332     /**
 333      * Bug 4122840
 334      * Make sure that the currency symbol is not hard-coded in any locale.
 335      */
 336     public void TestCurrencySubstitution() {
 337         final String SYM = "<currency>";
 338         final String INTL_SYM = "<intl.currency>";
 339         Locale[] locales = NumberFormat.getAvailableLocales();
 340         for (int i=0; i<locales.length; ++i) {
 341             NumberFormat nf = NumberFormat.getCurrencyInstance(locales[i]);
 342             if (nf instanceof DecimalFormat) {
 343                 DecimalFormat df = (DecimalFormat)nf;
 344                 String genericPos = df.format(1234.5678);
 345                 String genericNeg = df.format(-1234.5678);
 346                 DecimalFormatSymbols sym = df.getDecimalFormatSymbols();
 347                 sym.setCurrencySymbol(SYM);
 348                 sym.setInternationalCurrencySymbol(INTL_SYM);
 349                 // We have to make a new DecimalFormat from scratch in order
 350                 // to make the new symbols 'take'.  This may be a bug or
 351                 // design flaw in DecimalFormat.
 352                 String[] patterns = LocaleData.getBundle("sun.text.resources.FormatData", locales[i])
 353                                               .getStringArray("NumberPatterns");
 354                 df = new DecimalFormat(patterns[1 /*CURRENCYSTYLE*/], sym);
 355                 String customPos = df.format(1234.5678);
 356                 String customNeg = df.format(-1234.5678);
 357                 if (genericPos.equals(customPos) || genericNeg.equals(customNeg)) {
 358                     errln("FAIL: " + locales[i] +
 359                           " not using currency symbol substitution: " + genericPos);
 360                 }
 361                 else {
 362                     if (customPos.indexOf(SYM) >= 0) {
 363                         if (customNeg.indexOf(INTL_SYM) >= 0)
 364                             errln("Fail: Positive and negative patterns use different symbols");
 365                         else
 366                             logln("Ok: " + locales[i] +
 367                                   " uses currency symbol: " + genericPos +
 368                                   ", " + customPos);
 369                     }
 370                     else if (customPos.indexOf(INTL_SYM) >= 0) {
 371                         if (customNeg.indexOf(SYM) >= 0)
 372                             errln("Fail: Positive and negative patterns use different symbols");
 373                         else
 374                             logln("Ok: " + locales[i] +
 375                                   " uses intl. currency symbol: " + genericPos +
 376                                   ", " + customPos);
 377                     }
 378                     else {
 379                         errln("FAIL: " + locales[i] +
 380                               " contains no currency symbol (impossible!)");
 381                     }
 382                 }
 383             }
 384             else logln("Skipping " + locales[i] + "; not a DecimalFormat");
 385         }
 386     }
 387 
 388     public void TestIntegerFormat() throws ParseException {
 389         NumberFormat format = NumberFormat.getIntegerInstance(Locale.GERMANY);
 390 
 391         float[] formatInput = { 12345.67f, -12345.67f, -0, 0 };
 392         String[] formatExpected = { "12.346", "-12.346", "0", "0" };
 393 
 394         for (int i = 0; i < formatInput.length; i++) {
 395             String result = format.format(formatInput[i]);
 396             if (!result.equals(formatExpected[i])) {
 397                 errln("FAIL: Expected " + formatExpected[i] + ", got " + result);
 398             }
 399         }
 400 
 401         String[] parseInput = { "0", "-0", "12.345,67", "-12.345,67" };
 402         float[] parseExpected = { 0, 0, 12345, -12345 };
 403 
 404         for (int i = 0; i < parseInput.length; i++) {
 405             float result = ((Number) format.parse(parseInput[i])).floatValue();
 406             if (result != parseExpected[i]) {
 407                 errln("FAIL: Expected " + parseExpected[i] + ", got " + result);
 408             }
 409         }
 410     }
 411 }