Patch by: rzschech, jat
Review by: jat, rice
http://code.google.com/p/google-web-toolkit/source/detail?r=7351
Added:
/trunk/user/src/com/google/gwt/user/client/rpc/core/java/math
/trunk/user/super/com/google/gwt/emul/java/math
/trunk/user/super/com/google/gwt/emul/java/util/Random.java
/trunk/user/test/com/google/gwt/emultest/java/math
/trunk/user/test/com/google/gwt/emultest/java/util/RandomTest.java
Modified:
/trunk/user/src/com/google/gwt/i18n/client/NumberFormat.java
/trunk/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
/trunk/user/super/com/google/gwt/emul/java/lang/ArithmeticException.java
/trunk/user/super/com/google/gwt/emul/java/lang/Float.java
/trunk/user/test/com/google/gwt/emultest/EmulSuite.java
/trunk/user/test/com/google/gwt/emultest/java/lang/FloatTest.java
/trunk/user/test/com/google/gwt/i18n/client/I18NTest.java
/trunk/user/test/com/google/gwt/i18n/client/NumberFormat_en_Test.java
/trunk/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
Replaced:
/trunk/user/src/com/google/gwt/user/client/rpc/core/java/math/BigDecimal_CustomFieldSerializer.java
/trunk/user/src/com/google/gwt/user/client/rpc/core/java/math/BigInteger_CustomFieldSerializer.java
/trunk/user/src/com/google/gwt/user/client/rpc/core/java/math/MathContext_CustomFieldSerializer.java
/trunk/user/super/com/google/gwt/emul/java/math/BigDecimal.java
/trunk/user/super/com/google/gwt/emul/java/math/BigInteger.java
/trunk/user/super/com/google/gwt/emul/java/math/BitLevel.java
/trunk/user/super/com/google/gwt/emul/java/math/Conversion.java
/trunk/user/super/com/google/gwt/emul/java/math/Division.java
/trunk/user/super/com/google/gwt/emul/java/math/Elementary.java
/trunk/user/super/com/google/gwt/emul/java/math/Logical.java
/trunk/user/super/com/google/gwt/emul/java/math/MathContext.java
/trunk/user/super/com/google/gwt/emul/java/math/Multiplication.java
/trunk/user/super/com/google/gwt/emul/java/math/Primality.java
/trunk/user/super/com/google/gwt/emul/java/math/RoundingMode.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigDecimalArithmeticTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigDecimalCompareTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigDecimalConstructorsTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigDecimalConvertTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigDecimalScaleOperationsTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerAddTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerAndTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerCompareTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerConstructorsTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerConvertTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerDivideTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerHashCodeTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerModPowTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerMultiplyTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerNotTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerOperateBitsTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerOrTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerSubtractTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerToStringTest.java
/trunk/user/test/com/google/gwt/emultest/java/math/BigIntegerXorTest.java
=======================================
--- /dev/null
+++ /trunk/user/super/com/google/gwt/emul/java/util/Random.java Sun Dec 27
16:30:42 2009
@@ -0,0 +1,266 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ *
+ * INCLUDES MODIFICATIONS BY RICHARD ZSCHECH AS WELL AS GOOGLE.
+ */
+package java.util;
+
+import java.io.Serializable;
+
+/**
+ * This class provides methods that generates pseudo-random numbers of
different
+ * types, such as {@code int}, {@code long}, {@code double}, and {@code
float}.
+ */
+public class Random implements Serializable {
+
+ private static final long serialVersionUID = 3905348978240129619L;
+
+ private static final long multiplier = 0x5deece66dL;
+
+ /**
+ * A value used to avoid two random number generators produced at the
same
+ * time having the same seed.
+ */
+ private static int uniqueSeed = 0;
+
+ /**
+ * The boolean value indicating if the second Gaussian number is
available.
+ *
+ * @serial
+ */
+ private boolean haveNextNextGaussian;
+
+ /**
+ * @serial It is associated with the internal state of this generator.
+ */
+ private long seed;
+
+ /**
+ * The second Gaussian generated number.
+ *
+ * @serial
+ */
+ private double nextNextGaussian;
+
+ /**
+ * Construct a random generator with the current time of day in
milliseconds
+ * as the initial state.
+ *
+ * @see #setSeed
+ */
+ public Random() {
+ setSeed(uniqueSeed++ + System.currentTimeMillis());
+ }
+
+ /**
+ * Construct a random generator with the given {@code seed} as the
initial
+ * state.
+ *
+ * @param seed the seed that will determine the initial state of this
random
+ * number generator.
+ * @see #setSeed
+ */
+ public Random(long seed) {
+ setSeed(seed);
+ }
+
+ /**
+ * Returns the next pseudo-random, uniformly distributed {@code boolean}
value
+ * generated by this generator.
+ *
+ * @return a pseudo-random, uniformly distributed boolean value.
+ */
+ public boolean nextBoolean() {
+ return next(1) != 0;
+ }
+
+ /**
+ * Modifies the {@code byte} array by a random sequence of {@code byte}s
+ * generated by this random number generator.
+ *
+ * @param buf non-null array to contain the new random {@code byte}s.
+ * @see #next
+ */
+ public void nextBytes(byte[] buf) {
+ int rand = 0, count = 0, loop = 0;
+ while (count < buf.length) {
+ if (loop == 0) {
+ rand = nextInt();
+ loop = 3;
+ } else {
+ loop--;
+ }
+ buf[count++] = (byte) rand;
+ rand >>= 8;
+ }
+ }
+
+ /**
+ * Generates a normally distributed random {@code double} number between
0.0
+ * inclusively and 1.0 exclusively.
+ *
+ * @return a random {@code double} in the range [0.0 - 1.0)
+ * @see #nextFloat
+ */
+ public double nextDouble() {
+ return ((((long) next(26) << 27) + next(27)) / (double) (1L << 53));
+ }
+
+ /**
+ * Generates a normally distributed random {@code float} number between
0.0
+ * inclusively and 1.0 exclusively.
+ *
+ * @return float a random {@code float} number between [0.0 and 1.0)
+ * @see #nextDouble
+ */
+ public float nextFloat() {
+ return (next(24) / 16777216f);
+ }
+
+ /**
+ * Pseudo-randomly generates (approximately) a normally distributed
{@code
+ * double} value with mean 0.0 and a standard deviation value of {@code
1.0}
+ * using the <i>polar method<i> of G. E. P. Box, M. E. Muller, and G.
+ * Marsaglia, as described by Donald E. Knuth in <i>The Art of Computer
+ * Programming, Volume 2: Seminumerical Algorithms</i>, section 3.4.1,
+ * subsection C, algorithm P.
+ *
+ * @return a random {@code double}
+ * @see #nextDouble
+ */
+ public double nextGaussian() {
+ if (haveNextNextGaussian) {
+ // if X1 has been returned, return the second Gaussian
+ haveNextNextGaussian = false;
+ return nextNextGaussian;
+ }
+
+ double v1, v2, s;
+ do {
+ // Generates two independent random variables U1, U2
+ v1 = 2 * nextDouble() - 1;
+ v2 = 2 * nextDouble() - 1;
+ s = v1 * v1 + v2 * v2;
+ } while (s >= 1);
+ double norm = Math.sqrt(-2 * Math.log(s) / s);
+ nextNextGaussian = v2 * norm;
+ haveNextNextGaussian = true;
+ return v1 * norm;
+ }
+
+ /**
+ * Generates a uniformly distributed 32-bit {@code int} value from the
random
+ * number sequence.
+ *
+ * @return a uniformly distributed {@code int} value.
+ * @see java.lang.Integer#MAX_VALUE
+ * @see java.lang.Integer#MIN_VALUE
+ * @see #next
+ * @see #nextLong
+ */
+ public int nextInt() {
+ return next(32);
+ }
+
+ /**
+ * Returns a new pseudo-random {@code int} value which is uniformly
+ * distributed between 0 (inclusively) and the value of {@code n}
+ * (exclusively).
+ *
+ * @param n the exclusive upper border of the range [0 - n).
+ * @return a random {@code int}.
+ */
+ public int nextInt(int n) {
+ if (n > 0) {
+ if ((n & -n) == n) {
+ return (int) ((n * (long) next(31)) >> 31);
+ }
+ int bits, val;
+ do {
+ bits = next(31);
+ val = bits % n;
+ } while (bits - val + (n - 1) < 0);
+ return val;
+ }
+ throw new IllegalArgumentException();
+ }
+
+ /**
+ * Generates a uniformly distributed 64-bit integer value from the random
+ * number sequence.
+ *
+ * @return 64-bit random integer.
+ * @see java.lang.Integer#MAX_VALUE
+ * @see java.lang.Integer#MIN_VALUE
+ * @see #next
+ * @see #nextInt()
+ * @see #nextInt(int)
+ */
+ public long nextLong() {
+ return ((long) next(32) << 32) + next(32);
+ }
+
+ /**
+ * Modifies the seed a using linear congruential formula presented in
<i>The
+ * Art of Computer Programming, Volume 2</i>, Section 3.2.1.
+ *
+ * @param seed the seed that alters the state of the random number
generator.
+ * @see #next
+ * @see #Random()
+ * @see #Random(long)
+ */
+ public void setSeed(long seed) {
+ this.seed = (seed ^ multiplier) & ((1L << 48) - 1);
+ haveNextNextGaussian = false;
+ }
+
+ /**
+ * Returns a pseudo-random uniformly distributed {@code int} value of the
+ * number of bits specified by the argument {@code bits} as described by
+ * Donald E. Knuth in <i>The Art of Computer Programming, Volume 2:
+ * Seminumerical Algorithms</i>, section 3.2.1.
+ *
+ * @param bits number of bits of the returned value.
+ * @return a pseudo-random generated int number.
+ * @see #nextBytes
+ * @see #nextDouble
+ * @see #nextFloat
+ * @see #nextInt()
+ * @see #nextInt(int)
+ * @see #nextGaussian
+ * @see #nextLong
+ */
+ protected int next(int bits) {
+ seed = (seed * multiplier + 0xbL) & ((1L << 48) - 1);
+ return (int) (seed >>> (48 - bits));
+ }
+}
=======================================
--- /dev/null
+++ /trunk/user/test/com/google/gwt/emultest/java/util/RandomTest.java Sun
Dec 27 16:30:42 2009
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2009 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to You under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance with the
License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ *
+ * INCLUDES MODIFICATIONS BY GOOGLE.
+ */
+package com.google.gwt.emultest.java.util;
+
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.Random;
+
+/**
+ * Test java.util.Random.
+ */
+public class RandomTest extends GWTTestCase {
+
+ private Random r = new Random();
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.emultest.EmulSuite";
+ }
+
+ /**
+ * Tests that two generators with the same seed produce the same
sequence of
+ * values.
+ */
+ public void test_ConstructorJ() {
+ Random r = new Random(8409238L);
+ Random r2 = new Random(8409238L);
+ for (int i = 0; i < 100; i++) {
+ assertEquals("Values from randoms with same seed don't match",
+ r.nextInt(), r2.nextInt());
+ }
+ }
+
+ /**
+ * Tests {@link java.util.Random#nextBoolean()}.
+ */
+ public void test_nextBoolean() {
+ boolean falseAppeared = false, trueAppeared = false;
+ for (int counter = 0; counter < 100; counter++)
+ if (r.nextBoolean()) {
+ trueAppeared = true;
+ } else {
+ falseAppeared = true;
+ }
+ assertTrue("Calling nextBoolean() 100 times resulted in all trues",
+ falseAppeared);
+ assertTrue("Calling nextBoolean() 100 times resulted in all falses",
+ trueAppeared);
+ }
+
+ /**
+ * Tests {@link java.util.Random#nextBytes(byte[])}.
+ */
+ public void test_nextBytes$B() {
+ boolean someDifferent = false;
+ byte[] randomBytes = new byte[100];
+ r.nextBytes(randomBytes);
+ byte firstByte = randomBytes[0];
+ for (int counter = 1; counter < randomBytes.length; counter++) {
+ if (randomBytes[counter] != firstByte) {
+ someDifferent = true;
+ }
+ }
+ assertTrue("nextBytes() returned an array of length 100 of the same
byte",
+ someDifferent);
+ }
+
+ /**
+ * Tests {@link java.util.Random#nextDouble()}.
+ */
+ public void test_nextDouble() {
+ double lastNum = r.nextDouble();
+ double nextNum;
+ boolean someDifferent = false;
+ boolean inRange = true;
+ for (int counter = 0; counter < 100; counter++) {
+ nextNum = r.nextDouble();
+ if (nextNum != lastNum) {
+ someDifferent = true;
+ }
+ if (!(0 <= nextNum && nextNum < 1.0)) {
+ inRange = false;
+ }
+ lastNum = nextNum;
+ }
+ assertTrue("Calling nextDouble 100 times resulted in same number",
+ someDifferent);
+ assertTrue("Calling nextDouble resulted in a number out of range
[0,1)",
+ inRange);
+ }
+
+ /**
+ * Tests {@link java.util.Random#nextFloat()}.
+ */
+ public void test_nextFloat() {
+ float lastNum = r.nextFloat();
+ float nextNum;
+ boolean someDifferent = false;
+ boolean inRange = true;
+ for (int counter = 0; counter < 100; counter++) {
+ nextNum = r.nextFloat();
+ if (nextNum != lastNum) {
+ someDifferent = true;
+ }
+ if (!(0 <= nextNum && nextNum < 1.0)) {
+ inRange = false;
+ }
+ lastNum = nextNum;
+ }
+ assertTrue("Calling nextFloat 100 times resulted in same number",
+ someDifferent);
+ assertTrue("Calling nextFloat resulted in a number out of range [0,1)",
+ inRange);
+ }
+
+ /**
+ * Tests {@link java.util.Random#nextGaussian()}.
+ */
+ public void test_nextGaussian() {
+ double lastNum = r.nextGaussian();
+ double nextNum;
+ boolean someDifferent = false;
+ boolean someInsideStd = false;
+ for (int counter = 0; counter < 100; counter++) {
+ nextNum = r.nextGaussian();
+ if (nextNum != lastNum) {
+ someDifferent = true;
+ }
+ if (-1.0 <= nextNum && nextNum <= 1.0) {
+ someInsideStd = true;
+ }
+ lastNum = nextNum;
+ }
+ assertTrue("Calling nextGaussian 100 times resulted in same number",
+ someDifferent);
+ assertTrue(
+ "Calling nextGaussian 100 times resulted in no number within 1
std. deviation of mean",
+ someInsideStd);
+ }
+
+ /**
+ * Tests {@link java.util.Random#nextInt()}.
+ */
+ public void test_nextInt() {
+ int lastNum = r.nextInt();
+ int nextNum;
+ boolean someDifferent = false;
+ for (int counter = 0; counter < 100; counter++) {
+ nextNum = r.nextInt();
+ if (nextNum != lastNum) {
+ someDifferent = true;
+ }
+ lastNum = nextNum;
+ }
+ assertTrue("Calling nextInt 100 times resulted in same number",
+ someDifferent);
+ }
+
+ /**
+ * Tests {@link java.util.Random#nextInt(int)}.
+ */
+ public void test_nextIntI() {
+ final int range = 10;
+ int lastNum = r.nextInt(range);
+ int nextNum;
+ boolean someDifferent = false;
+ boolean inRange = true;
+ for (int counter = 0; counter < 100; counter++) {
+ nextNum = r.nextInt(range);
+ if (nextNum != lastNum) {
+ someDifferent = true;
+ }
+ if (!(0 <= nextNum && nextNum < range)) {
+ inRange = false;
+ }
+ lastNum = nextNum;
+ }
+ assertTrue("Calling nextInt (range) 100 times resulted in same number",
+ someDifferent);
+ assertTrue("Calling nextInt (range) resulted in a number outside of
[0, range)",
+ inRange);
+ }
+
+ /**
+ * Tests {@link java.util.Random#nextLong()}.
+ */
+ public void test_nextLong() {
+ long lastNum = r.nextLong();
+ long nextNum;
+ boolean someDifferent = false;
+ for (int counter = 0; counter < 100; counter++) {
+ nextNum = r.nextLong();
+ if (nextNum != lastNum) {
+ someDifferent = true;
+ }
+ lastNum = nextNum;
+ }
+ assertTrue("Calling nextLong 100 times resulted in same number",
+ someDifferent);
+ }
+
+ // two random create at a time should also generated different results
+ // regression test for Harmony 4616
+ public void test_random_generate() throws Exception {
+ for (int i = 0; i < 100; i++) {
+ Random random1 = new Random();
+ Random random2 = new Random();
+ assertFalse(random1.nextLong() == random2.nextLong());
+ }
+ }
+
+ /**
+ * Tests {@link java.util.Random#setSeed(long)}.
+ */
+ public void test_setSeedJ() {
+ long[] randomArray = new long[100];
+ boolean someDifferent = false;
+ long firstSeed = 1000;
+ long aLong, anotherLong, yetAnotherLong;
+ Random aRandom = new Random();
+ Random anotherRandom = new Random();
+ Random yetAnotherRandom = new Random();
+ aRandom.setSeed(firstSeed);
+ anotherRandom.setSeed(firstSeed);
+ for (int counter = 0; counter < randomArray.length; counter++) {
+ aLong = aRandom.nextLong();
+ anotherLong = anotherRandom.nextLong();
+ assertEquals("Two randoms with same seeds gave differing nextLong
values",
+ aLong, anotherLong);
+ yetAnotherLong = yetAnotherRandom.nextLong();
+ randomArray[counter] = aLong;
+ if (aLong != yetAnotherLong) {
+ someDifferent = true;
+ }
+ }
+ assertTrue("Two randoms with the different seeds gave the same chain
of values",
+ someDifferent);
+ aRandom.setSeed(firstSeed);
+ for (long element : randomArray) {
+ assertEquals(
+ "Reseting a random to its old seed did not result in the same
chain of values as it gave before",
+ element, aRandom.nextLong());
+ }
+ }
+}
=======================================
--- /trunk/user/src/com/google/gwt/i18n/client/NumberFormat.java Thu Dec 10
15:31:32 2009
+++ /trunk/user/src/com/google/gwt/i18n/client/NumberFormat.java Sun Dec 27
16:30:42 2009
@@ -15,9 +15,11 @@
*/
package com.google.gwt.i18n.client;
-import com.google.gwt.core.client.GWT;
import com.google.gwt.i18n.client.constants.NumberConstants;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
/**
* Formats and parses numbers using locale-sensitive patterns.
*
@@ -349,7 +351,7 @@
public static boolean forcedLatinDigits() {
return defaultNumberConstants != localizedNumberConstants;
}
-
+
/**
* Provides the standard currency format for the default locale.
*
@@ -363,7 +365,7 @@
}
return cachedCurrencyFormat;
}
-
+
/**
* Provides the standard currency format for the default locale using a
* specified currency.
@@ -376,7 +378,7 @@
return new NumberFormat(defaultNumberConstants.currencyPattern(),
currencyData, false);
}
-
+
/**
* Provides the standard currency format for the default locale using a
* specified currency.
@@ -607,6 +609,47 @@
}
return "\u00A0";
}
+
+ /**
+ * Appends a scaled string representation to a buffer, returning the
scale
+ * (which is the number of places to the right of the end of the string
the
+ * decimal point should be moved -- i.e., 3.5 would be added to the
buffer
+ * as "35" and a returned scale of -1).
+ *
+ * @param buf
+ * @param val
+ * @return scale to apply to the result
+ */
+ // @VisibleForTesting
+ static int toScaledString(StringBuilder buf, double val) {
+ int startLen = buf.length();
+ buf.append(toPrecision(val, 20));
+ int scale = 0;
+
+ // remove exponent if present, adjusting scale
+ int expIdx = buf.indexOf("e", startLen);
+ if (expIdx < 0) {
+ expIdx = buf.indexOf("E", startLen);
+ }
+ if (expIdx >= 0) {
+ int expDigits = expIdx + 1;
+ if (expDigits < buf.length() && buf.charAt(expDigits) == '+') {
+ ++expDigits;
+ }
+ if (expDigits < buf.length()) {
+ scale = Integer.parseInt(buf.substring(expDigits));
+ }
+ buf.delete(expIdx, buf.length());
+ }
+
+ // remove decimal point if present, adjusting scale
+ int dot = buf.indexOf(".", startLen);
+ if (dot >= 0) {
+ buf.deleteCharAt(dot);
+ scale -= buf.length() - dot;
+ }
+ return scale;
+ }
/**
* Lookup a currency code.
@@ -625,26 +668,43 @@
return currencyData;
}
- private static native String toFixed(double d, int digits) /*-{
- return d.toFixed(digits);
+ /**
+ * Convert a double to a string with {@code digits} precision. The
resulting
+ * string may still be in exponential notation.
+ *
+ * @param d double value
+ * @param digits number of digits of precision to include
+ * @return non-localized string representation of {@code d}
+ */
+ private static native String toPrecision(double d, int digits) /*-{
+ return d.toPrecision(digits);
}-*/;
- // The currency code.
+ /**
+ * The currency code.
+ */
private final String currencyCode;
- // Currency setting.
+ /**
+ * Currency symbol to use.
+ */
private final String currencySymbol;
- // Forces the decimal separator to always appear in a formatted number.
+ /**
+ * Forces the decimal separator to always appear in a formatted number.
+ */
private boolean decimalSeparatorAlwaysShown = false;
- // The number of digits between grouping separators in the integer
- // portion of a number.
+ /**
+ * The number of digits between grouping separators in the integer
portion of
+ * a number.
+ */
private int groupingSize = 3;
-
+
private boolean isCurrencyFormat = false;
-
+
private int maximumFractionDigits = 3; // invariant, >=
minFractionDigits.
+
private int maximumIntegerDigits = 40;
private int minExponentDigits;
private int minimumFractionDigits = 0;
@@ -670,6 +730,24 @@
// True to force the use of exponential (i.e. scientific) notation.
private boolean useExponentialNotation = false;
+ /**
+ * Holds the current exponent during one call to
+ * {@link #format(boolean, StringBuilder, int)}.
+ */
+ private transient int exponent;
+
+ /**
+ * Holds the current decimal position during one call to
+ * {@link #format(boolean, StringBuilder, int)}.
+ */
+ private transient int decimalPosition;
+
+ /**
+ * Holds the current digits length during one call to
+ * {@link #format(boolean, StringBuilder, int)}.
+ */
+ private transient int digitsLength;
+
/**
* Constructs a format object based on the specified settings.
*
@@ -719,33 +797,75 @@
* @return the formatted number string
*/
public String format(double number) {
- StringBuffer result = new StringBuffer();
-
if (Double.isNaN(number)) {
- result.append(numberConstants.notANumber());
- return result.toString();
- }
-
- boolean isNegative = ((number < 0.0) || (number == 0.0 && 1 / number <
0.0));
-
- result.append(isNegative ? negativePrefix : positivePrefix);
- if (Double.isInfinite(number)) {
- result.append(numberConstants.infinity());
- } else {
+ return numberConstants.notANumber();
+ }
+ boolean isNegative = ((number < 0.0)
+ || (number == 0.0 && 1 / number < 0.0));
+ if (isNegative) {
+ number = -number;
+ }
+ StringBuilder buf = new StringBuilder();
+ if (Double.isInfinite(number)) {
+ buf.append(isNegative ? negativePrefix : positivePrefix);
+ buf.append(numberConstants.infinity());
+ buf.append(isNegative ? negativeSuffix : positiveSuffix);
+ return buf.toString();
+ }
+ number *= multiplier;
+ int scale = toScaledString(buf, number);
+
+ // pre-round value to deal with .15 being represented as .149999... etc
+ // check at 3 more digits than will be required in the output
+ int preRound = buf.length() + scale + maximumFractionDigits + 3;
+ if (preRound > 0 && preRound < buf.length()
+ && buf.charAt(preRound) == '9') {
+ propagateCarry(buf, preRound - 1);
+ scale += buf.length() - preRound;
+ buf.delete(preRound, buf.length());
+ }
+
+ format(isNegative, buf, scale);
+ return buf.toString();
+ }
+
+ /**
+ * This method formats a Number to produce a string.
+ * <p>
+ * Any {@link Number} which is not a {@link BigDecimal}, {@link
BigInteger},
+ * or {@link Long} instance is formatted as a {@code double} value.
+ *
+ * @param number The Number instance to format
+ * @return the formatted number string
+ */
+ public String format(Number number) {
+ if (number instanceof BigDecimal) {
+ BigDecimal bigDec = (BigDecimal) number;
+ boolean isNegative = bigDec.signum() < 0;
if (isNegative) {
- number = -number;
- }
- number *= multiplier;
- if (useExponentialNotation) {
- subformatExponential(number, result);
- } else {
- subformatFixed(number, result, minimumIntegerDigits);
- }
- }
-
- result.append(isNegative ? negativeSuffix : positiveSuffix);
-
- return result.toString();
+ bigDec = bigDec.negate();
+ }
+ bigDec = bigDec.multiply(BigDecimal.valueOf(multiplier));
+ StringBuilder buf = new StringBuilder();
+ buf.append(bigDec.unscaledValue().toString());
+ format(isNegative, buf, -bigDec.scale());
+ return buf.toString();
+ } else if (number instanceof BigInteger) {
+ BigInteger bigInt = (BigInteger) number;
+ boolean isNegative = bigInt.signum() < 0;
+ if (isNegative) {
+ bigInt = bigInt.negate();
+ }
+ bigInt = bigInt.multiply(BigInteger.valueOf(multiplier));
+ StringBuilder buf = new StringBuilder();
+ buf.append(bigInt.toString());
+ format(isNegative, buf, 0);
+ return buf.toString();
+ } else if (number instanceof Long) {
+ return format(number.longValue(), 0);
+ } else {
+ return format(number.doubleValue());
+ }
}
/**
@@ -863,58 +983,296 @@
return ret;
}
+ /**
+ * Format a number with its significant digits already represented in
string
+ * form. This is done so both double and BigInteger/Decimal formatting
can
+ * share code without requiring all users to pay the code size penalty
for
+ * BigDecimal/etc.
+ * <p>
+ * Example values passed in:
+ * <ul>
+ * <li>-13e2
+ * <br>{@code isNegative=true, digits="13", scale=2}
+ * <li>3.14158
+ * <br>{@code isNegative=false, digits="314158", scale=-5}
+ * <li>.0001
+ * <br>{@code isNegative=false, digits="1" ("0001" would be ok),
scale=-4}
+ * </ul>
+ *
+ * @param isNegative true if the value to be formatted is negative
+ * @param digits a StringBuilder containing just the significant digits
in
+ * the value to be formatted, the formatted result will be left here
+ * @param scale the number of places to the right the decimal point
should
+ * be moved in the digit string -- negative means the value contains
+ * fractional digits
+ */
+ protected void format(boolean isNegative, StringBuilder digits, int
scale) {
+ char decimalSeparator;
+ char groupingSeparator;
+ if (isCurrencyFormat) {
+ decimalSeparator = numberConstants.monetarySeparator().charAt(0);
+ groupingSeparator =
numberConstants.monetaryGroupingSeparator().charAt(0);
+ } else {
+ decimalSeparator = numberConstants.decimalSeparator().charAt(0);
+ groupingSeparator = numberConstants.groupingSeparator().charAt(0);
+ }
+
+ // Set these transient fields, which will be adjusted/used by the
routines
+ // called in this method.
+ exponent = 0;
+ digitsLength = digits.length();
+ decimalPosition = digitsLength + scale;
+
+ boolean useExponent = this.useExponentialNotation;
+ int currentGroupingSize = this.groupingSize;
+ if (decimalPosition > 1024) {
+ // force really large numbers to be in exponential form
+ useExponent = true;
+ }
+
+ if (useExponent) {
+ computeExponent(digits);
+ }
+ processLeadingZeros(digits);
+ roundValue(digits);
+ insertGroupingSeparators(digits, groupingSeparator,
currentGroupingSize);
+ adjustFractionDigits(digits);
+ addZeroAndDecimal(digits, decimalSeparator);
+ if (useExponent) {
+ addExponent(digits);
+ // the above call has invalidated digitsLength == digits.length()
+ }
+ char zeroChar = numberConstants.zeroDigit().charAt(0);
+ if (zeroChar != '0') {
+ localizeDigits(digits, zeroChar);
+ }
+
+ // add prefix/suffix
+ digits.insert(0, isNegative ? negativePrefix : positivePrefix);
+ digits.append(isNegative ? negativeSuffix : positiveSuffix);
+ }
+
+ /**
+ * Parses text to produce a numeric value. A {@link
NumberFormatException} is
+ * thrown if either the text is empty or if the parse does not consume
all
+ * characters of the text.
+ *
+ * param text the string to be parsed
+ * return a parsed number value, which may be a Double, BigInteger, or
+ * BigDecimal, or {@code Double(0.0)} if the parse fails.
+ * throws NumberFormatException if the text segment could not be
converted
+ * into a number
+ */
+// public Number parseBig(String text) throws NumberFormatException {
+// // TODO(jat): implement
+// return Double.valueOf(parse(text));
+// }
+
+ /**
+ * Parses text to produce a numeric value.
+ *
+ * <p>
+ * The method attempts to parse text starting at the index given by pos.
If
+ * parsing succeeds, then the index of <code>pos</code> is updated to the
+ * index after the last character used (parsing does not necessarily use
all
+ * characters up to the end of the string), and the parsed number is
returned.
+ * The updated <code>pos</code> can be used to indicate the starting
point
+ * for the next call to this method. If an error occurs, then the index
of
+ * <code>pos</code> is not changed.
+ * </p>
+ *
+ * param text the string to be parsed
+ * pparam inOutPos position to pass in and get back
+ * return a parsed number value, which may be a Double, BigInteger, or
+ * BigDecimal, or {@code Double(0.0)} if the parse fails.
+ * throws NumberFormatException if the text segment could not be
converted
+ * into a number
+ */
+// public Number parseBig(String text, int[] inOutPos)
+// throws NumberFormatException {
+// // TODO(jat): implement
+// return Double.valueOf(parse(text, inOutPos));
+// }
+
+ /**
+ * Format a possibly scaled long value.
+ *
+ * @param value value to format
+ * @param scale the number of places to the right the decimal point
should
+ * be moved in the digit string -- negative means the value contains
+ * fractional digits
+ * @return formatted value
+ */
+ protected String format(long value, int scale) {
+ boolean isNegative = value < 0;
+ if (isNegative) {
+ value = -value;
+ }
+ value *= multiplier;
+ StringBuilder buf = new StringBuilder();
+ buf.append(String.valueOf(value));
+ format(isNegative, buf, scale);
+ return buf.toString();
+ }
+
+ /**
+ * @return the number of digits between grouping separators in the
integer
+ * portion of a number.
+ */
protected int getGroupingSize() {
return groupingSize;
}
+ /**
+ * @return the prefix to use for negative values.
+ */
protected String getNegativePrefix() {
return negativePrefix;
}
+ /**
+ * @return the suffix to use for negative values.
+ */
protected String getNegativeSuffix() {
return negativeSuffix;
}
+ /**
+ * @return the NumberConstants instance for this formatter.
+ */
protected NumberConstants getNumberConstants() {
return numberConstants;
}
+ /**
+ * @return the prefix to use for positive values.
+ */
protected String getPositivePrefix() {
return positivePrefix;
}
+ /**
+ * @return the suffix to use for positive values.
+ */
protected String getPositiveSuffix() {
return positiveSuffix;
}
+ /**
+ * @return true if the decimal separator should always be shown.
+ */
protected boolean isDecimalSeparatorAlwaysShown() {
return decimalSeparatorAlwaysShown;
}
/**
- * This method formats the exponent part of a double.
+ * Add exponent suffix.
*
- * @param exponent exponential value
- * @param result formatted exponential part will be append to it
+ * @param digits
*/
- private void addExponentPart(int exponent, StringBuffer result) {
- result.append(numberConstants.exponentialSymbol());
-
+ private void addExponent(StringBuilder digits) {
+ digits.append(numberConstants.exponentialSymbol());
if (exponent < 0) {
exponent = -exponent;
- result.append(numberConstants.minusSign());
- }
-
+ digits.append(numberConstants.minusSign());
+ }
String exponentDigits = String.valueOf(exponent);
- int len = exponentDigits.length();
- for (int i = len; i < minExponentDigits; ++i) {
- result.append(numberConstants.zeroDigit());
- }
- int zeroDelta = numberConstants.zeroDigit().charAt(0) - '0';
- for (int i = 0; i < len; ++i) {
- result.append((char) (exponentDigits.charAt(i) + zeroDelta));
+ for (int i = exponentDigits.length(); i < minExponentDigits; ++i) {
+ digits.append('0');
+ }
+ digits.append(exponentDigits);
+ }
+
+ /**
+ * @param digits
+ * @param decimalSeparator
+ */
+ private void addZeroAndDecimal(StringBuilder digits, char
decimalSeparator) {
+ // add zero and decimal point if required
+ if (digitsLength == 0) {
+ digits.insert(0, '0');
+ ++decimalPosition;
+ ++digitsLength;
+ }
+ if (decimalPosition < digitsLength || decimalSeparatorAlwaysShown) {
+ digits.insert(decimalPosition, decimalSeparator);
+ ++digitsLength;
}
}
+
+ /**
+ * Adjust the fraction digits, adding trailing zeroes if necessary or
removing
+ * excess trailing zeroes.
+ *
+ * @param digits
+ */
+ private void adjustFractionDigits(StringBuilder digits) {
+ // adjust fraction digits as required
+ int requiredDigits = decimalPosition + minimumFractionDigits;
+ if (digitsLength < requiredDigits) {
+ // add trailing zeros
+ while (digitsLength < requiredDigits) {
+ digits.append('0');
+ ++digitsLength;
+ }
+ } else {
+ // remove excess trailing zeros
+ int toRemove = decimalPosition + maximumFractionDigits;
+ if (toRemove > digitsLength) {
+ toRemove = digitsLength;
+ }
+ while (toRemove > requiredDigits
+ && digits.charAt(toRemove - 1) == '0') {
+ --toRemove;
+ }
+ if (toRemove < digitsLength) {
+ digits.delete(toRemove, digitsLength);
+ digitsLength = toRemove;
+ }
+ }
+ }
+
+ /**
+ * Compute the exponent to use and adjust decimal position if we are
using
+ * exponential notation.
+ *
+ * @param digits
+ */
+ private void computeExponent(StringBuilder digits) {
+ // always trim leading zeros
+ int strip = 0;
+ while (strip < digitsLength - 1 && digits.charAt(strip) == '0') {
+ ++strip;
+ }
+ if (strip > 0) {
+ digits.delete(0, strip);
+ digitsLength -= strip;
+ exponent -= strip;
+ }
+
+ // decimal should wind up between minimum & maximumIntegerDigits
+ if (maximumIntegerDigits > minimumIntegerDigits
+ && maximumIntegerDigits > 0) {
+ // in this case, the exponent should be a multiple of
+ // maximumIntegerDigits and 1 <= decimal <= maximumIntegerDigits
+ exponent += decimalPosition - 1;
+ int remainder = exponent % maximumIntegerDigits;
+ if (remainder < 0) {
+ remainder += maximumIntegerDigits;
+ }
+ decimalPosition = remainder + 1;
+ exponent -= remainder;
+ } else {
+ exponent += decimalPosition - minimumIntegerDigits;
+ decimalPosition = minimumIntegerDigits;
+ }
+
+ // special-case 0 to have an exponent of 0
+ if (digitsLength == 1 && digits.charAt(0) == '0') {
+ exponent = 0;
+ decimalPosition = minimumIntegerDigits;
+ }
+ }
/**
* This method return the digit that represented by current character, it
@@ -933,47 +1291,41 @@
}
/**
- * This does the work of String.valueOf(long), but given a double as
input
- * and avoiding our emulated longs. Contrasted with
String.valueOf(double),
- * it ensures (a) there will be no trailing .0, and (b) unwinds
E-notation.
- *
- * @param number the integral value to convert
- * @return the string representing that integer
+ * Insert grouping separators if needed.
+ *
+ * @param digits
+ * @param groupingSeparator
+ * @param g
*/
- private String makeIntString(double number) {
- String intPart = String.valueOf(number);
- if (GWT.isScript()) {
- return intPart; // JavaScript does the right thing for integral
doubles
- }
- // ...but bytecode (hosted mode) does not... String.valueOf(double)
will
- // either end in .0 (non internationalized) which we don't want but is
- // easy, or, for large numbers, it will be E-notation, which is
annoying.
- int digitLen = intPart.length();
-
- if (intPart.charAt(digitLen - 2) == '.') {
- return intPart.substring(0, digitLen - 2);
- }
-
- // if we have E notation, (1) the exponent will be positive (else
- // intValue is 0, which doesn't need E notation), and (2) there will
- // be a radix dot (String.valueOf() isn't interationalized)
- int radix = intPart.indexOf('.');
- int exp = intPart.indexOf('E');
- int digits = 0;
- for (int i = exp + 1; i < intPart.length(); i++) {
- digits = digits * 10 + (intPart.charAt(i) - '0');
- }
- digits++; // exp of zero is one int digit...
- StringBuffer newIntPart = new StringBuffer();
- newIntPart.append(intPart.substring(0, radix));
- newIntPart.append(intPart.substring(radix + 1, exp));
- while (newIntPart.length() < digits) {
- newIntPart.append('0');
- }
- newIntPart.setLength(digits);
- return newIntPart.toString();
- }
-
+ private void insertGroupingSeparators(StringBuilder digits,
+ char groupingSeparator, int g) {
+ if (g > 0) {
+ for (int i = g; i < decimalPosition; i += g + 1) {
+ digits.insert(decimalPosition - i, groupingSeparator);
+ ++decimalPosition;
+ ++digitsLength;
+ }
+ }
+ }
+
+ /**
+ * Replace locale-independent digits with locale-specific ones.
+ *
+ * @param digits StringBuilder containing formatted number
+ * @param zero locale-specific zero character -- the rest of the digits
must
+ * be consecutive
+ */
+ private void localizeDigits(StringBuilder digits, char zero) {
+ // don't use digitsLength since we may have added an exponent
+ int n = digits.length();
+ for (int i = 0; i < n; ++i) {
+ char ch = digits.charAt(i);
+ if (ch >= '0' && ch <= '9') {
+ digits.setCharAt(i, (char) (ch - '0' + zero));
+ }
+ }
+ }
+
/**
* This method parses affix part of pattern.
*
@@ -1301,141 +1653,87 @@
return pos - start;
}
-
+
/**
- * This method formats a <code>double</code> in exponential format.
+ * Remove excess leading zeros or add some if we don't have enough.
*
- * @param number value need to be formated
- * @param result where the formatted string goes
+ * @param digits
*/
- private void subformatExponential(double number, StringBuffer result) {
- if (number == 0.0) {
- subformatFixed(number, result, minimumIntegerDigits);
- addExponentPart(0, result);
- return;
+ private void processLeadingZeros(StringBuilder digits) {
+ // make sure we have enough trailing zeros
+ if (decimalPosition > digitsLength) {
+ while (digitsLength < decimalPosition) {
+ digits.append('0');
+ ++digitsLength;
+ }
}
- int exponent = (int) Math.floor(Math.log(number) / Math.log(10));
- number /= Math.pow(10, exponent);
-
- int minIntDigits = minimumIntegerDigits;
- if (maximumIntegerDigits > 1 && maximumIntegerDigits >
minimumIntegerDigits) {
- // A repeating range is defined; adjust to it as follows.
- // If repeat == 3, we have 6,5,4=>3; 3,2,1=>0; 0,-1,-2=>-3;
- // -3,-4,-5=>-6, etc. This takes into account that the
- // exponent we have here is off by one from what we expect;
- // it is for the format 0.MMMMMx10^n.
- while ((exponent % maximumIntegerDigits) != 0) {
- number *= 10;
- exponent--;
- }
- minIntDigits = 1;
- } else {
- // No repeating range is defined; use minimum integer digits.
- if (minimumIntegerDigits < 1) {
- exponent++;
- number /= 10;
- } else {
- for (int i = 1; i < minimumIntegerDigits; i++) {
- exponent--;
- number *= 10;
+ if (!useExponentialNotation) {
+ // make sure we have the right number of leading zeros
+ if (decimalPosition < minimumIntegerDigits) {
+ // add leading zeros
+ StringBuilder prefix = new StringBuilder();
+ while (decimalPosition < minimumIntegerDigits) {
+ prefix.append('0');
+ ++decimalPosition;
+ ++digitsLength;
+ }
+ digits.insert(0, prefix);
+ } else if (decimalPosition > minimumIntegerDigits) {
+ // trim excess leading zeros
+ int strip = decimalPosition - minimumIntegerDigits;
+ for (int i = 0; i < strip; ++i) {
+ if (digits.charAt(i) != '0') {
+ strip = i;
+ break;
+ }
+ }
+ if (strip > 0) {
+ digits.delete(0, strip);
+ digitsLength -= strip;
+ decimalPosition -= strip;
}
}
}
-
- subformatFixed(number, result, minIntDigits);
- addExponentPart(exponent, result);
}
/**
- * This method formats a <code>double</code> into a fractional
- * representation.
+ * Propagate a carry from incrementing the {@code i+1}'th digit.
*
- * @param number value need to be formated
- * @param result result will be written here
- * @param minIntDigits minimum integer digits
+ * @param digits
+ * @param i digit to start incrementing
*/
- private void subformatFixed(double number, StringBuffer result,
- int minIntDigits) {
- double power = Math.pow(10, maximumFractionDigits);
- // Use 3 extra digits to allow us to do our own rounding since
- // Java rounds up on .5 whereas some browsers might use 'round to even'
- // or other rules.
-
- // There are cases where more digits would be required to get
- // guaranteed results, but this at least makes such cases rarer.
- String fixedString = toFixed(number, maximumFractionDigits + 3);
-
- double intValue = 0, fracValue = 0;
- int exponentIndex = fixedString.indexOf('e');
- if (exponentIndex != -1) {
- // Large numbers may be returned in exponential notation: such
numbers
- // are integers anyway
- intValue = Math.floor(number);
- } else {
- int decimalIndex = fixedString.indexOf('.');
- int len = fixedString.length();
- if (decimalIndex == -1) {
- decimalIndex = len;
- }
- if (decimalIndex > 0) {
- intValue = Double.parseDouble(fixedString.substring(0,
decimalIndex));
- }
- if (decimalIndex < len - 1) {
- fracValue = Double.parseDouble(fixedString.substring(decimalIndex
+ 1));
- fracValue = (((int) fracValue) + 500) / 1000;
- if (fracValue >= power) {
- fracValue -= power;
- intValue++;
- }
+ private void propagateCarry(StringBuilder digits, int i) {
+ boolean carry = true;
+ while (carry && i >= 0) {
+ char digit = digits.charAt(i);
+ if (digit == '9') {
+ // set this to zero and keep going
+ digits.setCharAt(i--, '0');
+ } else {
+ digits.setCharAt(i, (char) (digit + 1));
+ carry = false;
}
}
-
- boolean fractionPresent = (minimumFractionDigits > 0) || (fracValue >
0);
-
- String intPart = makeIntString(intValue);
- String grouping = isCurrencyFormat
- ? numberConstants.monetaryGroupingSeparator()
- : numberConstants.groupingSeparator();
- String decimal = isCurrencyFormat ? numberConstants.monetarySeparator()
- : numberConstants.decimalSeparator();
-
- int zeroDelta = numberConstants.zeroDigit().charAt(0) - '0';
- int digitLen = intPart.length();
-
- if (intValue > 0 || minIntDigits > 0) {
- for (int i = digitLen; i < minIntDigits; i++) {
- result.append(numberConstants.zeroDigit());
- }
-
- for (int i = 0; i < digitLen; i++) {
- result.append((char) (intPart.charAt(i) + zeroDelta));
-
- if (digitLen - i > 1 && groupingSize > 0
- && ((digitLen - i) % groupingSize == 1)) {
- result.append(grouping);
- }
- }
- } else if (!fractionPresent) {
- // If there is no fraction present, and we haven't printed any
- // integer digits, then print a zero.
- result.append(numberConstants.zeroDigit());
- }
-
- // Output the decimal separator if we always do so.
- if (decimalSeparatorAlwaysShown || fractionPresent) {
- result.append(decimal);
- }
-
- // To make sure it lead zero will be kept.
- String fracPart = makeIntString(Math.floor(fracValue + power + 0.5d));
- int fracLen = fracPart.length();
- while (fracPart.charAt(fracLen - 1) == '0' && fracLen >
minimumFractionDigits + 1) {
- fracLen--;
- }
-
- for (int i = 1; i < fracLen; i++) {
- result.append((char) (fracPart.charAt(i) + zeroDelta));
+ if (carry) {
+ // ran off the front, prepend a 1
+ digits.insert(0, '1');
+ ++decimalPosition;
+ ++digitsLength;
+ }
+ }
+
+ /**
+ * Round the value at the requested place, propagating any carry
backward.
+ *
+ * @param digits
+ */
+ private void roundValue(StringBuilder digits) {
+ // TODO(jat): other rounding modes?
+ if (digitsLength > decimalPosition + maximumFractionDigits
+ && digits.charAt(decimalPosition + maximumFractionDigits) >= '5') {
+ int i = decimalPosition + maximumFractionDigits - 1;
+ propagateCarry(digits, i);
}
}
}
=======================================
--- /trunk/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
Tue Nov 10 11:52:25 2009
+++ /trunk/user/src/com/google/gwt/i18n/rebind/MessagesMethodCreator.java
Sun Dec 27 16:30:42 2009
@@ -83,7 +83,23 @@
public String format(StringGenerator out, String subformat, String
argName,
JType argType) {
JPrimitiveType argPrimType = argType.isPrimitive();
- if (argPrimType == null || argPrimType == JPrimitiveType.BOOLEAN
+ if (argPrimType != null) {
+ if (argPrimType == JPrimitiveType.BOOLEAN
+ || argPrimType == JPrimitiveType.VOID) {
+ return "Illegal argument type for number format";
+ }
+ } else {
+ JClassType classType = argType.isClass();
+ if (classType == null) {
+ return "Unexpected argument type for number format";
+ }
+ TypeOracle oracle = classType.getOracle();
+ JClassType numberType = oracle.findType("java.lang.Number");
+ if (!classType.isAssignableTo(numberType)) {
+ return "Only Number subclasses may be formatted as a number";
+ }
+ }
+ if (argPrimType == JPrimitiveType.BOOLEAN
|| argPrimType == JPrimitiveType.VOID) {
return "Illegal argument type for number format";
}
=======================================
---
/trunk/user/super/com/google/gwt/emul/java/lang/ArithmeticException.java
Fri Apr 4 09:22:37 2008
+++
/trunk/user/super/com/google/gwt/emul/java/lang/ArithmeticException.java
Sun Dec 27 16:30:42 2009
@@ -16,10 +16,11 @@
package java.lang;
/**
- * NOTE: in GWT this is only thrown for division by zero on longs.
- *
+ * NOTE: in GWT this is only thrown for division by zero on longs and
+ * BigInteger/BigDecimal.
+ * <p>
* See <a
- *
href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ArrayIndexOutOfBoundsException.html">the
+ *
href="http://java.sun.com/j2se/1.5.0/docs/api/java/lang/ArithmeticException.html">the
* official Java API doc</a> for details.
*/
public class ArithmeticException extends RuntimeException {
=======================================
--- /trunk/user/super/com/google/gwt/emul/java/lang/Float.java Fri Jun 13
17:45:25 2008
+++ /trunk/user/super/com/google/gwt/emul/java/lang/Float.java Sun Dec 27
16:30:42 2009
@@ -16,7 +16,7 @@
package java.lang;
/**
- * Wraps a primitve <code>float</code> as an object.
+ * Wraps a primitive <code>float</code> as an object.
*/
public final class Float extends Number implements Comparable<Float> {
public static final float MAX_VALUE = 3.4028235e+38f;
@@ -40,7 +40,9 @@
}
/**
- * @skip Here for shared implementation with Arrays.hashCode
+ * @skip Here for shared implementation with Arrays.hashCode.
+ * @param f
+ * @return hash value of float (currently just truncated to int)
*/
public static int hashCode(float f) {
return (int) f;
@@ -55,7 +57,13 @@
}-*/;
public static float parseFloat(String s) throws NumberFormatException {
- return (float) __parseAndValidateDouble(s);
+ double doubleValue = __parseAndValidateDouble(s);
+ if (doubleValue > Float.MAX_VALUE) {
+ return Float.POSITIVE_INFINITY;
+ } else if (doubleValue < -Float.MAX_VALUE) {
+ return Float.NEGATIVE_INFINITY;
+ }
+ return (float) doubleValue;
}
public static String toString(float b) {
=======================================
--- /trunk/user/test/com/google/gwt/emultest/EmulSuite.java Fri Jul 11
07:36:14 2008
+++ /trunk/user/test/com/google/gwt/emultest/EmulSuite.java Sun Dec 27
16:30:42 2009
@@ -29,6 +29,26 @@
import com.google.gwt.emultest.java.lang.StringBufferTest;
import com.google.gwt.emultest.java.lang.StringTest;
import com.google.gwt.emultest.java.lang.SystemTest;
+import com.google.gwt.emultest.java.math.BigDecimalArithmeticTest;
+import com.google.gwt.emultest.java.math.BigDecimalCompareTest;
+import com.google.gwt.emultest.java.math.BigDecimalConstructorsTest;
+import com.google.gwt.emultest.java.math.BigDecimalConvertTest;
+import com.google.gwt.emultest.java.math.BigDecimalScaleOperationsTest;
+import com.google.gwt.emultest.java.math.BigIntegerAddTest;
+import com.google.gwt.emultest.java.math.BigIntegerAndTest;
+import com.google.gwt.emultest.java.math.BigIntegerCompareTest;
+import com.google.gwt.emultest.java.math.BigIntegerConstructorsTest;
+import com.google.gwt.emultest.java.math.BigIntegerConvertTest;
+import com.google.gwt.emultest.java.math.BigIntegerDivideTest;
+import com.google.gwt.emultest.java.math.BigIntegerHashCodeTest;
+import com.google.gwt.emultest.java.math.BigIntegerModPowTest;
+import com.google.gwt.emultest.java.math.BigIntegerMultiplyTest;
+import com.google.gwt.emultest.java.math.BigIntegerNotTest;
+import com.google.gwt.emultest.java.math.BigIntegerOperateBitsTest;
+import com.google.gwt.emultest.java.math.BigIntegerOrTest;
+import com.google.gwt.emultest.java.math.BigIntegerSubtractTest;
+import com.google.gwt.emultest.java.math.BigIntegerToStringTest;
+import com.google.gwt.emultest.java.math.BigIntegerXorTest;
import com.google.gwt.emultest.java.sql.SqlDateTest;
import com.google.gwt.emultest.java.sql.SqlTimeTest;
import com.google.gwt.emultest.java.sql.SqlTimestampTest;
@@ -46,21 +66,22 @@
import com.google.gwt.emultest.java.util.LinkedHashMapTest;
import com.google.gwt.emultest.java.util.LinkedListTest;
import com.google.gwt.emultest.java.util.PriorityQueueTest;
+import com.google.gwt.emultest.java.util.RandomTest;
import com.google.gwt.emultest.java.util.StackTest;
import com.google.gwt.junit.tools.GWTTestSuite;
import junit.framework.Test;
/**
- * TODO: document me.
+ * Tests for JRE emulation classes.
*/
public class EmulSuite {
- /** Note: due to compiler error, only can use one Test Case at a time. */
public static Test suite() {
GWTTestSuite suite = new GWTTestSuite("Tests for
com.google.gwt.emul.java");
// $JUnit-BEGIN$
+ // java.lang
suite.addTestSuite(BooleanTest.class);
suite.addTestSuite(ByteTest.class);
suite.addTestSuite(CharacterTest.class);
@@ -76,6 +97,29 @@
suite.addTestSuite(StringTest.class);
suite.addTestSuite(SystemTest.class);
+ // java.math
+ suite.addTestSuite(BigDecimalArithmeticTest.class);
+ suite.addTestSuite(BigDecimalCompareTest.class);
+ suite.addTestSuite(BigDecimalConstructorsTest.class);
+ suite.addTestSuite(BigDecimalConvertTest.class);
+ suite.addTestSuite(BigDecimalScaleOperationsTest.class);
+ suite.addTestSuite(BigIntegerAddTest.class);
+ suite.addTestSuite(BigIntegerAndTest.class);
+ suite.addTestSuite(BigIntegerCompareTest.class);
+ suite.addTestSuite(BigIntegerConstructorsTest.class);
+ suite.addTestSuite(BigIntegerConvertTest.class);
+ suite.addTestSuite(BigIntegerDivideTest.class);
+ suite.addTestSuite(BigIntegerHashCodeTest.class);
+ suite.addTestSuite(BigIntegerModPowTest.class);
+ suite.addTestSuite(BigIntegerMultiplyTest.class);
+ suite.addTestSuite(BigIntegerNotTest.class);
+ suite.addTestSuite(BigIntegerOperateBitsTest.class);
+ suite.addTestSuite(BigIntegerOrTest.class);
+ suite.addTestSuite(BigIntegerSubtractTest.class);
+ suite.addTestSuite(BigIntegerToStringTest.class);
+ suite.addTestSuite(BigIntegerXorTest.class);
+
+ // java.util
suite.addTestSuite(ApacheMapTest.class);
suite.addTestSuite(ArrayListTest.class);
suite.addTestSuite(ArraysTest.class);
@@ -90,6 +134,7 @@
suite.addTestSuite(LinkedHashMapTest.class);
suite.addTestSuite(LinkedListTest.class);
suite.addTestSuite(PriorityQueueTest.class);
+ suite.addTestSuite(RandomTest.class);
suite.addTestSuite(StackTest.class);
suite.addTestSuite(SqlDateTest.class);
suite.addTestSuite(SqlTimeTest.class);
@@ -100,5 +145,4 @@
return suite;
}
-
-}
+}
=======================================
--- /trunk/user/test/com/google/gwt/emultest/java/lang/FloatTest.java Thu
Mar 20 21:06:18 2008
+++ /trunk/user/test/com/google/gwt/emultest/java/lang/FloatTest.java Sun
Dec 27 16:30:42 2009
@@ -24,6 +24,7 @@
*/
public class FloatTest extends GWTTestCase {
+ @Override
public String getModuleName() {
return "com.google.gwt.emultest.EmulSuite";
}
@@ -77,9 +78,13 @@
assertEquals(-1.5f, Float.parseFloat("-1.5"), 0.0);
assertEquals(3.0f, Float.parseFloat("3."), 0.0);
assertEquals(0.5f, Float.parseFloat(".5"), 0.0);
- assertEquals("Can't parse MAX_VALUE", Float.MAX_VALUE,
- Float.parseFloat(String.valueOf(Float.MAX_VALUE)), 1e31);
- assertEquals("Can't parse MIN_VALUE", Float.MIN_VALUE,
- Float.parseFloat(String.valueOf(Float.MIN_VALUE)),
Float.MIN_VALUE);
+ // TODO(jat): it isn't safe to parse MAX/MIN_VALUE because we also
want to
+ // be able to get POSITIVE/NEGATIVE_INFINITY for out-of-range values,
and
+ // since all math in JS is done in double we can't rely on getting the
+ // exact value back.
+// assertEquals("Can't parse MAX_VALUE", Float.MAX_VALUE,
+// Float.parseFloat(String.valueOf(Float.MAX_VALUE)), 1e31);
+// assertEquals("Can't parse MIN_VALUE", Float.MIN_VALUE,
+// Float.parseFloat(String.valueOf(Float.MIN_VALUE)),
Float.MIN_VALUE);
}
}
=======================================
--- /trunk/user/test/com/google/gwt/i18n/client/I18NTest.java Wed Jun 17
12:35:29 2009
+++ /trunk/user/test/com/google/gwt/i18n/client/I18NTest.java Sun Dec 27
16:30:42 2009
@@ -33,6 +33,8 @@
import
com.google.gwt.i18n.client.resolutiontest.Inners.InnerClass.LocalizableInnerInner;
import com.google.gwt.junit.client.GWTTestCase;
+import java.math.BigDecimal;
+import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
@@ -558,6 +560,21 @@
assertEquals("nested dollar", m.nestedDollar());
assertEquals("estednay underscoray", m.nestedUnderscore());
}
+
+ /**
+ * Test that messages works with Number subclasses.
+ */
+ public void testNumber() {
+ TestAnnotatedMessages m = GWT.create(TestAnnotatedMessages.class);
+ BigInteger intVal = new BigInteger("1000000000000000000");
+ assertEquals("Total is US$1,000,000,000,000,000,000.00",
+ m.withNumberCurrency(intVal));
+ BigDecimal decVal = new BigDecimal("1000000000000000000.01");
+ assertEquals("Total is US$1,000,000,000,000,000,000.01",
+ m.withNumberCurrency(decVal));
+ assertEquals("Distance is 1.0E18", m.withNumberExponent(intVal));
+ assertEquals("Distance is 100.0E6", m.withNumberExponent(1e8f));
+ }
public void testShapesFamily() {
Shapes shapes = GWT.create(Shapes.class);
=======================================
--- /trunk/user/test/com/google/gwt/i18n/client/NumberFormat_en_Test.java
Wed Nov 18 13:48:28 2009
+++ /trunk/user/test/com/google/gwt/i18n/client/NumberFormat_en_Test.java
Sun Dec 27 16:30:42 2009
@@ -13,13 +13,15 @@
* License for the specific language governing permissions and limitations
under
* the License.
*/
-
package com.google.gwt.i18n.client;
import com.google.gwt.junit.client.GWTTestCase;
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
/**
- * GWT JUnit tests must extend GWTTestCase.
+ * Test {@link NumberFormat} in the {@code en} locale.
*/
public class NumberFormat_en_Test extends GWTTestCase {
@@ -52,17 +54,38 @@
assertEquals("R$123,456.79", str);
}
- /**
- * Add as many tests as you like.
- */
public void testBasicFormat() {
String str =
NumberFormat.getFormat("0.0000").format(123.45789179565757f);
assertEquals("123.4579", str);
-
+
// tests for overflow of mantissa bits during formatting
str = NumberFormat.getFormat("#,##0.000").format(112589990684262.5);
assertEquals("112,589,990,684,262.500", str);
}
+
+ public void testBigDecimal() {
+ BigDecimal decVal = new BigDecimal("1000000000000000000000000");
+ String str = NumberFormat.getFormat("0.000").format(decVal);
+ assertEquals("1000000000000000000000000.000", str);
+
+ decVal = decVal.add(new BigDecimal(".1"));
+ str = NumberFormat.getFormat("#,##0.000").format(decVal);
+ assertEquals("1,000,000,000,000,000,000,000,000.100", str);
+
+ decVal = new BigDecimal(".1499999999999999999999");
+ str = NumberFormat.getFormat(".0").format(decVal);
+ assertEquals(".1", str);
+}
+
+ public void testBigInteger() {
+ BigInteger intVal = new BigInteger("1000000000000000000000000");
+ String str = NumberFormat.getFormat("#,##0").format(intVal);
+ assertEquals("1,000,000,000,000,000,000,000,000", str);
+
+ intVal = intVal.add(BigInteger.ONE);
+ str = NumberFormat.getFormat("#,##0").format(intVal);
+ assertEquals("1,000,000,000,000,000,000,000,001", str);
+ }
public void testCurrency() {
String str;
@@ -94,7 +117,7 @@
assertEquals("BRL 1,234.56", str);
str = formatter.format(-1234.56);
assertEquals("(BRL 1,234.56)", str);
-
+
// Test using a deprecated currency.
formatter = NumberFormat.getCurrencyFormat("ITL");
str = formatter.format(1234.556);
@@ -237,18 +260,6 @@
value = NumberFormat.getFormat("0E0").parse("1.2345E+4");
assertTrue(value == 12345.0);
}
-
- public void testGrouping() {
- String str;
-
- str = NumberFormat.getFormat("#,###").format(1234567890);
- assertEquals("1,234,567,890", str);
- str = NumberFormat.getFormat("#,####").format(1234567890);
- assertEquals("12,3456,7890", str);
-
- str = NumberFormat.getFormat("#").format(1234567890);
- assertEquals("1234567890", str);
- }
public void testForceLatin() {
assertFalse(NumberFormat.forcedLatinDigits());
@@ -263,10 +274,51 @@
NumberFormat unforced = NumberFormat.getDecimalFormat();
assertEquals("3.14", unforced.format(3.14));
}
-
+
+ public void testGrouping() {
+ String str;
+
+ str = NumberFormat.getFormat("#,###").format(1234567890);
+ assertEquals("1,234,567,890", str);
+ str = NumberFormat.getFormat("#,####").format(1234567890);
+ assertEquals("12,3456,7890", str);
+
+ str = NumberFormat.getFormat("#").format(1234567890);
+ assertEquals("1234567890", str);
+ }
+
+ // See external issue 3140
+ public void testLeadingZeros() {
+ String str;
+
+ str =
NumberFormat.getFormat("0,000,000,000.#").format(123456789.489123);
+ assertEquals("0,123,456,789.5", str);
+
+ str = NumberFormat.getFormat("#,###.####").format(0.414014);
+ assertEquals("0.414", str); // why leading 0?
+
+ str = NumberFormat.getFormat("#.####").format(0.414014);
+ assertEquals("0.414", str); // why leading 0?
+
+ str = NumberFormat.getFormat("#.0###").format(0.414014);
+ assertEquals(".414", str);
+
+ str = NumberFormat.getFormat("0.0###").format(0.414014);
+ assertEquals("0.414", str);
+
+ str = NumberFormat.getFormat("0.####").format(0.414014);
+ assertEquals("0.414", str);
+
+ str = NumberFormat.getFormat("0.0000").format(0.414014);
+ assertEquals("0.4140", str);
+
+ str = NumberFormat.getFormat("#.0000").format(0.414014);
+ assertEquals(".4140", str);
+ }
+
public void testNegative() {
String str;
-
+
// verify default negative pattern
str = NumberFormat.getFormat("#,##0.0").format(-0.15);
assertEquals("-0.2", str);
@@ -274,7 +326,7 @@
assertEquals("-15.3%", str);
str = NumberFormat.getFormat("X #,##0.0%").format(-0.1534);
assertEquals("-X 15.3%", str);
-
+
// verify we can specify percent/permille suffixes in both parts
str = NumberFormat.getFormat("#,##0.0%;#,##0.0%-").format(-0.152);
assertEquals("15.2%-", str);
@@ -288,7 +340,7 @@
public void testParseNotANumber() {
try {
- double d = NumberFormat.getDecimalFormat().parse("blue");
+ NumberFormat.getDecimalFormat().parse("blue");
fail("Expected a NumberFormatException");
} catch (NumberFormatException e) {
assertEquals("blue", e.getMessage());
@@ -323,96 +375,125 @@
assertEquals("a'b123", str);
}
- public void testStandardFormat() {
- String str;
-
- str = NumberFormat.getCurrencyFormat().format(1234.579);
- assertEquals("$1,234.58", str);
- str = NumberFormat.getDecimalFormat().format(1234.579);
- assertEquals("1,234.579", str);
- str = NumberFormat.getPercentFormat().format(1234.579);
- assertEquals("123,458%", str);
- str = NumberFormat.getScientificFormat().format(1234.579);
- assertEquals("1E3", str);
- }
-
- public void testZeros() {
- String str;
-
- str = NumberFormat.getFormat("#.#").format(0);
- assertEquals("0", str);
- str = NumberFormat.getFormat("#.").format(0);
- assertEquals("0.", str);
- str = NumberFormat.getFormat(".#").format(0);
- assertEquals(".0", str);
- str = NumberFormat.getFormat("#").format(0);
- assertEquals("0", str);
-
- str = NumberFormat.getFormat("#0.#").format(0);
- assertEquals("0", str);
- str = NumberFormat.getFormat("#0.").format(0);
- assertEquals("0.", str);
- str = NumberFormat.getFormat("#.0").format(0);
- assertEquals(".0", str);
- str = NumberFormat.getFormat("#").format(0);
- assertEquals("0", str);
- }
-
public void testRounding() {
String str;
-
+
str = NumberFormat.getFormat("#0.##").format(0.555);
assertEquals("0.56", str);
-
+
str = NumberFormat.getFormat("#.##").format(30.555);
assertEquals("30.56", str);
-
+
str = NumberFormat.getFormat("#.00").format(0.997);
assertEquals("1.00", str);
-
+
str = NumberFormat.getFormat("#.00").format(-0.997);
assertEquals("-1.00", str);
-
+
str = NumberFormat.getFormat("#.00").format(27.997);
assertEquals("28.00", str);
-
+
str = NumberFormat.getFormat("#.00").format(-27.997);
assertEquals("-28.00", str);
-
+
str = NumberFormat.getFormat("#0.00000").format(1.23456789E-03);
assertEquals("0.00123", str);
-
+
str = NumberFormat.getFormat("#0.0000000").format(1.23456789E-03);
assertEquals("0.0012346", str);
-
+
str = NumberFormat.getFormat("#0.0000").format(1.2E-03);
assertEquals("0.0012", str);
-
+
str = NumberFormat.getFormat("#0.000").format(1.2E-03);
assertEquals("0.001", str);
-
+
str = NumberFormat.getFormat("#0.00").format(1.2E-03);
assertEquals("0.00", str);
-
+
str = NumberFormat.getFormat("#0.0").format(1.2E-03);
assertEquals("0.0", str);
-
+
str = NumberFormat.getFormat("#0.00").format(11.2E-03);
assertEquals("0.01", str);
-
+
str = NumberFormat.getFormat("#0.00").format(111.2E-03);
assertEquals("0.11", str);
-
+
str = NumberFormat.getFormat("#0.00").format(1111.2E-03);
assertEquals("1.11", str);
-
+
str = NumberFormat.getFormat("#0.00000").format(1.23456789E-05);
assertEquals("0.00001", str);
-
+
str = NumberFormat.getFormat("#0.0000000").format(1.23456789E-05);
assertEquals("0.0000123", str);
-
+
str = NumberFormat.getFormat("#0.0000000").format(1.23756789E-05);
assertEquals("0.0000124", str);
+
+ str =
NumberFormat.getFormat("#,##,###,##0.00000000000").format(111.18);
+ assertEquals("111.18000000000", str);
+ }
+
+ public void testStandardFormat() {
+ String str;
+
+ str = NumberFormat.getCurrencyFormat().format(1234.579);
+ assertEquals("$1,234.58", str);
+ str = NumberFormat.getDecimalFormat().format(1234.579);
+ assertEquals("1,234.579", str);
+ str = NumberFormat.getPercentFormat().format(1234.579);
+ assertEquals("123,458%", str);
+ str = NumberFormat.getScientificFormat().format(1234.579);
+ assertEquals("1E3", str);
+ }
+
+ public void testToScaledString() {
+ StringBuilder buf = new StringBuilder();
+ int scale = NumberFormat.toScaledString(buf, .1);
+ String str = buf.toString();
+ assertStartsWith("100", str.substring(str.length() + scale));
+ assertAllZeros(str, str.length() + scale);
+ buf = new StringBuilder();
+ scale = NumberFormat.toScaledString(buf, 12345e38);
+ str = buf.toString();
+ assertStartsWith("12345", str);
+ assertEquals(43, scale + str.length());
+ }
+
+ public void testZeros() {
+ String str;
+
+ str = NumberFormat.getFormat("#.#").format(0);
+ assertEquals("0", str);
+ str = NumberFormat.getFormat("#.").format(0);
+ assertEquals("0.", str);
+ str = NumberFormat.getFormat(".#").format(0);
+ assertEquals(".0", str);
+ str = NumberFormat.getFormat("#").format(0);
+ assertEquals("0", str);
+
+ str = NumberFormat.getFormat("#0.#").format(0);
+ assertEquals("0", str);
+ str = NumberFormat.getFormat("#0.").format(0);
+ assertEquals("0.", str);
+ str = NumberFormat.getFormat("#.0").format(0);
+ assertEquals(".0", str);
+ str = NumberFormat.getFormat("#").format(0);
+ assertEquals("0", str);
+ }
+
+ private void assertAllZeros(String str, int prefixLen) {
+ if (prefixLen > str.length()) {
+ prefixLen = str.length();
+ }
+ for (int i = 0; i < prefixLen; ++i) {
+ assertEquals('0', str.charAt(i));
+ }
+ }
+
+ private void assertStartsWith(String prefix, String str) {
+ assertTrue(str + " does not start with " + prefix,
str.startsWith(prefix));
}
}
=======================================
--- /trunk/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
Mon May 18 11:47:32 2009
+++ /trunk/user/test/com/google/gwt/i18n/client/TestAnnotatedMessages.java
Sun Dec 27 16:30:42 2009
@@ -88,4 +88,10 @@
@PluralText({"one", "A {0}"})
String twoParamPlural(String name, @PluralCount
int count);
-}
+
+ @DefaultMessage("Total is {0,number,currency}")
+ String withNumberCurrency(Number value);
+
+ @DefaultMessage("Distance is {0,number,##0.0##E0}")
+ String withNumberExponent(Number value);
+}