blob: 422e6b66c8a42dd0532f427b36f03b6ecb780c82 [file] [log] [blame]
/* GENERATED SOURCE. DO NOT MODIFY. */
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html#License
package android.icu.impl.number.formatters;
import android.icu.impl.number.Format;
import android.icu.impl.number.FormatQuantity;
import android.icu.impl.number.FormatQuantitySelector;
import android.icu.impl.number.ModifierHolder;
import android.icu.impl.number.Properties;
import android.icu.impl.number.Rounder;
import android.icu.impl.number.modifiers.ConstantAffixModifier;
import android.icu.impl.number.modifiers.PositiveNegativeAffixModifier;
import android.icu.impl.number.rounders.IncrementRounder;
import android.icu.impl.number.rounders.SignificantDigitsRounder;
import android.icu.text.DecimalFormatSymbols;
import android.icu.text.NumberFormat;
/**
* @hide Only a subset of ICU is exposed in Android
*/
public class ScientificFormat extends Format.BeforeFormat implements Rounder.MultiplierGenerator {
public static interface IProperties
extends RoundingFormat.IProperties, CurrencyFormat.IProperties {
static boolean DEFAULT_EXPONENT_SIGN_ALWAYS_SHOWN = false;
/** @see #setExponentSignAlwaysShown */
public boolean getExponentSignAlwaysShown();
/**
* Sets whether to show the plus sign in the exponent part of numbers with a zero or positive
* exponent. For example, the number "1200" with the pattern "0.0E0" would be formatted as
* "1.2E+3" instead of "1.2E3" in <em>en-US</em>.
*
* @param exponentSignAlwaysShown Whether to show the plus sign in positive exponents.
* @return The property bag, for chaining.
*/
public IProperties setExponentSignAlwaysShown(boolean exponentSignAlwaysShown);
static int DEFAULT_MINIMUM_EXPONENT_DIGITS = -1;
/** @see #setMinimumExponentDigits */
public int getMinimumExponentDigits();
/**
* Sets the minimum number of digits to display in the exponent. For example, the number "1200"
* with the pattern "0.0E00", which has 2 exponent digits, would be formatted as "1.2E03" in
* <em>en-US</em>.
*
* @param minimumExponentDigits The minimum number of digits to display in the exponent field.
* @return The property bag, for chaining.
*/
public IProperties setMinimumExponentDigits(int minimumExponentDigits);
}
public static boolean useScientificNotation(IProperties properties) {
return properties.getMinimumExponentDigits() != IProperties.DEFAULT_MINIMUM_EXPONENT_DIGITS;
}
private static final ThreadLocal<Properties> threadLocalProperties =
new ThreadLocal<Properties>() {
@Override
protected Properties initialValue() {
return new Properties();
}
};
public static ScientificFormat getInstance(DecimalFormatSymbols symbols, IProperties properties) {
// If significant digits or rounding interval are specified through normal means, we use those.
// Otherwise, we use the special significant digit rules for scientific notation.
Rounder rounder;
if (IncrementRounder.useRoundingIncrement(properties)) {
rounder = IncrementRounder.getInstance(properties);
} else if (SignificantDigitsRounder.useSignificantDigits(properties)) {
rounder = SignificantDigitsRounder.getInstance(properties);
} else {
Properties rprops = threadLocalProperties.get().clear();
int minInt = properties.getMinimumIntegerDigits();
int maxInt = properties.getMaximumIntegerDigits();
int minFrac = properties.getMinimumFractionDigits();
int maxFrac = properties.getMaximumFractionDigits();
// If currency is in use, pull information from CurrencyUsage.
if (CurrencyFormat.useCurrency(properties)) {
// Use rprops as the vehicle (it is still clean)
CurrencyFormat.populateCurrencyRounderProperties(rprops, symbols, properties);
minFrac = rprops.getMinimumFractionDigits();
maxFrac = rprops.getMaximumFractionDigits();
rprops.clear();
}
// TODO: Mark/Andy, take a look at this logic and see if it makes sense to you.
// I fiddled with the settings and fallbacks to make the unit tests pass, but I
// don't feel that it's the "right way" to do things.
if (minInt < 0) minInt = 0;
if (maxInt < minInt) maxInt = minInt;
if (minFrac < 0) minFrac = 0;
if (maxFrac < minFrac) maxFrac = minFrac;
rprops.setRoundingMode(properties.getRoundingMode());
if (minInt == 0 && maxFrac == 0) {
// Special case for the pattern "#E0" with no significant digits specified.
rprops.setMinimumSignificantDigits(1);
rprops.setMaximumSignificantDigits(Integer.MAX_VALUE);
} else if (minInt == 0 && minFrac == 0) {
// Special case for patterns like "#.##E0" with no significant digits specified.
rprops.setMinimumSignificantDigits(1);
rprops.setMaximumSignificantDigits(1 + maxFrac);
} else {
rprops.setMinimumSignificantDigits(minInt + minFrac);
rprops.setMaximumSignificantDigits(minInt + maxFrac);
}
rprops.setMinimumIntegerDigits(maxInt == 0 ? 0 : Math.max(1, minInt + minFrac - maxFrac));
rprops.setMaximumIntegerDigits(maxInt);
rprops.setMinimumFractionDigits(Math.max(0, minFrac + minInt - maxInt));
rprops.setMaximumFractionDigits(maxFrac);
rounder = SignificantDigitsRounder.getInstance(rprops);
}
return new ScientificFormat(symbols, properties, rounder);
}
public static ScientificFormat getInstance(
DecimalFormatSymbols symbols, IProperties properties, Rounder rounder) {
return new ScientificFormat(symbols, properties, rounder);
}
// Properties
private final boolean exponentShowPlusSign;
private final int exponentDigits;
private final int minInt;
private final int maxInt;
private final int interval;
private final Rounder rounder;
private final ConstantAffixModifier separatorMod;
private final PositiveNegativeAffixModifier signMod;
// Symbols
private final String[] digitStrings;
private ScientificFormat(DecimalFormatSymbols symbols, IProperties properties, Rounder rounder) {
exponentShowPlusSign = properties.getExponentSignAlwaysShown();
exponentDigits = Math.max(1, properties.getMinimumExponentDigits());
// Calculate minInt/maxInt for the purposes of engineering notation:
// 0 <= minInt <= maxInt < 8
// The values are validated separately for rounding. This scheme needs to prevent OOM issues
// (see #13118). Note that the bound 8 on integer digits is historic.
int _maxInt = properties.getMaximumIntegerDigits();
int _minInt = properties.getMinimumIntegerDigits();
// Bug #13289: if maxInt > minInt > 1, then minInt should be 1 for the
// purposes of engineering notatation.
if (_maxInt > _minInt && _minInt > 1) {
_minInt = 1;
}
minInt = _minInt < 0 ? 0 : _minInt >= 8 ? 1 : _minInt;
maxInt = _maxInt < _minInt ? _minInt : _maxInt >= 8 ? _minInt : _maxInt;
assert 0 <= minInt && minInt <= maxInt && maxInt < 8;
interval = maxInt < 1 ? 1 : maxInt;
this.rounder = rounder;
digitStrings = symbols.getDigitStrings(); // makes a copy
separatorMod =
new ConstantAffixModifier(
"", symbols.getExponentSeparator(), NumberFormat.Field.EXPONENT_SYMBOL, true);
signMod =
new PositiveNegativeAffixModifier(
new ConstantAffixModifier(
"",
exponentShowPlusSign ? symbols.getPlusSignString() : "",
NumberFormat.Field.EXPONENT_SIGN,
true),
new ConstantAffixModifier(
"", symbols.getMinusSignString(), NumberFormat.Field.EXPONENT_SIGN, true));
}
private static final ThreadLocal<StringBuilder> threadLocalStringBuilder =
new ThreadLocal<StringBuilder>() {
@Override
protected StringBuilder initialValue() {
return new StringBuilder();
}
};
@Override
public void before(FormatQuantity input, ModifierHolder mods) {
// Treat zero as if it had magnitude 0
int exponent;
if (input.isZero()) {
rounder.apply(input);
exponent = 0;
} else {
exponent = -rounder.chooseMultiplierAndApply(input, this);
}
// Format the exponent part of the scientific format.
// Insert digits starting from the left so that append can be used.
// TODO: Use thread locals here.
FormatQuantity exponentQ = FormatQuantitySelector.from(exponent);
StringBuilder exponentSB = threadLocalStringBuilder.get();
exponentSB.setLength(0);
exponentQ.setIntegerFractionLength(exponentDigits, Integer.MAX_VALUE, 0, 0);
for (int i = exponentQ.getUpperDisplayMagnitude(); i >= 0; i--) {
exponentSB.append(digitStrings[exponentQ.getDigit(i)]);
}
// Add modifiers from the outside in.
mods.add(
new ConstantAffixModifier("", exponentSB.toString(), NumberFormat.Field.EXPONENT, true));
mods.add(signMod.getModifier(exponent < 0));
mods.add(separatorMod);
}
@Override
public int getMultiplier(int magnitude) {
int digitsShown = ((magnitude % interval + interval) % interval) + 1;
if (digitsShown < minInt) {
digitsShown = minInt;
} else if (digitsShown > maxInt) {
digitsShown = maxInt;
}
int retval = digitsShown - magnitude - 1;
return retval;
}
@Override
public void export(Properties properties) {
properties.setMinimumExponentDigits(exponentDigits);
properties.setExponentSignAlwaysShown(exponentShowPlusSign);
// Set the transformed object into the property bag. This may result in a pattern string that
// uses different syntax from the original, but it will be functionally equivalent.
rounder.export(properties);
}
}