diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDecimalData.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDecimalData.java index 7eef6db84..0f7a48b5f 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDecimalData.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRDecimalData.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 Alexey Andreev. + * Copyright 2015 Alexey Andreev. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,62 +15,59 @@ */ package org.teavm.classlib.impl.unicode; -import org.teavm.platform.metadata.Resource; - /** * * @author Alexey Andreev */ -public interface CLDRDecimalData extends Resource { - int getMaximumFractionDigits(); +public class CLDRDecimalData { + int groupingSeparator; + int decimalSeparator; + int listSeparator; + int perMille; + int percent; + String NaN; + String infinity; + int minusSign; + int monetaryDecimalSeparator; + String exponentSeparator; - void setMaximumFractionDigits(int value); + public int getGroupingSeparator() { + return groupingSeparator; + } - int getMaximumIntegerDigits(); + public int getDecimalSeparator() { + return decimalSeparator; + } - void setMaximumIntegerDigits(int value); + public int getListSeparator() { + return listSeparator; + } - int getMinimumFractionDigits(); + public int getPerMille() { + return perMille; + } - void setMinimumFractionDigits(int value); + public int getPercent() { + return percent; + } - int getMinimumIntegerDigits(); + public String getNaN() { + return NaN; + } - void setMinimumIntegerDigits(int value); + public String getInfinity() { + return infinity; + } - int getGroupingSeparator(); + public int getMinusSign() { + return minusSign; + } - void setGroupingSeparator(int value); + public int getMonetaryDecimalSeparator() { + return monetaryDecimalSeparator; + } - int getDecimalSeparator(); - - void setDecimalSeparator(int value); - - int getPerMill(); - - void setPerMill(int value); - - int getPercent(); - - void setPercent(int value); - - String getNaN(); - - void setNaN(String nan); - - String getInfinity(); - - void setInfinity(String infinity); - - int getMinusSign(); - - void setMinusSign(int value); - - int getMonetaryDecimalSeparator(); - - void setMonetaryDecimalSeparator(int value); - - String getExponentSeparator(); - - void setExponentSeparator(String value); + public String getExponentSeparator() { + return exponentSeparator; + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java index 3f8d7b5e2..8975def9d 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRHelper.java @@ -200,14 +200,15 @@ public class CLDRHelper { return res.getValue(); } - public static CLDRDecimalData resolveDecimalData(String language, String country) { - ResourceMap map = getDecimalDataMap(); + public static DecimalData resolveDecimalData(String language, String country) { + ResourceMap map = getDecimalDataMap(); String localeCode = getCode(language, country); return map.has(localeCode) ? map.get(localeCode) : map.has(language) ? map.get(language) : map.get("root"); } - private static native ResourceMap getDecimalDataMap(); + @MetadataProvider(DecimalMetadataGenerator.class) + private static native ResourceMap getDecimalDataMap(); public static CurrencyLocalization resolveCurrency(String language, String country, String currency) { String localeCode = getCode(language, country); diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java index 6d8a60981..44422dc46 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRLocale.java @@ -38,6 +38,7 @@ public class CLDRLocale { CLDRDateFormats timeFormats; CLDRDateFormats dateTimeFormats; CLDRTimeZone[] timeZones; + CLDRDecimalData decimalData = new CLDRDecimalData(); public Map getLanguages() { return Collections.unmodifiableMap(languages); @@ -90,4 +91,8 @@ public class CLDRLocale { public CLDRTimeZone[] getTimeZones() { return timeZones.clone(); } + + public CLDRDecimalData getDecimalData() { + return decimalData; + } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java index cce19a42d..c1ea2bccd 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/CLDRReader.java @@ -127,9 +127,12 @@ public class CLDRReader { readDateTimeFormats(localeName, localeInfo, root); break; } - case "currencies.json": { + case "currencies.json": readCurrencies(localeName, localeInfo, input); - } + break; + case "numbers.json": + readNumbers(localeName, localeInfo, input); + break; } } } catch (IOException e) { @@ -207,6 +210,23 @@ public class CLDRReader { } } + private void readNumbers(String localeCode, CLDRLocale locale, InputStream input) { + JsonObject root = (JsonObject)new JsonParser().parse(new InputStreamReader(input)); + JsonObject numbersJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() + .get("numbers").getAsJsonObject(); + String numbering = numbersJson.get("defaultNumberingSystem").getAsString(); + JsonObject symbolsJson = numbersJson.get("symbols-numberSystem-" + numbering).getAsJsonObject(); + locale.decimalData.decimalSeparator = symbolsJson.get("decimal").getAsString().charAt(0); + locale.decimalData.groupingSeparator = symbolsJson.get("group").getAsString().charAt(0); + locale.decimalData.listSeparator = symbolsJson.get("list").getAsString().charAt(0); + locale.decimalData.percent = symbolsJson.get("percentSign").getAsString().charAt(0); + locale.decimalData.minusSign = symbolsJson.get("minusSign").getAsString().charAt(0); + locale.decimalData.exponentSeparator = symbolsJson.get("exponential").getAsString(); + locale.decimalData.perMille = symbolsJson.get("perMille").getAsString().charAt(0); + locale.decimalData.infinity = symbolsJson.get("infinity").getAsString(); + locale.decimalData.NaN = symbolsJson.get("nan").getAsString(); + } + private void readEras(String localeCode, CLDRLocale locale, JsonObject root) { JsonObject erasJson = root.get("main").getAsJsonObject().get(localeCode).getAsJsonObject() .get("dates").getAsJsonObject().get("calendars").getAsJsonObject() diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DecimalData.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DecimalData.java new file mode 100644 index 000000000..bd0b43c8e --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DecimalData.java @@ -0,0 +1,60 @@ +/* + * Copyright 2014 Alexey Andreev. + * + * 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. + */ +package org.teavm.classlib.impl.unicode; + +import org.teavm.platform.metadata.Resource; + +/** + * + * @author Alexey Andreev + */ +public interface DecimalData extends Resource { + int getGroupingSeparator(); + + void setGroupingSeparator(int value); + + int getDecimalSeparator(); + + void setDecimalSeparator(int value); + + int getListSeparator(); + + void setListSeparator(int value); + + int getPerMille(); + + void setPerMille(int value); + + int getPercent(); + + void setPercent(int value); + + String getNaN(); + + void setNaN(String nan); + + String getInfinity(); + + void setInfinity(String infinity); + + int getMinusSign(); + + void setMinusSign(int value); + + String getExponentSeparator(); + + void setExponentSeparator(String value); +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DecimalMetadataGenerator.java b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DecimalMetadataGenerator.java new file mode 100644 index 000000000..0923af671 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/impl/unicode/DecimalMetadataGenerator.java @@ -0,0 +1,50 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * 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. + */ +package org.teavm.classlib.impl.unicode; + +import java.util.Map; +import org.teavm.model.MethodReference; +import org.teavm.platform.metadata.MetadataGenerator; +import org.teavm.platform.metadata.MetadataGeneratorContext; +import org.teavm.platform.metadata.Resource; +import org.teavm.platform.metadata.ResourceMap; + +/** + * + * @author Alexey Andreev + */ +public class DecimalMetadataGenerator implements MetadataGenerator { + @Override + public Resource generateMetadata(MetadataGeneratorContext context, MethodReference method) { + CLDRReader reader = context.getService(CLDRReader.class); + ResourceMap map = context.createResourceMap(); + for (Map.Entry entry : reader.getKnownLocales().entrySet()) { + CLDRDecimalData data = entry.getValue().getDecimalData(); + DecimalData dataRes = context.createResource(DecimalData.class); + dataRes.setDecimalSeparator(data.getDecimalSeparator()); + dataRes.setExponentSeparator(data.getExponentSeparator()); + dataRes.setGroupingSeparator(data.getGroupingSeparator()); + dataRes.setInfinity(data.getInfinity()); + dataRes.setListSeparator(data.getListSeparator()); + dataRes.setMinusSign(data.getMinusSign()); + dataRes.setNaN(data.getNaN()); + dataRes.setPercent(data.getPercent()); + dataRes.setPerMille(data.getPerMille()); + map.put(entry.getKey(), dataRes); + } + return map; + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java index 7e1205acd..1062bdd76 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/lang/TDouble.java @@ -169,7 +169,7 @@ public class TDouble extends TNumber implements TComparable { return mantissa * decimalExponent(exp); } - private static double decimalExponent(int n) { + public static double decimalExponent(int n) { double d; if (n < 0) { d = 0.1; diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java index 50d16ad12..9db41c46d 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormat.java @@ -15,9 +15,13 @@ */ package org.teavm.classlib.java.text; +import java.math.BigDecimal; +import java.math.BigInteger; import java.text.DecimalFormatSymbols; -import org.teavm.classlib.impl.unicode.CLDRDecimalData; import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.lang.TArithmeticException; +import org.teavm.classlib.java.lang.TDouble; +import org.teavm.classlib.java.lang.TString; import org.teavm.classlib.java.util.TLocale; /** @@ -25,7 +29,26 @@ import org.teavm.classlib.java.util.TLocale; * @author Alexey Andreev */ public class TDecimalFormat extends TNumberFormat { - private TDecimalFormatSymbols symbols; + private static final long[] POW10_ARRAY = { 1, 10, 100, 1000, 1_0000, 1_0_0000, 1_00_0000, + 1_000_0000, 1_0000_0000, 1_0_0000_0000, 1_00_0000_0000L, 1_000_0000_0000L, 1_0000_0000_0000L, + 1_0_0000_0000_0000L, 1_00_0000_0000_0000L, 1_000_0000_0000_0000L, 1_0000_0000_0000_0000L, + 1_0_0000_0000_0000_0000L, 1_00_0000_0000_0000_0000L }; + private static final int[] POW10_INT_ARRAY = { 1, 10, 100, 1000, 1_0000, 1_0_0000, 1_00_0000, + 1_000_0000, 1_0000_0000, 1_0_0000_0000 }; + private static final double[] POW10_FRAC_ARRAY = { 1E1, 1E2, 1E4, 1E8, 1E16, 1E32, 1E64, 1E128, 1E256 }; + private static final double[] POWM10_FRAC_ARRAY = { 1E-1, 1E-2, 1E-4, 1E-8, 1E-16, 1E-32, 1E-64, 1E-128, 1E-256 }; + private static final int DOUBLE_MAX_EXPONENT = 308; + private static long MAX_LONG_DIV_10 = Long.MAX_VALUE / 10; + TDecimalFormatSymbols symbols; + FormatField[] positivePrefix = {}; + FormatField[] negativePrefix = { new TextField("-") }; + FormatField[] positiveSuffix = {}; + FormatField[] negativeSuffix = {}; + private int multiplier = 1; + private int groupingSize; + private boolean decimalSeparatorAlwaysShown; + private boolean parseBigDecimal; + int exponentDigits; public TDecimalFormat() { this(CLDRHelper.resolveDecimalFormat(TLocale.getDefault().getLanguage(), TLocale.getDefault().getCountry())); @@ -37,36 +60,1195 @@ public class TDecimalFormat extends TNumberFormat { public TDecimalFormat(String pattern, TDecimalFormatSymbols value) { symbols = (TDecimalFormatSymbols)value.clone(); - TLocale locale = symbols.getLocale(); applyPattern(pattern); - - CLDRDecimalData decimalData = CLDRHelper.resolveDecimalData(locale.getLanguage(), locale.getCountry()); - super.setMaximumFractionDigits(decimalData.getMaximumFractionDigits()); - super.setMaximumIntegerDigits(decimalData.getMaximumIntegerDigits()); - super.setMinimumFractionDigits(decimalData.getMinimumFractionDigits()); - super.setMinimumIntegerDigits(decimalData.getMinimumIntegerDigits()); } - public void applyPattern(@SuppressWarnings("unused") String pattern) { - + public void applyPattern(String pattern) { + TDecimalFormatParser parser = new TDecimalFormatParser(); + parser.parse(pattern); + parser.apply(this); } public DecimalFormatSymbols getDecimalFormatSymbols() { return (DecimalFormatSymbols)symbols.clone(); } + private StringBuffer fieldsToText(FormatField[] fields, StringBuffer buffer) { + for (FormatField field : fields) { + field.render(this, buffer); + } + return buffer; + } + + private String fieldsToText(FormatField[] fields) { + if (fields == null) { + return null; + } + return fieldsToText(fields, new StringBuffer()).toString(); + } + + private FormatField[] textToFields(String text) { + return new FormatField[] { new TextField(text) }; + } + + public String getPositivePrefix() { + return fieldsToText(positivePrefix); + } + + public void setPositivePrefix(String newValue) { + positivePrefix = textToFields(newValue); + } + + public String getNegativePrefix() { + return fieldsToText(negativePrefix); + } + + public void setNegativePrefix(String newValue) { + negativePrefix = textToFields(newValue); + } + + public String getPositiveSuffix() { + return fieldsToText(positiveSuffix); + } + + public void setPositiveSuffix(String newValue) { + positiveSuffix = textToFields(newValue); + } + + public String getNegativeSuffix() { + return fieldsToText(negativeSuffix); + } + + public void setNegativeSuffix(String newValue) { + negativeSuffix = textToFields(newValue); + } + + public int getMultiplier() { + return multiplier; + } + + public void setMultiplier(int newValue) { + multiplier = newValue; + } + + public int getGroupingSize() { + return groupingSize; + } + + public void setGroupingSize(int newValue) { + groupingSize = newValue; + } + + public boolean isDecimalSeparatorAlwaysShown() { + return decimalSeparatorAlwaysShown; + } + + public void setDecimalSeparatorAlwaysShown(boolean newValue) { + decimalSeparatorAlwaysShown = newValue; + } + + public boolean isParseBigDecimal() { + return parseBigDecimal; + } + + public void setParseBigDecimal(boolean newValue) { + parseBigDecimal = newValue; + } + @Override - public StringBuffer format(long value, StringBuffer buffer, TFieldPosition field) { - return null; + public Object clone() { + return super.clone(); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (!(obj instanceof TDecimalFormat)) { + return false; + } + TDecimalFormat other = (TDecimalFormat)obj; + if (!super.equals(obj)) { + return false; + } + return positivePrefix.equals(other.positivePrefix) && + positiveSuffix.equals(other.positiveSuffix) && + negativePrefix.equals(other.negativePrefix) && + negativeSuffix.equals(other.negativeSuffix) && + multiplier == other.multiplier && + groupingSize == other.groupingSize && + decimalSeparatorAlwaysShown == other.decimalSeparatorAlwaysShown && + parseBigDecimal == other.parseBigDecimal && + exponentDigits == other.exponentDigits; + } + + @Override + public int hashCode() { + int result = super.hashCode(); + result = result * 31 + positivePrefix.hashCode(); + result = result * 31 + positiveSuffix.hashCode(); + result = result * 31 + negativePrefix.hashCode(); + result = result * 31 + negativeSuffix.hashCode(); + result = result * 31 + multiplier; + result = result * 31 + groupingSize; + result = result * 31 + (decimalSeparatorAlwaysShown ? 1 : 0); + result = result * 31 + (parseBigDecimal ? 1 : 0); + result = result * 31 + exponentDigits; + return result; } @Override public Number parse(String string, TParsePosition position) { - return null; + return isParseBigDecimal() ? parseBigDecimal(string, position) : parseNumber(string, position); + } + + private BigDecimal parseBigDecimal(String string, TParsePosition position) { + BigInteger mantissa = BigInteger.ZERO; + int exponent = 0; + int index = position.getIndex(); + boolean allowGroupSeparator = false; + String exponentSeparator = symbols.getExponentSeparator(); + int intSize = 0; + int fracSize = 0; + boolean fractionalPart = false; + boolean positive = true; + + // Find prefix + String negPrefix = getNegativePrefix(); + String posPrefix = getPositivePrefix(); + if (string.regionMatches(index, negPrefix, 0, negPrefix.length())) { + positive = false; + index += negPrefix.length(); + } else if (string.regionMatches(index, posPrefix, 0, posPrefix.length())) { + index += posPrefix.length(); + } else { + position.setErrorIndex(index); + return null; + } + + // Find suffix + String suffix = positive ? getPositiveSuffix() : getNegativeSuffix(); + if (suffix == null) { + suffix = getPositiveSuffix(); + } + + // Parse mantissa and exponent + while (index < string.length()) { + char c = string.charAt(index); + int digit = c - symbols.getZeroDigit(); + if (digit >= 0 && digit <= 9) { + if (!fractionalPart) { + ++intSize; + allowGroupSeparator = groupingSize > 1; + } else { + ++fracSize; + } + mantissa = mantissa.multiply(BigInteger.TEN).add(BigInteger.valueOf(digit)); + ++index; + } else if (c == symbols.getDecimalSeparator()) { + if (fractionalPart) { + break; + } + if (intSize < 1) { + position.setErrorIndex(index); + return null; + } + fractionalPart = true; + allowGroupSeparator = false; + ++index; + } else if (c == symbols.getGroupingSeparator()) { + if (!allowGroupSeparator) { + break; + } + allowGroupSeparator = false; + ++index; + } else if (string.regionMatches(index, exponentSeparator, 0, exponentSeparator.length())) { + if (exponentDigits == 0) { + break; + } + index += exponentSeparator.length(); + if (index == string.length()) { + position.setErrorIndex(index); + return null; + } + boolean positiveExponent = true; + if (string.charAt(index) == symbols.getMinusSign()) { + positiveExponent = false; + ++index; + } + int exponentLength = 0; + while (index < string.length()) { + digit = string.charAt(index) - symbols.getZeroDigit(); + if (digit < 0 || digit > 9) { + break; + } + exponent = exponent * 10 + digit; + ++exponentLength; + ++index; + } + if (exponentLength == 0) { + position.setErrorIndex(index); + return null; + } + if (!positiveExponent) { + exponent = -exponent; + } + break; + } else { + break; + } + } + + // If decimal separator without fractional part is not allowed, report error + if (fracSize == 0 && fractionalPart && !isDecimalSeparatorAlwaysShown()) { + position.setErrorIndex(index); + return null; + } + + // Check suffix + if (suffix != null && !string.regionMatches(index, suffix, 0, suffix.length())) { + position.setErrorIndex(index); + return null; + } + + // Advance parse position + position.setIndex(index); + + // Apply exponent + exponent -= fracSize; + + // Expose result + BigDecimal result = new BigDecimal(mantissa, -exponent); + if (multiplier != 1) { + result = result.divide(BigDecimal.valueOf(multiplier)); + } + if (!positive) { + result = result.negate(); + } + return result; + } + + private Number parseNumber(String string, TParsePosition position) { + long mantissa = 0; + int shift = 0; + int exponent = 0; + int index = position.getIndex(); + boolean allowGroupSeparator = false; + String exponentSeparator = symbols.getExponentSeparator(); + int intSize = 0; + int fracSize = 0; + boolean fractionalPart = false; + boolean positive = true; + + // Find prefix + String negPrefix = getNegativePrefix(); + String posPrefix = getPositivePrefix(); + if (string.regionMatches(index, negPrefix, 0, negPrefix.length())) { + positive = false; + index += negPrefix.length(); + } else if (string.regionMatches(index, posPrefix, 0, posPrefix.length())) { + index += posPrefix.length(); + } else { + position.setErrorIndex(index); + return null; + } + + // Find suffix + String suffix = positive ? getPositiveSuffix() : getNegativeSuffix(); + if (suffix == null) { + suffix = getPositiveSuffix(); + } + + // Check for infinity + if (string.regionMatches(index, symbols.getInfinity(), 0, symbols.getInfinity().length())) { + index += symbols.getInfinity().length(); + if (suffix != null && !string.regionMatches(index, suffix, 0, suffix.length())) { + position.setErrorIndex(index); + return null; + } + position.setIndex(index); + return positive ? Double.POSITIVE_INFINITY : Double.NEGATIVE_INFINITY; + } + + // Parse mantissa and exponent + while (index < string.length()) { + char c = string.charAt(index); + int digit = c - symbols.getZeroDigit(); + if (digit >= 0 && digit <= 9) { + if (!fractionalPart) { + ++intSize; + allowGroupSeparator = groupingSize > 1; + } else { + ++fracSize; + } + if (mantissa > MAX_LONG_DIV_10) { + if (shift == 0 && digit > 5) { + ++mantissa; + } + ++shift; + } else { + long next = mantissa * 10; + if (next > Long.MAX_VALUE - digit) { + ++shift; + } else { + mantissa = next + digit; + } + } + ++index; + } else if (c == symbols.getDecimalSeparator()) { + if (fractionalPart) { + break; + } + if (intSize < 1) { + position.setErrorIndex(index); + return null; + } + fractionalPart = true; + allowGroupSeparator = false; + ++index; + } else if (c == symbols.getGroupingSeparator()) { + if (!allowGroupSeparator) { + break; + } + allowGroupSeparator = false; + ++index; + } else if (string.regionMatches(index, exponentSeparator, 0, exponentSeparator.length())) { + if (exponentDigits == 0) { + break; + } + index += exponentSeparator.length(); + if (index == string.length()) { + position.setErrorIndex(index); + return null; + } + boolean positiveExponent = true; + if (string.charAt(index) == symbols.getMinusSign()) { + positiveExponent = false; + ++index; + } + int exponentLength = 0; + while (index < string.length()) { + digit = string.charAt(index) - symbols.getZeroDigit(); + if (digit < 0 || digit > 9) { + break; + } + exponent = exponent * 10 + digit; + ++exponentLength; + ++index; + } + if (exponentLength == 0) { + position.setErrorIndex(index); + return null; + } + if (!positiveExponent) { + exponent = -exponent; + } + break; + } else { + break; + } + } + + // If decimal separator without fractional part is not allowed, report error + if (fracSize == 0 && fractionalPart && !isDecimalSeparatorAlwaysShown()) { + position.setErrorIndex(index); + return null; + } + + // Check suffix + if (suffix != null && !string.regionMatches(index, suffix, 0, suffix.length())) { + position.setErrorIndex(index); + return null; + } + + // Advance parse position + position.setIndex(index); + + // Apply multiplier + if (multiplier != 1) { + int multiplierDigits = fastLn10(multiplier); + if (POW10_INT_ARRAY[multiplierDigits] != multiplier) { + mantissa /= multiplier; + } else { + exponent -= multiplierDigits; + } + } + + // Apply exponent + exponent += shift - fracSize; + if (exponent > 0 && exponent < POW10_ARRAY.length) { + if (mantissa < Long.MAX_VALUE / POW10_ARRAY[exponent]) { + mantissa *= POW10_ARRAY[exponent]; + exponent = 0; + } + } else if (exponent < 0 && -exponent < POW10_ARRAY.length) { + if (mantissa % POW10_ARRAY[-exponent] == 0) { + mantissa /= POW10_ARRAY[-exponent]; + exponent = 0; + } + } + + // Expose result + if (mantissa == 0 && !positive) { + return -0.0; + } + if (exponent == 0) { + return positive ? mantissa : -mantissa; + } + double result = TDouble.decimalExponent(exponent) * mantissa; + return positive ? result : -result; + } + + @Override + public StringBuffer format(Object object, StringBuffer buffer, TFieldPosition field) { + if (object instanceof BigDecimal) { + return format((BigDecimal)object, buffer, field); + } else if (object instanceof BigInteger) { + return format((BigInteger)object, buffer, field); + } else { + return super.format(object, buffer, field); + } + } + + private StringBuffer format(BigInteger value, StringBuffer buffer, TFieldPosition field) { + return format(new BigDecimal(value), buffer, field); + } + + private StringBuffer format(BigDecimal value, StringBuffer buffer, + @SuppressWarnings("unused") TFieldPosition field) { + if (exponentDigits > 0) { + formatExponent(value, buffer); + } else { + formatRegular(value, buffer); + } + return buffer; + } + + @Override + public StringBuffer format(long value, StringBuffer buffer, TFieldPosition field) { + if (exponentDigits > 0) { + formatExponent(value, buffer); + } else { + formatRegular(value, buffer); + } + return buffer; } @Override public StringBuffer format(double value, StringBuffer buffer, TFieldPosition field) { - return null; + if (Double.isNaN(value)) { + fieldsToText(positivePrefix, buffer).append(symbols.getNaN()); + appendSuffix(true, buffer); + } else if (Double.isInfinite(value)) { + fieldsToText(value > 0 ? positivePrefix : negativePrefix, buffer).append(symbols.getInfinity()); + appendSuffix(value > 0, buffer); + } else { + MantissaAndExponent me = getMantissaAndExponent(value); + if (exponentDigits > 0) { + formatExponent(me.mantissa, me.exponent, buffer); + } else { + formatRegular(me.mantissa, me.exponent, buffer); + } + } + return buffer; + } + + private void formatExponent(long value, StringBuffer buffer) { + int exponent = fastLn10(Math.abs(value)); + formatExponent(value, exponent, buffer); + } + + private void formatRegular(long value, StringBuffer buffer) { + int exponent = fastLn10(Math.abs(value)); + formatRegular(value, exponent, buffer); + } + + private void formatExponent(long mantissa, int exponent, StringBuffer buffer) { + boolean positive = mantissa >= 0; + int visibleExponent = fastLn10(mantissa); + int mantissaLength = visibleExponent + 1; + + if (multiplier != 1) { + int multiplierDigits = fastLn10(multiplier); + int tenMultiplier = POW10_INT_ARRAY[multiplierDigits]; + if (tenMultiplier == multiplier) { + exponent += multiplierDigits; + } else if (mantissa >= Long.MAX_VALUE / multiplier || mantissa <= Long.MIN_VALUE / multiplier) { + formatExponent(new BigDecimal(BigInteger.valueOf(mantissa), visibleExponent - exponent), buffer); + return; + } else { + mantissa *= multiplier; + positive = mantissa >= 0; + visibleExponent = fastLn10(mantissa); + mantissaLength = visibleExponent + 1; + } + } + + int significantSize = getMinimumIntegerDigits() + getMaximumFractionDigits(); + int exponentMultiplier = getMaximumIntegerDigits() - getMinimumIntegerDigits() + 1; + if (exponentMultiplier > 1) { + int delta = exponent - (exponent / exponentMultiplier) * exponentMultiplier; + exponent -= delta; + visibleExponent -= delta; + } else { + exponent -= getMinimumIntegerDigits() - 1; + visibleExponent -= getMinimumIntegerDigits() - 1; + } + + if (significantSize < 0) { + mantissa = 0; + } else if (significantSize < mantissaLength) { + mantissa = applyRounding(mantissa, mantissaLength, significantSize); + } + + // Append pattern prefix + fieldsToText(positive ? positivePrefix : negativePrefix, buffer); + + int exponentPos = Math.max(visibleExponent, 0); + for (int i = mantissaLength - 1; i >= exponentPos; --i) { + long mantissaDigitMask = POW10_ARRAY[i]; + buffer.append(forDigit(Math.abs((int)(mantissa / mantissaDigitMask)))); + mantissa %= mantissaDigitMask; + } + for (int i = exponentPos - 1; i >= visibleExponent; --i) { + buffer.append('0'); + } + + significantSize -= mantissaLength - visibleExponent; + int requiredSize = significantSize - (getMaximumFractionDigits() - getMinimumFractionDigits()); + if (requiredSize > 0 || (mantissa != 0 && significantSize > 0)) { + buffer.append(symbols.getDecimalSeparator()); + + int limit = Math.max(0, visibleExponent - significantSize); + int count = 0; + for (int i = visibleExponent - 1; i >= limit; --i) { + long mantissaDigitMask = POW10_ARRAY[i]; + buffer.append(forDigit(Math.abs((int)(mantissa / mantissaDigitMask)))); + mantissa %= mantissaDigitMask; + ++count; + if (mantissa == 0) { + break; + } + } + while (count++ < requiredSize) { + buffer.append('0'); + } + } + + buffer.append(symbols.getExponentSeparator()); + if (exponent < 0) { + exponent = -exponent; + buffer.append(symbols.getMinusSign()); + } + int exponentLength = Math.max(exponentDigits, fastLn10(exponent) + 1); + for (int i = exponentLength - 1; i >= 0; --i) { + int exponentDigit = POW10_INT_ARRAY[i]; + buffer.append(forDigit(exponent / exponentDigit)); + exponent %= exponentDigit; + } + + // Add suffix + appendSuffix(positive, buffer); + } + + private void formatRegular(long mantissa, int exponent, StringBuffer buffer) { + boolean positive = mantissa >= 0; + int mantissaLength = fastLn10(mantissa) + 1; + ++exponent; + + if (multiplier != 1) { + int multiplierDigits = fastLn10(multiplier); + int tenMultiplier = POW10_INT_ARRAY[multiplierDigits]; + if (tenMultiplier == multiplier) { + exponent += multiplierDigits; + } else if (mantissa >= Long.MAX_VALUE / multiplier || mantissa <= Long.MIN_VALUE / multiplier) { + formatRegular(new BigDecimal(BigInteger.valueOf(mantissa), mantissaLength - exponent), buffer); + return; + } else { + mantissa *= multiplier; + mantissaLength = fastLn10(mantissa) + 1; + } + } + + // Apply rounding if necessary + int roundingPos = exponent + getMaximumFractionDigits(); + if (roundingPos < 0) { + mantissa = 0; + } else if (roundingPos < mantissaLength) { + mantissa = applyRounding(mantissa, mantissaLength, roundingPos); + } + + // Append pattern prefix + fieldsToText(positive ? positivePrefix : negativePrefix, buffer); + + // Add insignificant integer zeros + int intLength = Math.max(0, exponent); + int digitPos = Math.max(intLength, getMinimumIntegerDigits()) - 1; + for (int i = getMinimumIntegerDigits() - 1; i >= intLength; --i) { + buffer.append('0'); + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + --digitPos; + } + + // Add significant integer digits + int significantIntDigits = Math.min(mantissaLength, intLength); + int mantissaDigit = mantissaLength - 1; + for (int i = 0; i < significantIntDigits; ++i) { + long mantissaDigitMask = POW10_ARRAY[mantissaDigit--]; + buffer.append(forDigit(Math.abs((int)(mantissa / mantissaDigitMask)))); + mantissa %= mantissaDigitMask; + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + --digitPos; + } + + // Add significant integer zeroes + intLength -= significantIntDigits; + for (int i = 0; i < intLength; ++i) { + buffer.append('0'); + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + --digitPos; + } + + if (mantissa == 0) { + if (getMinimumFractionDigits() == 0) { + if (isDecimalSeparatorAlwaysShown()) { + buffer.append(symbols.getDecimalSeparator()); + } + } else { + buffer.append(symbols.getDecimalSeparator()); + for (int i = 0; i < getMinimumFractionDigits(); ++i) { + buffer.append('0'); + } + } + } else { + buffer.append(symbols.getDecimalSeparator()); + + // Add significant fractional zeros + int fracZeros = Math.min(getMaximumFractionDigits(), Math.max(0, -exponent)); + digitPos = 0; + for (int i = 0; i < fracZeros; ++i) { + ++digitPos; + buffer.append('0'); + } + + // Add significant fractional digits + int significantFracDigits = Math.min(getMaximumFractionDigits() - digitPos, mantissaDigit); + for (int i = 0; i < significantFracDigits; ++i) { + if (mantissa == 0) { + break; + } + ++digitPos; + long mantissaDigitMask = POW10_ARRAY[mantissaDigit]; + buffer.append(forDigit(Math.abs((int)(mantissa / mantissaDigitMask)))); + mantissa %= mantissaDigitMask; + mantissaDigit--; + } + + // Add insignificant fractional zeros + for (int i = digitPos; i < getMinimumFractionDigits(); ++i) { + ++digitPos; + buffer.append('0'); + } + } + + // Add suffix + appendSuffix(positive, buffer); + } + + private void formatExponent(BigDecimal value, StringBuffer buffer) { + if (multiplier != 1) { + value = value.multiply(BigDecimal.valueOf(multiplier)); + } + boolean positive = value.compareTo(BigDecimal.ZERO) >= 0; + int mantissaLength = value.precision(); + int visibleExponent = mantissaLength - 1; + int exponent = visibleExponent - value.scale(); + BigInteger mantissa = value.unscaledValue(); + + int significantSize = getMinimumIntegerDigits() + getMaximumFractionDigits(); + int exponentMultiplier = getMaximumIntegerDigits() - getMinimumIntegerDigits() + 1; + if (exponentMultiplier > 1) { + int delta = exponent - (exponent / exponentMultiplier) * exponentMultiplier; + exponent -= delta; + visibleExponent -= delta; + } else { + exponent -= getMinimumIntegerDigits() - 1; + visibleExponent -= getMinimumIntegerDigits() - 1; + } + + if (significantSize < 0) { + mantissa = BigInteger.ZERO; + } else if (significantSize < mantissaLength) { + mantissa = applyRounding(mantissa, mantissaLength, significantSize); + } + + // Append pattern prefix + fieldsToText(positive ? positivePrefix : negativePrefix, buffer); + + int exponentPos = Math.max(visibleExponent, 0); + BigInteger mantissaDigitMask = pow10(BigInteger.ONE, mantissaLength - 1); + for (int i = mantissaLength - 1; i >= exponentPos; --i) { + BigInteger[] parts = mantissa.divideAndRemainder(mantissaDigitMask); + buffer.append(forDigit(Math.abs(parts[0].intValue()))); + mantissa = parts[1]; + mantissaDigitMask = mantissaDigitMask.divide(BigInteger.TEN); + } + for (int i = exponentPos - 1; i >= visibleExponent; --i) { + buffer.append('0'); + } + + significantSize -= mantissaLength - visibleExponent; + int requiredSize = significantSize - (getMaximumFractionDigits() - getMinimumFractionDigits()); + if (requiredSize > 0 || (!mantissa.equals(BigInteger.ZERO) && significantSize > 0)) { + buffer.append(symbols.getDecimalSeparator()); + + int limit = Math.max(0, visibleExponent - significantSize); + int count = 0; + for (int i = visibleExponent - 1; i >= limit; --i) { + BigInteger[] parts = mantissa.divideAndRemainder(mantissaDigitMask); + buffer.append(forDigit(Math.abs(parts[0].intValue()))); + mantissa = parts[1]; + ++count; + if (mantissa.equals(BigInteger.ZERO)) { + break; + } + mantissaDigitMask = mantissaDigitMask.divide(BigInteger.TEN); + } + while (count++ < requiredSize) { + buffer.append('0'); + } + } + + buffer.append(symbols.getExponentSeparator()); + if (exponent < 0) { + exponent = -exponent; + buffer.append(symbols.getMinusSign()); + } + int exponentLength = Math.max(exponentDigits, fastLn10(exponent) + 1); + for (int i = exponentLength - 1; i >= 0; --i) { + int exponentDigit = POW10_INT_ARRAY[i]; + buffer.append(forDigit(exponent / exponentDigit)); + exponent %= exponentDigit; + } + + // Add suffix + appendSuffix(positive, buffer); + } + + private void appendSuffix(boolean positive, StringBuffer buffer) { + if (positive) { + if (positiveSuffix != null) { + fieldsToText(positiveSuffix, buffer); + } + } else { + fieldsToText(negativeSuffix != null ? negativeSuffix : + positiveSuffix != null ? positiveSuffix : new FormatField[0], buffer); + } + } + + private void formatRegular(BigDecimal value, StringBuffer buffer) { + if (multiplier != 1) { + value = value.multiply(BigDecimal.valueOf(multiplier)); + } + BigInteger mantissa = value.unscaledValue(); + boolean positive = mantissa.compareTo(BigInteger.ZERO) >= 0; + int mantissaLength = value.precision(); + int exponent = value.precision() - value.scale(); + + // Apply rounding if necessary + int roundingPos = exponent + getMaximumFractionDigits(); + if (roundingPos < 0) { + mantissa = BigInteger.ZERO; + } else if (roundingPos < mantissaLength) { + mantissa = applyRounding(mantissa, mantissaLength, roundingPos); + } + + // Append pattern prefix + fieldsToText(positive ? positivePrefix : negativePrefix, buffer); + + // Add insignificant integer zeros + int intLength = Math.max(0, exponent); + int digitPos = Math.max(intLength, getMinimumIntegerDigits()) - 1; + for (int i = getMinimumIntegerDigits() - 1; i >= intLength; --i) { + buffer.append('0'); + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + --digitPos; + } + + // Add significant integer digits + int significantIntDigits = Math.min(mantissaLength, intLength); + BigInteger mantissaDigitMask = pow10(BigInteger.ONE, mantissaLength - 1); + for (int i = 0; i < significantIntDigits; ++i) { + BigInteger[] parts = mantissa.divideAndRemainder(mantissaDigitMask); + buffer.append(forDigit(Math.abs(parts[0].intValue()))); + mantissa = parts[1]; + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + --digitPos; + --mantissaLength; + mantissaDigitMask = mantissaDigitMask.divide(BigInteger.TEN); + } + + // Add significant integer zeros + intLength -= significantIntDigits; + for (int i = 0; i < intLength; ++i) { + buffer.append('0'); + if (groupingSize > 0 && digitPos % groupingSize == 0 && digitPos > 0) { + buffer.append(symbols.getGroupingSeparator()); + } + --digitPos; + } + + if (mantissa.equals(BigInteger.ZERO)) { + if (getMinimumFractionDigits() == 0) { + if (isDecimalSeparatorAlwaysShown()) { + buffer.append(symbols.getDecimalSeparator()); + } + } else { + buffer.append(symbols.getDecimalSeparator()); + for (int i = 0; i < getMinimumFractionDigits(); ++i) { + buffer.append('0'); + } + } + } else { + buffer.append(symbols.getDecimalSeparator()); + + // Add significant fractional zeros + int fracZeros = Math.min(getMaximumFractionDigits(), Math.max(0, -exponent)); + digitPos = 0; + for (int i = 0; i < fracZeros; ++i) { + ++digitPos; + buffer.append('0'); + } + + // Add significant fractional digits + int significantFracDigits = Math.min(getMaximumFractionDigits() - digitPos, mantissaLength); + for (int i = 0; i < significantFracDigits; ++i) { + if (mantissa.equals(BigInteger.ZERO)) { + break; + } + ++digitPos; + BigInteger[] parts = mantissa.divideAndRemainder(mantissaDigitMask); + buffer.append(forDigit(Math.abs(parts[0].intValue()))); + mantissa = parts[1]; + --mantissaLength; + mantissaDigitMask = mantissaDigitMask.divide(BigInteger.TEN); + } + + // Add insignificant fractional zeros + for (int i = digitPos; i < getMinimumFractionDigits(); ++i) { + ++digitPos; + buffer.append('0'); + } + } + + // Add suffix + appendSuffix(positive, buffer); + } + + private long applyRounding(long mantissa, int mantissaLength, int exponent) { + long rounding = POW10_ARRAY[mantissaLength - exponent]; + long signedRounding = mantissa > 0 ? rounding : -rounding; + switch (getRoundingMode()) { + case CEILING: + mantissa = (mantissa / rounding) * rounding; + if (mantissa >= 0) { + mantissa += rounding; + } + break; + case FLOOR: + mantissa = (mantissa / rounding) * rounding; + if (mantissa <= 0) { + mantissa -= rounding; + } + break; + case UP: + mantissa = (mantissa / rounding) * rounding + signedRounding; + break; + case DOWN: + mantissa = (mantissa / rounding) * rounding; + break; + case UNNECESSARY: + if (mantissa % rounding != 0) { + throw new TArithmeticException(TString.wrap("Can't avoid rounding")); + } + break; + case HALF_DOWN: + if (mantissa % rounding == signedRounding / 2) { + mantissa = (mantissa / rounding) * rounding; + } else { + mantissa = ((mantissa + signedRounding / 2) / rounding) * rounding; + } + break; + case HALF_UP: + if (mantissa % rounding == signedRounding / 2) { + mantissa = (mantissa / rounding) * rounding + signedRounding; + } else { + mantissa = ((mantissa + signedRounding / 2) / rounding) * rounding; + } + break; + case HALF_EVEN: { + if (mantissa % rounding == signedRounding / 2) { + mantissa = (mantissa / rounding) * rounding; + if ((mantissa / rounding) % 2 != 0) { + mantissa += signedRounding; + } + } else { + mantissa = ((mantissa + signedRounding / 2) / rounding) * rounding; + } + break; + } + } + return mantissa; + } + + private BigInteger applyRounding(BigInteger mantissa, int mantissaLength, int exponent) { + BigInteger rounding = pow10(BigInteger.ONE, mantissaLength - exponent); + BigInteger signedRounding = mantissa.compareTo(BigInteger.ZERO) >= 0 ? rounding : rounding.negate(); + switch (getRoundingMode()) { + case CEILING: + mantissa = mantissa.divide(rounding).multiply(rounding); + if (mantissa.compareTo(BigInteger.ZERO) >= 0) { + mantissa = mantissa.add(rounding); + } + break; + case FLOOR: + mantissa = mantissa.divide(rounding).multiply(rounding); + if (mantissa.compareTo(BigInteger.ZERO) <= 0) { + mantissa = mantissa.subtract(rounding); + } + break; + case UP: + mantissa = mantissa.divide(rounding).multiply(rounding).add(signedRounding); + break; + case DOWN: + mantissa = mantissa.divide(rounding).multiply(rounding); + break; + case UNNECESSARY: + if (mantissa.remainder(rounding).equals(BigInteger.ZERO)) { + throw new TArithmeticException(TString.wrap("Can't avoid rounding")); + } + break; + case HALF_DOWN: + if (mantissa.remainder(rounding).equals(signedRounding.divide(BigInteger.valueOf(2)))) { + mantissa = mantissa.divide(rounding).multiply(rounding); + } else { + mantissa = mantissa.add(signedRounding.divide(BigInteger.valueOf(2))) + .divide(rounding).multiply(rounding); + } + break; + case HALF_UP: + if (mantissa.remainder(rounding).equals(signedRounding.divide(BigInteger.valueOf(2)))) { + mantissa = mantissa.divide(rounding).multiply(rounding).add(signedRounding); + } else { + mantissa = mantissa.add(signedRounding.divide(BigInteger.valueOf(2))) + .divide(rounding).multiply(rounding); + } + break; + case HALF_EVEN: { + if (mantissa.remainder(rounding).equals(signedRounding.divide(BigInteger.valueOf(2)))) { + mantissa = mantissa.divide(rounding).multiply(rounding); + if (!mantissa.divide(rounding).remainder(BigInteger.valueOf(2)).equals(BigInteger.ZERO)) { + mantissa = mantissa.add(signedRounding); + } + } else { + mantissa = mantissa.add(signedRounding.divide(BigInteger.valueOf(2))) + .divide(rounding).multiply(rounding); + } + break; + } + } + return mantissa; + } + + private int fastLn10(long value) { + int result = 0; + if (value >= 0) { + if (value >= 1_0000_0000_0000_0000L) { + result += 16; + value /= 1_0000_0000_0000_0000L; + } + if (value >= 1_0000_0000L) { + result += 8; + value /= 1_0000_0000L; + } + if (value >= 1_0000L) { + result += 4; + value /= 1_0000L; + } + if (value >= 100L) { + result += 2; + value /= 100L; + } + if (value >= 10L) { + result += 1; + value /= 10L; + } + } else { + if (value <= -1_0000_0000_0000_0000L) { + result += 16; + value /= 1_0000_0000_0000_0000L; + } + if (value <= -1_0000_0000L) { + result += 8; + value /= 1_0000_0000L; + } + if (value <= -1_0000L) { + result += 4; + value /= 1_0000L; + } + if (value <= -100L) { + result += 2; + value /= 100L; + } + if (value <= -10L) { + result += 1; + value /= 10L; + } + } + return result; + } + + private int fastLn10(int value) { + int result = 0; + if (value >= 1_0000_0000) { + result += 8; + value /= 1_0000_0000; + } + if (value >= 1_0000) { + result += 4; + value /= 1_0000; + } + if (value >= 100) { + result += 2; + value /= 100; + } + if (value >= 10) { + result += 1; + value /= 10; + } + return result; + } + + private BigInteger pow10(BigInteger value, int power) { + BigInteger digit = BigInteger.TEN; + while (power != 0) { + if ((power & 1) != 0) { + value = value.multiply(digit); + } + digit = digit.multiply(digit); + power >>>= 1; + } + return value; + } + + private MantissaAndExponent getMantissaAndExponent(double value) { + long mantissaPattern = POW10_ARRAY[17]; + int exp = 0; + long mantissa = 0; + boolean positive; + if (value >= 0) { + positive = true; + } else { + positive = false; + value = -value; + } + if (value >= 1) { + int bit = 256; + exp = 0; + double digit = 1; + for (int i = POW10_FRAC_ARRAY.length - 1; i >= 0; --i) { + if ((exp | bit) <= DOUBLE_MAX_EXPONENT && POW10_FRAC_ARRAY[i] * digit <= value) { + digit *= POW10_FRAC_ARRAY[i]; + exp |= bit; + } + bit >>= 1; + } + mantissa = (long)(((value / digit) * mantissaPattern) + 0.5); + } else { + int bit = 256; + exp = 0; + double digit = 1; + for (int i = POWM10_FRAC_ARRAY.length - 1; i >= 0; --i) { + if ((exp | bit) <= DOUBLE_MAX_EXPONENT && POWM10_FRAC_ARRAY[i] * digit * 10 > value) { + digit *= POWM10_FRAC_ARRAY[i]; + exp |= bit; + } + bit >>= 1; + } + exp = -exp; + mantissa = (long)(((value * mantissaPattern) / digit) + 0.5); + } + mantissa = ((mantissa + 500) / 1000) * 1000; + return new MantissaAndExponent(positive ? mantissa : -mantissa, exp); + } + + private char forDigit(int n) { + return (char)(symbols.getZeroDigit() + n); + } + + static class MantissaAndExponent { + long mantissa; + int exponent; + + public MantissaAndExponent(long mantissa, int exponent) { + this.mantissa = mantissa; + this.exponent = exponent; + } + } + + interface FormatField { + void render(TDecimalFormat format, StringBuffer buffer); + } + + static class TextField implements FormatField { + private String text; + + public TextField(String text) { + this.text = text; + } + + @Override + public void render(TDecimalFormat format, StringBuffer buffer) { + buffer.append(text); + } + } + + static class CurrencyField implements FormatField { + @Override + public void render(TDecimalFormat format, StringBuffer buffer) { + buffer.append(format.getCurrency().getSymbol(format.symbols.getLocale())); + } + } + + static class PercentField implements FormatField { + @Override + public void render(TDecimalFormat format, StringBuffer buffer) { + buffer.append(format.symbols.getPercent()); + } + } + + static class PerMillField implements FormatField { + @Override + public void render(TDecimalFormat format, StringBuffer buffer) { + buffer.append(format.symbols.getPerMill()); + } + } + + static class MinusField implements FormatField { + @Override + public void render(TDecimalFormat format, StringBuffer buffer) { + buffer.append(format.symbols.getMinusSign()); + } } } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatParser.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatParser.java new file mode 100644 index 000000000..49858f860 --- /dev/null +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatParser.java @@ -0,0 +1,304 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * 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. + */ +package org.teavm.classlib.java.text; + +import java.util.ArrayList; +import java.util.List; +import org.teavm.classlib.java.text.TDecimalFormat.FormatField; + +/** + * + * @author Alexey Andreev + */ +class TDecimalFormatParser { + private FormatField[] positivePrefix; + private FormatField[] positiveSuffix; + private FormatField[] negativePrefix; + private FormatField[] negativeSuffix; + private int groupSize; + private int minimumIntLength; + private int intLength; + private int minimumFracLength; + private int fracLength; + private int exponentLength; + private boolean decimalSeparatorRequired; + private String string; + private int index; + private int multiplier; + + public void parse(String string) { + groupSize = 0; + minimumFracLength = 0; + fracLength = 0; + exponentLength = 0; + decimalSeparatorRequired = false; + multiplier = 1; + this.string = string; + index = 0; + positivePrefix = parseText(false, false); + if (index == string.length()) { + throw new IllegalArgumentException("Positive number pattern not found in " + string); + } + parseNumber(true); + negativePrefix = null; + negativeSuffix = null; + if (index < string.length() && string.charAt(index) != ';') { + positiveSuffix = parseText(true, false); + } + if (index < string.length()) { + if (string.charAt(index++) != ';') { + throw new IllegalArgumentException("Expected ';' at " + index + " in " + string); + } + negativePrefix = parseText(false, true); + parseNumber(false); + negativeSuffix = parseText(true, true); + } + } + + public void apply(TDecimalFormat format) { + format.positivePrefix = positivePrefix; + format.positiveSuffix = positiveSuffix; + if (negativePrefix != null) { + format.negativePrefix = negativePrefix; + } else { + format.negativePrefix = new FormatField[positivePrefix.length + 1]; + System.arraycopy(positivePrefix, 0, format.negativePrefix, 1, positivePrefix.length); + format.negativePrefix[0] = new TDecimalFormat.MinusField(); + } + format.negativeSuffix = negativeSuffix != null ? negativeSuffix : positiveSuffix; + format.setGroupingSize(groupSize); + format.setGroupingUsed(groupSize > 0); + format.setMinimumIntegerDigits(!decimalSeparatorRequired ? minimumIntLength : + Math.max(1, minimumIntLength)); + format.setMaximumIntegerDigits(intLength); + format.setMinimumFractionDigits(minimumFracLength); + format.setMaximumFractionDigits(fracLength); + format.setDecimalSeparatorAlwaysShown(decimalSeparatorRequired); + format.exponentDigits = exponentLength; + format.setMultiplier(multiplier); + } + + FormatField[] parseText(boolean suffix, boolean end) { + List fields = new ArrayList<>(); + StringBuilder sb = new StringBuilder(); + loop: while (index < string.length()) { + char c = string.charAt(index); + switch (c) { + case '#': + case '0': + if (suffix) { + throw new IllegalArgumentException("Prefix contains special character at " + index + " in " + + string); + } + break loop; + case ';': + if (end) { + throw new IllegalArgumentException("Prefix contains special character at " + index + " in " + + string); + } + break loop; + case '.': + case 'E': + throw new IllegalArgumentException("Prefix contains special character at " + index + " in " + + string); + case '\'': { + ++index; + int next = string.indexOf('\'', index); + if (next < 0) { + throw new IllegalArgumentException("Quote opened at " + index + " was not closed in " + + string); + } + if (next == index) { + sb.append('\''); + } else { + sb.append(string.substring(index, next)); + } + index = next + 1; + break; + } + // Currency symbol ¤ + case '\u00A4': + if (sb.length() > 0) { + fields.add(new TDecimalFormat.TextField(sb.toString())); + sb.setLength(0); + } + fields.add(new TDecimalFormat.CurrencyField()); + ++index; + break; + case '%': + if (sb.length() > 0) { + fields.add(new TDecimalFormat.TextField(sb.toString())); + sb.setLength(0); + } + fields.add(new TDecimalFormat.PercentField()); + ++index; + multiplier = 100; + break; + // Per mill symbol + case '\u2030': + if (sb.length() > 0) { + fields.add(new TDecimalFormat.TextField(sb.toString())); + sb.setLength(0); + } + fields.add(new TDecimalFormat.PerMillField()); + ++index; + multiplier = 1000; + break; + case '-': + if (sb.length() > 0) { + fields.add(new TDecimalFormat.TextField(sb.toString())); + sb.setLength(0); + } + fields.add(new TDecimalFormat.MinusField()); + ++index; + break; + default: + sb.append(c); + ++index; + break; + } + } + if (sb.length() > 0) { + fields.add(new TDecimalFormat.TextField(sb.toString())); + } + return fields.toArray(new FormatField[fields.size()]); + } + + private void parseNumber(boolean apply) { + parseIntegerPart(apply); + if (index < string.length() && string.charAt(index) == '.') { + ++index; + parseFractionalPart(apply); + } + if (index < string.length() && string.charAt(index) == 'E') { + ++index; + parseExponent(apply); + } + } + + private void parseIntegerPart(boolean apply) { + int start = index; + int lastGroup = index; + boolean optionalDigits = true; + int length = 0; + int minimumLength = 0; + loop: while (index < string.length()) { + switch (string.charAt(index)) { + case '#': + if (!optionalDigits) { + throw new IllegalArgumentException("Unexpected '#' at non-optional digit part at " + index + + " in " + string); + } + ++length; + break; + case ',': + if (lastGroup == index) { + throw new IllegalArgumentException("Two group separators at " + index + " in " + string); + } + if (apply) { + groupSize = index - lastGroup; + } + lastGroup = index + 1; + break; + case '0': + optionalDigits = false; + ++length; + ++minimumLength; + break; + default: + break loop; + } + ++index; + } + if (length == 0) { + throw new IllegalArgumentException("Pattern does not specify integer digits at " + index + + " in " + string); + } + if (lastGroup == index) { + throw new IllegalArgumentException("Group separator at the end of number at " + index + " in " + string); + } + if (apply && lastGroup > start) { + groupSize = index - lastGroup; + } + if (apply) { + intLength = length; + minimumIntLength = minimumLength; + } + } + + private void parseFractionalPart(boolean apply) { + boolean optionalDigits = false; + int length = 0; + int minimumLength = 0; + loop: while (index < string.length()) { + switch (string.charAt(index)) { + case '#': + ++length; + optionalDigits = true; + break; + case ',': + throw new IllegalArgumentException("Group separator found at fractional part at " + index + + " in " + string); + case '0': + if (optionalDigits) { + throw new IllegalArgumentException("Unexpected '0' at optional digit part at " + index + + " in " + string); + } + ++length; + ++minimumLength; + break; + case '.': + throw new IllegalArgumentException("Unexpected second decimal separator at " + index + + " in " + string); + default: + break loop; + } + ++index; + } + if (apply) { + fracLength = length; + minimumFracLength = minimumLength; + decimalSeparatorRequired = length == 0; + } + } + + private void parseExponent(boolean apply) { + int length = 0; + loop: while (index < string.length()) { + switch (string.charAt(index)) { + case '#': + case ',': + case '.': + case 'E': + throw new IllegalArgumentException("Unexpected char at exponent at " + index + + " in " + string); + case '0': + ++length; + break; + default: + break loop; + } + ++index; + } + if (length == 0) { + throw new IllegalArgumentException("Pattern does not specify exponent digits at " + index + + " in " + string); + } + if (apply) { + exponentLength = length; + } + } +} diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatSymbols.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatSymbols.java index 56191625a..6c8d1bdc1 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatSymbols.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TDecimalFormatSymbols.java @@ -15,7 +15,7 @@ */ package org.teavm.classlib.java.text; -import org.teavm.classlib.impl.unicode.CLDRDecimalData; +import org.teavm.classlib.impl.unicode.DecimalData; import org.teavm.classlib.impl.unicode.CLDRHelper; import org.teavm.classlib.java.util.TLocale; @@ -23,7 +23,7 @@ import org.teavm.classlib.java.util.TLocale; * * @author Alexey Andreev */ -public class TDecimalFormatSymbols { +public class TDecimalFormatSymbols implements Cloneable { private TLocale locale; private char zeroDigit; private char groupingSeparator; @@ -48,18 +48,18 @@ public class TDecimalFormatSymbols { } private void initData() { - CLDRDecimalData data = CLDRHelper.resolveDecimalData(locale.getLanguage(), locale.getCountry()); + DecimalData data = CLDRHelper.resolveDecimalData(locale.getLanguage(), locale.getCountry()); zeroDigit = '0'; groupingSeparator = (char)data.getGroupingSeparator(); decimalSeparator = (char)data.getDecimalSeparator(); - perMill = (char)data.getPerMill(); + perMill = (char)data.getPerMille(); percent = (char)data.getPercent(); digit = '#'; patternSeparator = ';'; NaN = data.getNaN(); infinity = data.getInfinity(); minusSign = (char)data.getMinusSign(); - monetaryDecimalSeparator = (char)data.getMonetaryDecimalSeparator(); + monetaryDecimalSeparator = (char)data.getDecimalSeparator(); exponentSeparator = data.getExponentSeparator(); } diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java index 545185fe2..60ea007be 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/text/TNumberFormat.java @@ -15,7 +15,10 @@ */ package org.teavm.classlib.java.text; +import java.util.Objects; import org.teavm.classlib.impl.unicode.CLDRHelper; +import org.teavm.classlib.java.math.TRoundingMode; +import org.teavm.classlib.java.util.TCurrency; import org.teavm.classlib.java.util.TLocale; /** @@ -25,9 +28,11 @@ import org.teavm.classlib.java.util.TLocale; public abstract class TNumberFormat extends TFormat { public static final int INTEGER_FIELD = 0; public static final int FRACTION_FIELD = 1; - private boolean groupingUsed = true, parseIntegerOnly = false; + private boolean groupingUsed = true, parseIntegerOnly; private int maximumIntegerDigits = 40, minimumIntegerDigits = 1, maximumFractionDigits = 3, minimumFractionDigits = 0; + private TRoundingMode roundingMode = TRoundingMode.HALF_EVEN; + TCurrency currency = TCurrency.getInstance(TLocale.getDefault()); public TNumberFormat() { } @@ -37,6 +42,14 @@ public abstract class TNumberFormat extends TFormat { return super.clone(); } + public TCurrency getCurrency() { + return currency; + } + + public void setCurrency(TCurrency currency) { + this.currency = currency; + } + @Override public boolean equals(Object object) { if (object == this) { @@ -51,7 +64,9 @@ public abstract class TNumberFormat extends TFormat { && maximumFractionDigits == obj.maximumFractionDigits && maximumIntegerDigits == obj.maximumIntegerDigits && minimumFractionDigits == obj.minimumFractionDigits - && minimumIntegerDigits == obj.minimumIntegerDigits; + && minimumIntegerDigits == obj.minimumIntegerDigits + && roundingMode == obj.roundingMode + && currency == obj.currency; } public final String format(double value) { @@ -140,7 +155,8 @@ public abstract class TNumberFormat extends TFormat { public int hashCode() { return (groupingUsed ? 1231 : 1237) + (parseIntegerOnly ? 1231 : 1237) + maximumFractionDigits + maximumIntegerDigits - + minimumFractionDigits + minimumIntegerDigits; + + minimumFractionDigits + minimumIntegerDigits + + roundingMode.hashCode() + Objects.hashCode(currency); } public boolean isGroupingUsed() { @@ -211,6 +227,14 @@ public abstract class TNumberFormat extends TFormat { parseIntegerOnly = value; } + public TRoundingMode getRoundingMode() { + return roundingMode; + } + + public void setRoundingMode(TRoundingMode roundingMode) { + this.roundingMode = roundingMode; + } + public static class Field extends TFormat.Field { public static final Field SIGN = new Field("sign"); public static final Field INTEGER = new Field("integer"); diff --git a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TObservable.java b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TObservable.java index dc65d1316..f8af26322 100644 --- a/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TObservable.java +++ b/teavm-classlib/src/main/java/org/teavm/classlib/java/util/TObservable.java @@ -20,7 +20,7 @@ package org.teavm.classlib.java.util; * @author shannah */ public class TObservable { - TList observers = new TArrayList(); + TList observers = new TArrayList<>(); boolean changed = false; @@ -34,7 +34,7 @@ public class TObservable { /** * Adds the specified observer to the list of observers. If it is already * registered, it is not added a second time. - * + * * @param observer * the Observer to add. */ @@ -58,7 +58,7 @@ public class TObservable { /** * Returns the number of observers registered to this {@code Observable}. - * + * * @return the number of observers. */ public int countObservers() { @@ -68,7 +68,7 @@ public class TObservable { /** * Removes the specified observer from the list of observers. Passing null * won't do anything. - * + * * @param observer * the observer to remove. */ @@ -85,7 +85,7 @@ public class TObservable { /** * Returns the changed flag for this {@code Observable}. - * + * * @return {@code true} when the changed flag for this {@code Observable} is * set, {@code false} otherwise. */ @@ -108,11 +108,10 @@ public class TObservable { * If {@code hasChanged()} returns {@code true}, calls the {@code update()} * method for every Observer in the list of observers using the specified * argument. Afterwards calls {@code clearChanged()}. - * + * * @param data * the argument passed to {@code update()}. */ - @SuppressWarnings("unchecked") public void notifyObservers(Object data) { int size = 0; TObserver[] arrays = null; diff --git a/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java b/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java index 8d4f627d0..af9941e38 100644 --- a/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java +++ b/teavm-cli/src/main/java/org/teavm/cli/TeaVMTestRunner.java @@ -65,6 +65,10 @@ public final class TeaVMTestRunner { .withDescription("qualified class names of transformers") .withLongOpt("transformers") .create("T")); + options.addOption(OptionBuilder + .withDescription("Incremental build") + .withLongOpt("incremental") + .create('i')); if (args.length == 0) { printUsage(options); @@ -97,6 +101,10 @@ public final class TeaVMTestRunner { tool.getTransformers().add(instantiateTransformer(transformerType)); } } + if (commandLine.hasOption('i')) { + tool.setIncremental(true); + } + args = commandLine.getArgs(); if (args.length == 0) { System.err.println("You did not specify any test classes"); diff --git a/teavm-core/src/main/java/org/teavm/cache/NoCache.java b/teavm-core/src/main/java/org/teavm/cache/NoCache.java new file mode 100644 index 000000000..02ac35c36 --- /dev/null +++ b/teavm-core/src/main/java/org/teavm/cache/NoCache.java @@ -0,0 +1,30 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * 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. + */ +package org.teavm.cache; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * + * @author Alexey Andreev + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface NoCache { +} diff --git a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java index 290cbc094..80969bd09 100644 --- a/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java +++ b/teavm-core/src/main/java/org/teavm/javascript/Decompiler.java @@ -28,6 +28,7 @@ import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; +import org.teavm.cache.NoCache; import org.teavm.common.Graph; import org.teavm.common.GraphIndexer; import org.teavm.common.Loop; @@ -245,7 +246,7 @@ public class Decompiler { } public RegularMethodNode decompileRegular(MethodHolder method) { - if (regularMethodCache == null) { + if (regularMethodCache == null || method.getAnnotations().get(NoCache.class.getName()) != null) { return decompileRegularCacheMiss(method); } RegularMethodNode node = regularMethodCache.get(method.getReference()); @@ -283,7 +284,7 @@ public class Decompiler { } public AsyncMethodNode decompileAsync(MethodHolder method) { - if (regularMethodCache == null) { + if (regularMethodCache == null || method.getAnnotations().get(NoCache.class.getName()) != null) { return decompileAsyncCacheMiss(method); } AsyncMethodNode node = regularMethodCache.getAsync(method.getReference()); diff --git a/teavm-core/src/main/java/org/teavm/model/InMemoryProgramCache.java b/teavm-core/src/main/java/org/teavm/model/InMemoryProgramCache.java index e9046950a..6d085a704 100644 --- a/teavm-core/src/main/java/org/teavm/model/InMemoryProgramCache.java +++ b/teavm-core/src/main/java/org/teavm/model/InMemoryProgramCache.java @@ -17,6 +17,7 @@ package org.teavm.model; import java.util.HashMap; import java.util.Map; +import org.teavm.model.util.ProgramUtils; /** * @@ -27,11 +28,12 @@ public class InMemoryProgramCache implements ProgramCache { @Override public Program get(MethodReference method) { - return cache.get(method); + Program program = cache.get(method); + return program != null ? ProgramUtils.copy(program) : null; } @Override public void store(MethodReference method, Program program) { - cache.put(method, program); + cache.put(method, ProgramUtils.copy(program)); } } diff --git a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java index 413421a0c..83ffe6def 100644 --- a/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java +++ b/teavm-core/src/main/java/org/teavm/tooling/TeaVMTool.java @@ -236,7 +236,7 @@ public class TeaVMTool { symbolTable.update(); fileTable.update(); } catch (IOException e) { - log.info("Cache was not read"); + log.info("Cache is missing"); } vmBuilder.setClassLoader(classLoader).setClassSource(cachedClassSource); } else { diff --git a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java index f06967cd5..086b7e7c7 100644 --- a/teavm-core/src/main/java/org/teavm/vm/TeaVM.java +++ b/teavm-core/src/main/java/org/teavm/vm/TeaVM.java @@ -17,6 +17,7 @@ package org.teavm.vm; import java.io.*; import java.util.*; +import org.teavm.cache.NoCache; import org.teavm.codegen.*; import org.teavm.common.ServiceRepository; import org.teavm.debugging.information.DebugInformationEmitter; @@ -616,7 +617,9 @@ public class TeaVM implements TeaVMHost, ServiceRepository { if (method.getProgram() == null) { return; } - Program optimizedProgram = incremental && programCache != null ? + + boolean noCache = method.getAnnotations().get(NoCache.class.getName()) != null; + Program optimizedProgram = incremental && !noCache && programCache != null ? programCache.get(method.getReference()) : null; if (optimizedProgram == null) { optimizedProgram = ProgramUtils.copy(method.getProgram()); diff --git a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js index 6d8e90300..b4e1b0d5e 100644 --- a/teavm-core/src/main/resources/org/teavm/javascript/runtime.js +++ b/teavm-core/src/main/resources/org/teavm/javascript/runtime.js @@ -744,6 +744,9 @@ function Long_rem(a, b) { return Long_divRem(a, b)[1]; } function Long_divRem(a, b) { + if (b.lo == 0 && b.hi == 0) { + throw new Error("Division by zero"); + } var positive = Long_isNegative(a) === Long_isNegative(b); if (Long_isNegative(a)) { a = Long_neg(a); diff --git a/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java b/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java index 2ac8a0bc9..65e4235c7 100644 --- a/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java +++ b/teavm-platform/src/main/java/org/teavm/platform/plugin/MetadataProviderTransformer.java @@ -15,6 +15,7 @@ */ package org.teavm.platform.plugin; +import org.teavm.cache.NoCache; import org.teavm.diagnostics.Diagnostics; import org.teavm.javascript.spi.GeneratedBy; import org.teavm.model.*; @@ -49,10 +50,14 @@ class MetadataProviderTransformer implements ClassHolderTransformer { method.getReference(), ClassScopedMetadataProvider.class.getName(), PlatformClass.class.getName()); } + AnnotationHolder genAnnot = new AnnotationHolder(GeneratedBy.class.getName()); genAnnot.getValues().put("value", new AnnotationValue(ValueType.object( ClassScopedMetadataProviderNativeGenerator.class.getName()))); method.getAnnotations().add(genAnnot); + + AnnotationHolder noCacheAnnot = new AnnotationHolder(NoCache.class.getName()); + method.getAnnotations().add(noCacheAnnot); } } } @@ -95,6 +100,9 @@ class MetadataProviderTransformer implements ClassHolderTransformer { fork.setElse(pe.createBlock()); pe.setField(field.getReference(), field.getType(), pe.invoke(createMethod.getReference())); pe.jump(resourceFound); + + AnnotationHolder noCacheAnnot = new AnnotationHolder(NoCache.class.getName()); + method.getAnnotations().add(noCacheAnnot); } private boolean validate(MethodHolder method, Diagnostics diagnostics) { diff --git a/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatParseTest.java b/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatParseTest.java new file mode 100644 index 000000000..a203570d1 --- /dev/null +++ b/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatParseTest.java @@ -0,0 +1,127 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * 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. + */ +package org.teavm.classlib.java.text; + +import static org.junit.Assert.*; +import java.math.BigDecimal; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.text.ParseException; +import java.util.Locale; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class DecimalFormatParseTest { + private static DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH); + + @Test + public void parsesNumber() throws ParseException { + DecimalFormat format = createFormat("#,#00.#"); + assertEquals(2L, format.parse("2")); + assertEquals(23L, format.parse("23")); + assertEquals(23L, format.parse("23.0")); + assertEquals(2300L, format.parse("2,3,0,0")); + assertEquals(23.1, format.parse("23.1")); + } + + @Test + public void parsesBigNumber() throws ParseException { + DecimalFormat format = createFormat("#,#00.#"); + format.setParseBigDecimal(true); + assertEquals(BigDecimal.valueOf(2), format.parse("2")); + assertEquals(BigDecimal.valueOf(23), format.parse("23")); + assertEquals(BigDecimal.valueOf(230, 1), format.parse("23.0")); + assertEquals(BigDecimal.valueOf(2300), format.parse("2,3,0,0")); + assertEquals(BigDecimal.valueOf(231, 1), format.parse("23.1")); + } + + @Test + public void parsesLargeValue() throws ParseException { + DecimalFormat format = createFormat("#,#00.#"); + assertEquals(9223372036854775807L, format.parse("9223372036854775807")); + assertEquals(99E18, format.parse("99000000000000000000")); + assertEquals(3.333333333333333E20, format.parse("333333333333333333456").doubleValue(), 1000000); + assertEquals(10E20, format.parse("999999999999999999999").doubleValue(), 1000000); + } + + @Test + public void parsesExponential() throws ParseException { + DecimalFormat format = createFormat("0.#E0"); + assertEquals(23L, format.parse("2.3E1")); + assertEquals(23L, format.parse("2300E-2")); + assertEquals(0.23, format.parse("2300E-4").doubleValue(), 0.0001); + assertEquals(99E18, format.parse("99E18")); + } + + @Test + public void parsesBigExponential() throws ParseException { + DecimalFormat format = createFormat("0.#E0"); + format.setParseBigDecimal(true); + assertEquals(BigDecimal.valueOf(23), format.parse("2.3E1")); + assertEquals(BigDecimal.valueOf(2300, 2), format.parse("2300E-2")); + assertEquals(BigDecimal.valueOf(2300, 4), format.parse("2300E-4")); + assertEquals(BigDecimal.valueOf(99, -18), format.parse("99E18")); + } + + @Test + public void parsesPrefixSuffix() throws ParseException { + DecimalFormat format = createFormat("[0.#E0]"); + assertEquals(23L, format.parse("[23]")); + assertEquals(-23L, format.parse("-[23]")); + try { + format.parse("23"); + fail("Exception expected as there aren't neither prefix nor suffix"); + } catch (ParseException e) { + assertEquals(0, e.getErrorOffset()); + } + try { + format.parse("[23"); + fail("Exception expected as there is no suffix"); + } catch (ParseException e) { + assertEquals(3, e.getErrorOffset()); + } + } + + @Test + public void parsesPercent() throws ParseException { + DecimalFormat format = createFormat("0.#E0%"); + assertEquals(0.23, format.parse("23%").doubleValue(), 0.001); + assertEquals(23L, format.parse("2300%")); + } + + @Test + public void parsesBigPercent() throws ParseException { + DecimalFormat format = createFormat("0.#E0%"); + format.setParseBigDecimal(true); + assertEquals(BigDecimal.valueOf(23, 2), format.parse("23%")); + assertEquals(BigDecimal.valueOf(23, 0), format.parse("2300%")); + } + + @Test + public void parsesSpecial() throws ParseException { + DecimalFormat format = createFormat("0.#E0"); + assertEquals(Double.POSITIVE_INFINITY, format.parse("∞")); + assertEquals(Double.NEGATIVE_INFINITY, format.parse("-∞")); + assertEquals(-0.0, format.parse("-0")); + } + + private DecimalFormat createFormat(String format) { + return new DecimalFormat(format, symbols); + } +} diff --git a/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java b/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java new file mode 100644 index 000000000..496e54b3f --- /dev/null +++ b/teavm-tests/src/test/java/org/teavm/classlib/java/text/DecimalFormatTest.java @@ -0,0 +1,475 @@ +/* + * Copyright 2015 Alexey Andreev. + * + * 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. + */ +package org.teavm.classlib.java.text; + +import static org.junit.Assert.*; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.math.RoundingMode; +import java.text.DecimalFormat; +import java.text.DecimalFormatSymbols; +import java.util.Currency; +import java.util.Locale; +import org.junit.Test; + +/** + * + * @author Alexey Andreev + */ +public class DecimalFormatTest { + private static DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH); + + @Test + public void parsesIntegerPattern() { + DecimalFormat format = createFormat("00"); + assertEquals(2, format.getMinimumIntegerDigits()); + assertFalse(format.isDecimalSeparatorAlwaysShown()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + + format = createFormat("##"); + assertEquals(0, format.getMinimumIntegerDigits()); + assertFalse(format.isDecimalSeparatorAlwaysShown()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + + format = createFormat("#,##0"); + assertEquals(1, format.getMinimumIntegerDigits()); + assertFalse(format.isDecimalSeparatorAlwaysShown()); + assertTrue(format.isGroupingUsed()); + assertEquals(3, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + } + + @Test + public void selectsLastGrouping() { + DecimalFormat format = new DecimalFormat("#,0,000"); + assertEquals(4, format.getMinimumIntegerDigits()); + assertTrue(format.isGroupingUsed()); + assertEquals(3, format.getGroupingSize()); + } + + @Test + public void parsesPrefixAndSuffixInPattern() { + DecimalFormat format = createFormat("(00)"); + assertEquals(2, format.getMinimumIntegerDigits()); + assertEquals("(", format.getPositivePrefix()); + assertEquals(")", format.getPositiveSuffix()); + assertEquals("-(", format.getNegativePrefix()); + assertEquals(")", format.getNegativeSuffix()); + + format = createFormat("+(00);-{#}"); + assertEquals(2, format.getMinimumIntegerDigits()); + assertEquals("+(", format.getPositivePrefix()); + assertEquals(")", format.getPositiveSuffix()); + assertEquals("-{", format.getNegativePrefix()); + } + + @Test + public void parsesFractionalPattern() { + DecimalFormat format = createFormat("#."); + assertEquals(1, format.getMinimumIntegerDigits()); + assertTrue(format.isDecimalSeparatorAlwaysShown()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + + format = createFormat("#.00"); + assertEquals(0, format.getMinimumIntegerDigits()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(2, format.getMinimumFractionDigits()); + assertEquals(2, format.getMaximumFractionDigits()); + + format = createFormat("#.00##"); + assertEquals(0, format.getMinimumIntegerDigits()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(2, format.getMinimumFractionDigits()); + assertEquals(4, format.getMaximumFractionDigits()); + + format = createFormat("#00.00##"); + assertEquals(2, format.getMinimumIntegerDigits()); + assertFalse(format.isGroupingUsed()); + assertEquals(0, format.getGroupingSize()); + assertEquals(2, format.getMinimumFractionDigits()); + assertEquals(4, format.getMaximumFractionDigits()); + + format = createFormat("#,#00.00##"); + assertEquals(2, format.getMinimumIntegerDigits()); + assertTrue(format.isGroupingUsed()); + assertEquals(3, format.getGroupingSize()); + assertEquals(2, format.getMinimumFractionDigits()); + assertEquals(4, format.getMaximumFractionDigits()); + } + + @Test + public void parsesExponentialPattern() { + DecimalFormat format = createFormat("##0E00"); + assertEquals(1, format.getMinimumIntegerDigits()); + assertEquals(0, format.getGroupingSize()); + assertEquals(0, format.getMinimumFractionDigits()); + assertEquals(0, format.getMaximumFractionDigits()); + } + + @Test + public void formatsIntegerPart() { + DecimalFormat format = createFormat("00"); + assertEquals("02", format.format(2)); + assertEquals("23", format.format(23)); + assertEquals("23", format.format(23.2)); + assertEquals("24", format.format(23.7)); + } + + @Test + public void formatsBigIntegerPart() { + DecimalFormat format = createFormat("00"); + assertEquals("02", format.format(new BigInteger("2"))); + assertEquals("23", format.format(new BigInteger("23"))); + assertEquals("23", format.format(new BigDecimal("23.2"))); + assertEquals("24", format.format(new BigDecimal("23.7"))); + } + + @Test + public void formatsNumber() { + DecimalFormat format = createFormat("0.0"); + assertEquals("23.0", format.format(23)); + assertEquals("23.2", format.format(23.2)); + assertEquals("23.2", format.format(23.23)); + assertEquals("23.3", format.format(23.27)); + assertEquals("0.0", format.format(0.0001)); + + format = createFormat("00000000000000000000000000.0"); + assertEquals("00000000000000000000000023.0", format.format(23)); + assertEquals("00002300000000000000000000.0", format.format(23E20)); + assertEquals("23000000000000000000000000.0", format.format(23E24)); + + format = createFormat("0.00000000000000000000000000"); + assertEquals("23.00000000000000000000000000", format.format(23)); + assertEquals("0.23000000000000000000000000", format.format(0.23)); + assertEquals("0.00230000000000000000000000", format.format(0.0023)); + assertEquals("0.00000000000000000000230000", format.format(23E-22)); + assertEquals("0.00000000000000000000000023", format.format(23E-26)); + } + + @Test + public void formatsBigNumber() { + DecimalFormat format = createFormat("0.0"); + assertEquals("23.0", format.format(BigInteger.valueOf(23))); + assertEquals("23.2", format.format(new BigDecimal("23.2"))); + assertEquals("23.2", format.format(new BigDecimal("23.23"))); + assertEquals("23.3", format.format(new BigDecimal("23.27"))); + assertEquals("0.0", format.format(new BigDecimal("0.0001"))); + + format = createFormat("00000000000000000000000000.0"); + assertEquals("00000000000000000000000023.0", format.format(new BigInteger("23"))); + assertEquals("00002300000000000000000000.0", format.format(new BigInteger("2300000000000000000000"))); + assertEquals("23000000000000000000000000.0", format.format(new BigInteger("23000000000000000000000000"))); + + format = createFormat("0.00000000000000000000000000"); + assertEquals("23.00000000000000000000000000", format.format(new BigInteger("23"))); + assertEquals("0.23000000000000000000000000", format.format(new BigDecimal("0.23"))); + assertEquals("0.00230000000000000000000000", format.format(new BigDecimal("0.0023"))); + assertEquals("0.00000000000000000000230000", format.format(new BigDecimal("0.0000000000000000000023"))); + assertEquals("0.00000000000000000000000023", format.format(new BigDecimal("0.00000000000000000000000023"))); + } + + @Test + public void formatsFractionalPart() { + DecimalFormat format = createFormat("0.0000####"); + assertEquals("0.00001235", format.format(0.0000123456)); + assertEquals("0.00012346", format.format(0.000123456)); + assertEquals("0.00123456", format.format(0.00123456)); + assertEquals("0.0123456", format.format(0.0123456)); + assertEquals("0.1200", format.format(0.12)); + assertEquals("0.1230", format.format(0.123)); + assertEquals("0.1234", format.format(0.1234)); + assertEquals("0.12345", format.format(0.12345)); + + format = createFormat("0.##"); + assertEquals("23", format.format(23)); + assertEquals("2.3", format.format(2.3)); + assertEquals("0.23", format.format(0.23)); + assertEquals("0.02", format.format(0.023)); + } + + @Test + public void roundingWorks() { + DecimalFormat format = createFormat("0"); + + format.setRoundingMode(RoundingMode.UP); + assertEquals("3", format.format(2.3)); + assertEquals("3", format.format(2.7)); + assertEquals("-3", format.format(-2.3)); + assertEquals("-3", format.format(-2.7)); + + format.setRoundingMode(RoundingMode.DOWN); + assertEquals("2", format.format(2.3)); + assertEquals("2", format.format(2.7)); + assertEquals("-2", format.format(-2.3)); + assertEquals("-2", format.format(-2.7)); + + format.setRoundingMode(RoundingMode.FLOOR); + assertEquals("2", format.format(2.3)); + assertEquals("2", format.format(2.7)); + assertEquals("-3", format.format(-2.3)); + assertEquals("-3", format.format(-2.7)); + + format.setRoundingMode(RoundingMode.CEILING); + assertEquals("3", format.format(2.3)); + assertEquals("3", format.format(2.7)); + assertEquals("-2", format.format(-2.3)); + assertEquals("-2", format.format(-2.7)); + + format.setRoundingMode(RoundingMode.HALF_DOWN); + assertEquals("2", format.format(2.3)); + assertEquals("3", format.format(2.7)); + assertEquals("2", format.format(2.5)); + assertEquals("3", format.format(3.5)); + assertEquals("-2", format.format(-2.5)); + assertEquals("-3", format.format(-3.5)); + + format.setRoundingMode(RoundingMode.HALF_UP); + assertEquals("2", format.format(2.3)); + assertEquals("3", format.format(2.7)); + assertEquals("3", format.format(2.5)); + assertEquals("4", format.format(3.5)); + assertEquals("-3", format.format(-2.5)); + assertEquals("-4", format.format(-3.5)); + + format.setRoundingMode(RoundingMode.HALF_EVEN); + assertEquals("2", format.format(2.3)); + assertEquals("3", format.format(2.7)); + assertEquals("2", format.format(2.5)); + assertEquals("4", format.format(3.5)); + assertEquals("-2", format.format(-2.5)); + assertEquals("-4", format.format(-3.5)); + } + + @Test + public void bigRoundingWorks() { + DecimalFormat format = createFormat("0"); + + format.setRoundingMode(RoundingMode.UP); + assertEquals("3", format.format(new BigDecimal("2.3"))); + assertEquals("3", format.format(new BigDecimal("2.7"))); + assertEquals("-3", format.format(new BigDecimal("-2.3"))); + assertEquals("-3", format.format(new BigDecimal("-2.7"))); + + format.setRoundingMode(RoundingMode.DOWN); + assertEquals("2", format.format(new BigDecimal("2.3"))); + assertEquals("2", format.format(new BigDecimal("2.7"))); + assertEquals("-2", format.format(new BigDecimal("-2.3"))); + assertEquals("-2", format.format(new BigDecimal("-2.7"))); + + format.setRoundingMode(RoundingMode.FLOOR); + assertEquals("2", format.format(new BigDecimal("2.3"))); + assertEquals("2", format.format(new BigDecimal("2.7"))); + assertEquals("-3", format.format(new BigDecimal("-2.3"))); + assertEquals("-3", format.format(new BigDecimal("-2.7"))); + + format.setRoundingMode(RoundingMode.CEILING); + assertEquals("3", format.format(new BigDecimal("2.3"))); + assertEquals("3", format.format(new BigDecimal("2.7"))); + assertEquals("-2", format.format(new BigDecimal("-2.3"))); + assertEquals("-2", format.format(new BigDecimal("-2.7"))); + + format.setRoundingMode(RoundingMode.HALF_DOWN); + assertEquals("2", format.format(new BigDecimal("2.3"))); + assertEquals("3", format.format(new BigDecimal("2.7"))); + assertEquals("2", format.format(new BigDecimal("2.5"))); + assertEquals("3", format.format(new BigDecimal("3.5"))); + assertEquals("-2", format.format(new BigDecimal("-2.5"))); + assertEquals("-3", format.format(new BigDecimal("-3.5"))); + + format.setRoundingMode(RoundingMode.HALF_UP); + assertEquals("2", format.format(new BigDecimal("2.3"))); + assertEquals("3", format.format(new BigDecimal("2.7"))); + assertEquals("3", format.format(new BigDecimal("2.5"))); + assertEquals("4", format.format(new BigDecimal("3.5"))); + assertEquals("-3", format.format(new BigDecimal("-2.5"))); + assertEquals("-4", format.format(new BigDecimal("-3.5"))); + + format.setRoundingMode(RoundingMode.HALF_EVEN); + assertEquals("2", format.format(new BigDecimal("2.3"))); + assertEquals("3", format.format(new BigDecimal("2.7"))); + assertEquals("2", format.format(new BigDecimal("2.5"))); + assertEquals("4", format.format(new BigDecimal("3.5"))); + assertEquals("-2", format.format(new BigDecimal("-2.5"))); + assertEquals("-4", format.format(new BigDecimal("-3.5"))); + } + + @Test + public void formatsWithGroups() { + DecimalFormat format = createFormat("#,###.0"); + assertEquals("23.0", format.format(23)); + assertEquals("2,300.0", format.format(2300)); + assertEquals("2,300,000,000,000,000,000,000.0", format.format(23E20)); + assertEquals("23,000,000,000,000,000,000,000,000.0", format.format(23E24)); + + format = createFormat("000,000,000,000,000,000,000"); + assertEquals("000,000,000,000,000,000,023", format.format(23)); + } + + @Test + public void formatsBigWithGroups() { + DecimalFormat format = createFormat("#,###.0"); + assertEquals("23.0", format.format(BigInteger.valueOf(23))); + assertEquals("2,300.0", format.format(BigInteger.valueOf(2300))); + assertEquals("2,300,000,000,000,000,000,000.0", format.format(new BigInteger("2300000000000000000000"))); + assertEquals("23,000,000,000,000,000,000,000,000.0", format.format( + new BigInteger("23000000000000000000000000"))); + + format = createFormat("000,000,000,000,000,000,000"); + assertEquals("000,000,000,000,000,000,023", format.format(BigInteger.valueOf(23))); + } + + @Test + public void formatsLargeValues() { + DecimalFormat format = createFormat("0"); + assertEquals("9223372036854775807", format.format(9223372036854775807L)); + assertEquals("-9223372036854775808", format.format(-9223372036854775808L)); + } + + @Test + public void formatsExponent() { + DecimalFormat format = createFormat("000E0"); + assertEquals("230E-1", format.format(23)); + assertEquals("230E0", format.format(230)); + assertEquals("230E1", format.format(2300)); + assertEquals("123E1", format.format(1234)); + assertEquals("-123E1", format.format(-1234)); + + format = createFormat("0.00E0"); + assertEquals("2.00E1", format.format(20)); + assertEquals("2.30E1", format.format(23)); + assertEquals("2.30E2", format.format(230)); + assertEquals("1.23E3", format.format(1234)); + + format = createFormat("000000000000000000000.00E0"); + assertEquals("230000000000000000000.00E-19", format.format(23)); + + format = createFormat("0.0000000000000000000000E0"); + assertEquals("2.3000000000000000000000E1", format.format(23)); + + format = createFormat("0.0##E0"); + assertEquals("1.0E0", format.format(1)); + assertEquals("1.2E1", format.format(12)); + assertEquals("1.23E2", format.format(123)); + assertEquals("1.234E3", format.format(1234)); + assertEquals("1.234E4", format.format(12345)); + } + + @Test + public void formatsBigExponent() { + DecimalFormat format = createFormat("000E0"); + assertEquals("230E-1", format.format(BigInteger.valueOf(23))); + assertEquals("230E0", format.format(BigInteger.valueOf(230))); + assertEquals("230E1", format.format(BigInteger.valueOf(2300))); + assertEquals("123E1", format.format(BigInteger.valueOf(1234))); + assertEquals("-123E1", format.format(BigInteger.valueOf(-1234))); + + format = createFormat("0.00E0"); + assertEquals("2.00E1", format.format(BigInteger.valueOf(20))); + assertEquals("2.30E1", format.format(BigInteger.valueOf(23))); + assertEquals("2.30E2", format.format(BigInteger.valueOf(230))); + assertEquals("1.23E3", format.format(BigInteger.valueOf(1234))); + + format = createFormat("000000000000000000000.00E0"); + assertEquals("230000000000000000000.00E-19", format.format(BigInteger.valueOf(23))); + + format = createFormat("0.0000000000000000000000E0"); + assertEquals("2.3000000000000000000000E1", format.format(BigInteger.valueOf(23))); + + format = createFormat("0.0##E0"); + assertEquals("1.0E0", format.format(BigInteger.valueOf(1))); + assertEquals("1.2E1", format.format(BigInteger.valueOf(12))); + assertEquals("1.23E2", format.format(BigInteger.valueOf(123))); + assertEquals("1.234E3", format.format(BigInteger.valueOf(1234))); + assertEquals("1.234E4", format.format(BigInteger.valueOf(12345))); + } + + @Test + public void formatsExponentWithMultiplier() { + DecimalFormat format = createFormat("##0.00E0"); + assertEquals("2.30E0", format.format(2.3)); + assertEquals("23.0E0", format.format(23)); + assertEquals("230E0", format.format(230)); + assertEquals("2.30E3", format.format(2300)); + assertEquals("23.0E3", format.format(23000)); + } + + @Test + public void formatsBigExponentWithMultiplier() { + DecimalFormat format = createFormat("##0.00E0"); + assertEquals("2.30E0", format.format(new BigDecimal("2.3"))); + assertEquals("23.0E0", format.format(new BigDecimal("23"))); + assertEquals("230E0", format.format(new BigDecimal("230"))); + assertEquals("2.30E3", format.format(new BigDecimal("2300"))); + assertEquals("23.0E3", format.format(new BigDecimal("23000"))); + } + + @Test + public void formatsSpecialValues() { + DecimalFormat format = createFormat("0"); + assertEquals("∞", format.format(Double.POSITIVE_INFINITY)); + assertEquals("-∞", format.format(Double.NEGATIVE_INFINITY)); + } + + @Test + public void formatsWithMultiplier() { + DecimalFormat format = createFormat("0"); + format.setMultiplier(2); + assertEquals("18446744073709551614", format.format(9223372036854775807L)); + assertEquals("46", format.format(BigInteger.valueOf(23))); + + format.setMultiplier(100); + assertEquals("2300", format.format(23)); + assertEquals("2300", format.format(BigInteger.valueOf(23))); + + format = createFormat("00E0"); + format.setMultiplier(2); + assertEquals("18E18", format.format(9223372036854775807L)); + assertEquals("46E0", format.format(BigInteger.valueOf(23))); + + format.setMultiplier(100); + assertEquals("23E2", format.format(23)); + assertEquals("23E2", format.format(BigInteger.valueOf(23))); + } + + @Test + public void formatsSpecial() { + DecimalFormat format = createFormat("0%"); + assertEquals("23%", format.format(0.23)); + + format = createFormat("0‰"); + assertEquals("230‰", format.format(0.23)); + + format = createFormat("0.00 ¤"); + format.setCurrency(Currency.getInstance("RUB")); + assertEquals("23.00 RUB", format.format(23)); + } + + private DecimalFormat createFormat(String format) { + return new DecimalFormat(format, symbols); + } +}