const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &quantity,
MicroProps µs,
UErrorCode &status) const {
- // TODO(icu-units#21): mixed units without usage() is not yet supported.
- // That should be the only reason why this happens, so delete this whole if
- // once fixed:
if (micros.mixedMeasuresCount == 0) {
+ U_ASSERT(micros.mixedMeasuresCount > 0); // Mixed unit: we must have more than one unit value
status = U_UNSUPPORTED_ERROR;
return µs.helpers.emptyWeakModifier;
}
auto appendable = UnicodeStringAppendable(num);
fIntegerFormatter.formatDecimalQuantity(fdec, status).appendTo(appendable, status);
compiledFormatter.format(num, outputMeasuresList[i], status);
+ // TODO(icu-units#67): fix field positions
}
UnicodeString *finalSimpleFormats = &fMixedUnitData[(fMixedUnitCount - 1) * ARRAY_LENGTH];
return µs.helpers.emptyWeakModifier;
}
+ // TODO(icu-units#67): fix field positions
// Return a SimpleModifier for the "premixed" pattern
micros.helpers.mixedUnitModifier =
SimpleModifier(premixedCompiled, kUndefinedField, false, {this, SIGNUM_POS_ZERO, finalPlural});
// Not owned
const MicroPropsGenerator *parent;
- // Total number of units in the MeasureUnit this LongNameHandler was
- // configured for: for "foot-and-inch", this will be 2. (If not a mixed unit,
- // this will be 1.)
+ // Total number of units in the MeasureUnit this handler was configured for:
+ // for "foot-and-inch", this will be 2.
int32_t fMixedUnitCount = 1;
- // If this LongNameHandler is for a mixed unit, this stores unit data for
- // each of the individual units. For each unit, it stores ARRAY_LENGTH
- // strings, as returned by getMeasureData. (Each unit with index `i` has
- // ARRAY_LENGTH strings starting at index `i*ARRAY_LENGTH` in this array.)
+ // Stores unit data for each of the individual units. For each unit, it
+ // stores ARRAY_LENGTH strings, as returned by getMeasureData. (Each unit
+ // with index `i` has ARRAY_LENGTH strings starting at index
+ // `i*ARRAY_LENGTH` in this array.)
LocalArray<UnicodeString> fMixedUnitData;
// A localized NumberFormatter used to format the integer-valued bigger
// units of Mixed Unit measurements.
UErrorCode status = U_ZERO_ERROR;
};
-// TODO(units): generated by MicroPropsGenerator, but inherits from it too. Do
-// we want to better document why? There's an explanation for processQuantity:
-// * As MicroProps is the "base instance", this implementation of
-// * MicroPropsGenerator::processQuantity() just ensures that the output
-// * `micros` is correctly initialized.
+/**
+ * MicroProps is the first MicroPropsGenerator that should be should be called,
+ * producing an initialized MicroProps instance that will be passed on and
+ * modified throughout the rest of the chain of MicroPropsGenerator instances.
+ */
struct MicroProps : public MicroPropsGenerator {
// NOTE: All of these fields are properly initialized in NumberFormatterImpl.
fUsage[fLength] = 0;
}
+// Populates micros.mixedMeasures and modifies quantity, based on the values in
+// measures.
void mixedMeasuresToMicros(const MaybeStackVector<Measure> &measures, DecimalQuantity *quantity,
MicroProps *micros, UErrorCode status) {
micros->mixedMeasuresCount = measures.length() - 1;
if (U_FAILURE(status)) {
return;
}
- const MaybeStackVector<Measure>& routedUnits = routed.measures;
+ const MaybeStackVector<Measure>& routedMeasures = routed.measures;
micros.outputUnit = routed.outputUnit.copy(status).build(status);
if (U_FAILURE(status)) {
return;
}
- mixedMeasuresToMicros(routedUnits, &quantity, µs, status);
+ mixedMeasuresToMicros(routedMeasures, &quantity, µs, status);
UnicodeString precisionSkeleton = routed.precision;
if (micros.rounder.fPrecision.isBogus()) {
FormattedNumber formattedNum;
UnicodeString uTestCase;
+ status.assertSuccess();
+ formattedNum =
+ NumberFormatter::with().usage("road").locale(Locale::getEnglish()).formatInt(1, status);
+ status.expectErrorAndReset(U_ILLEGAL_ARGUMENT_ERROR);
+
unloc_formatter = NumberFormatter::with().usage("road").unit(MeasureUnit::getMeter());
uTestCase = u"unitUsage() en-ZA road";
u"0 pounds, 0 ounces");
assertFormatDescendingBig(
- u"Scientific notation with Usage: possible when using a reasonable Precision",
- u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
- u"scientific @### usage/default unit/square-meter unit-width-full-name",
- NumberFormatter::with()
- .unit(SQUARE_METER)
- .usage("default")
- .notation(Notation::scientific())
- .precision(Precision::minMaxSignificantDigits(1, 4))
- .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
- Locale("en-ZA"),
- u"8,765E1 square kilometres",
- u"8,765E0 square kilometres",
- u"8,765E1 hectares",
- u"8,765E0 hectares",
- u"8,765E3 square metres",
- u"8,765E2 square metres",
- u"8,765E1 square metres",
- u"8,765E0 square metres",
- u"0E0 square centimetres");
+ u"Scientific notation with Usage: possible when using a reasonable Precision",
+ u"scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
+ u"scientific @### usage/default unit/square-meter unit-width-full-name",
+ NumberFormatter::with()
+ .unit(SQUARE_METER)
+ .usage("default")
+ .notation(Notation::scientific())
+ .precision(Precision::minMaxSignificantDigits(1, 4))
+ .unitWidth(UNumberUnitWidth::UNUM_UNIT_WIDTH_FULL_NAME),
+ Locale("en-ZA"),
+ u"8,765E1 square kilometres",
+ u"8,765E0 square kilometres",
+ u"8,765E1 hectares",
+ u"8,765E0 hectares",
+ u"8,765E3 square metres",
+ u"8,765E2 square metres",
+ u"8,765E1 square metres",
+ u"8,765E0 square metres",
+ u"0E0 square centimetres");
+
+ assertFormatSingle(
+ u"Rounding Mode propagates: rounding down",
+ u"usage/road measure-unit/length-centimeter rounding-mode-floor",
+ u"usage/road unit/centimeter rounding-mode-floor",
+ NumberFormatter::with()
+ .unit(MeasureUnit::forIdentifier("centimeter", status))
+ .usage("road")
+ .roundingMode(UNUM_ROUND_FLOOR),
+ Locale("en-ZA"),
+ 34500,
+ u"300 m");
+
+ assertFormatSingle(
+ u"Rounding Mode propagates: rounding up",
+ u"usage/road measure-unit/length-centimeter rounding-mode-ceiling",
+ u"usage/road unit/centimeter rounding-mode-ceiling",
+ NumberFormatter::with()
+ .unit(MeasureUnit::forIdentifier("centimeter", status))
+ .usage("road")
+ .roundingMode(UNUM_ROUND_CEILING),
+ Locale("en-ZA"),
+ 30500,
+ u"350 m");
}
void NumberFormatterApiTest::unitUsageErrorCodes() {
import com.ibm.icu.util.ULocale;
import com.ibm.icu.util.UResourceBundle;
-public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
+public class LongNameHandler
+ implements MicroPropsGenerator, ModifierStore, LongNameMultiplexer.ParentlessMicroPropsGenerator {
private static final int DNAM_INDEX = StandardPlural.COUNT;
private static final int PER_INDEX = StandardPlural.COUNT + 1;
- protected static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
+ static final int ARRAY_LENGTH = StandardPlural.COUNT + 2;
private static int getIndex(String pluralKeyword) {
// pluralKeyword can also be "dnam" or "per"
}
}
- protected static String getWithPlural(String[] strings, StandardPlural plural) {
+ static String getWithPlural(String[] strings, StandardPlural plural) {
String result = strings[plural.ordinal()];
if (result == null) {
result = strings[StandardPlural.OTHER.ordinal()];
// NOTE: outArray MUST have at least ARRAY_LENGTH entries. No bounds checking is performed.
- protected static void getMeasureData(
+ static void getMeasureData(
ULocale locale,
MeasureUnit unit,
UnitWidth width,
* <p>
* Mixed units are not supported, use MixedUnitLongNameHandler.forMeasureUnit.
*
- * @param locale The desired locale.
- * @param unit The measure unit to construct a LongNameHandler for. If
- * `perUnit` is also defined, `unit` must not be a mixed unit.
- * @param perUnit If `unit` is a mixed unit, `perUnit` must be "none".
- * @param width Specifies the desired unit rendering.
- * @param rules Does not take ownership.
- * @param parent Does not take ownership.
+ * @param locale The desired locale.
+ * @param unit The measure unit to construct a LongNameHandler for. If
+ * `perUnit` is also defined, `unit` must not be a mixed unit.
+ * @param perUnit If `unit` is a mixed unit, `perUnit` must be null.
+ * @param width Specifies the desired unit rendering.
+ * @param rules Plural rules.
+ * @param parent Plural rules.
*/
public static LongNameHandler forMeasureUnit(
ULocale locale,
UnitWidth width,
PluralRules rules,
MicroPropsGenerator parent) {
+ if (unit.getType() == null || (perUnit != null && perUnit.getType() == null)) {
+ // TODO(ICU-20941): Unsanctioned unit. Not yet fully supported. Set an
+ // error code. Once we support not-built-in units here, unitRef may be
+ // anything, but if not built-in, perUnit has to be "none".
+ throw new UnsupportedOperationException("Unsanctioned units, not yet supported");
+ }
if (perUnit != null) {
// Compound unit: first try to simplify (e.g., meters per second is its own unit).
MeasureUnit simplified = MeasureUnit.resolveUnitPerUnit(unit, perUnit);
return micros;
}
+ /**
+ * Produces a plural-appropriate Modifier for a unit: `quantity` is taken as
+ * the final smallest unit, while the larger unit values must be provided
+ * via `micros.mixedMeasures`.
+ *
+ * Does not call parent.processQuantity, so cannot get a MicroProps instance
+ * that way. Instead, the instance is passed in as a parameter.
+ */
+ public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) {
+ StandardPlural pluralForm = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
+ micros.modOuter = modifiers.get(pluralForm);
+ return micros;
+ }
+
@Override
public Modifier getModifier(Signum signum, StandardPlural plural) {
// Signum ignored
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
-
-
package com.ibm.icu.impl.number;
import com.ibm.icu.number.NumberFormatter;
import java.util.ArrayList;
import java.util.List;
+/**
+ * A MicroPropsGenerator that multiplexes between different LongNameHandlers,
+ * depending on the outputUnit.
+ *
+ * See processQuantity() for the input requirements.
+ */
public class LongNameMultiplexer implements MicroPropsGenerator {
+ /**
+ * LongNameMultiplexer calls the parent MicroPropsGenerator itself,
+ * receiving the MicroProps instance in use for this formatting pipeline.
+ * Next it multiplexes between name handlers (fHandlers) which are not given
+ * access to a parent. Consequently LongNameMultiplexer must give these
+ * handlers the MicroProps instance.
+ */
+ public static interface ParentlessMicroPropsGenerator {
+ public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros);
+ }
+
private final MicroPropsGenerator fParent;
- private List<MicroPropsGenerator> fHandlers;
+ private List<ParentlessMicroPropsGenerator> fHandlers;
// Each MeasureUnit corresponds to the same-index MicroPropsGenerator
// pointed to in fHandlers.
result.fHandlers.add(mlnh);
} else {
LongNameHandler lnh = LongNameHandler
- .forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null );
+ .forMeasureUnit(locale, unit, NoUnit.BASE, width, rules, null);
result.fHandlers.add(lnh);
}
}
// one of the units provided to the factory function.
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
-
// We call parent->processQuantity() from the Multiplexer, instead of
// letting LongNameHandler handle it: we don't know which LongNameHandler to
// call until we've called the parent!
// Call the correct LongNameHandler based on outputUnit
for (int i = 0; i < this.fHandlers.size(); i++) {
- if (fMeasureUnits.get(i).equals( micros.outputUnit)) {
- return fHandlers.get(i).processQuantity(quantity);
+ if (fMeasureUnits.get(i).equals(micros.outputUnit)) {
+ ParentlessMicroPropsGenerator handler = fHandlers.get(i);
+ return handler.processQuantityWithMicros(quantity, micros);
}
-
}
-
throw new AssertionError
(" We shouldn't receive any outputUnit for which we haven't already got a LongNameHandler");
}
import java.util.List;
-// TODO(units): generated by MicroPropsGenerator, but inherits from it too. Do we want to better document why?
-// There's an explanation for processQuantity:
-// - As MicroProps is the "base instance", this implementation of
-// - MicoPropsGenerator::processQuantity() just ensures that the output
-// - `micros` is correctly initialized.
+/**
+ * MicroProps is the first MicroPropsGenerator that should be should be called,
+ * producing an initialized MicroProps instance that will be passed on and
+ * modified throughout the rest of the chain of MicroPropsGenerator instances.
+ */
public class MicroProps implements Cloneable, MicroPropsGenerator {
// Populated globally:
public SignDisplay sign;
// In the case of mixed units, this is the set of integer-only units
// *preceding* the final unit.
- public List<Measure> mixedMeasures ;
+ public List<Measure> mixedMeasures;
private volatile boolean exhausted;
* will be modified and thus not be available for re-use.
*
* @param quantity The quantity for consideration and optional mutation.
- * @return a MicroProps instance to populate.
+ * @return an initialized MicroProps instance.
*/
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
if (immutable) {
return (MicroProps) this.clone();
+ } else if (exhausted) {
+ // Safety check
+ throw new AssertionError("Cannot re-use a mutable MicroProps in the quantity chain");
+ } else {
+ exhausted = true;
+ return this;
}
-
- assert !exhausted : "Cannot re-use a mutable MicroProps in the quantity chain";
- exhausted = true;
- return this;
}
@Override
* {@link MicroProps} with properties that are not quantity-dependent. Each element in the linked list
* calls {@link #processQuantity} on its "parent", then does its work, and then returns the result.
*
+ * <p>
* This chain of MicroPropsGenerators is typically constructed by NumberFormatterImpl::macrosToMicroGenerator() when
* constructing a NumberFormatter.
*
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
-
-
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.FormattedStringBuilder;
+import com.ibm.icu.impl.SimpleFormatterImpl;
import com.ibm.icu.impl.StandardPlural;
import com.ibm.icu.number.LocalizedNumberFormatter;
import com.ibm.icu.number.NumberFormatter;
import com.ibm.icu.text.SimpleFormatter;
import com.ibm.icu.util.MeasureUnit;
import com.ibm.icu.util.ULocale;
-
import java.util.ArrayList;
import java.util.List;
-public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierStore {
- // Not owned
+/** Similar to LongNameHandler, but only for MIXED units. */
+public class MixedUnitLongNameHandler
+ implements MicroPropsGenerator, ModifierStore, LongNameMultiplexer.ParentlessMicroPropsGenerator {
private final PluralRules rules;
- // Not owned
private final MicroPropsGenerator parent;
- // If this LongNameHandler is for a mixed unit, this stores unit data for
- // each of the individual units. For each unit, it stores ARRAY_LENGTH
- // strings, as returned by getMeasureData.
+ /**
+ * Stores unit data for each of the individual units. For each unit, it
+ * stores ARRAY_LENGTH strings, as returned by getMeasureData.
+ */
private List<String[]> fMixedUnitData;
- // A localized NumberFormatter used to format the integer-valued bigger
- // units of Mixed Unit measurements.
+ /**
+ * A localized NumberFormatter used to format the integer-valued bigger
+ * units of Mixed Unit measurements.
+ */
private LocalizedNumberFormatter fIntegerFormatter;
- // A localised list formatter for joining mixed units together.
+ /** A localised list formatter for joining mixed units together. */
private ListFormatter fListFormatter;
private MixedUnitLongNameHandler(PluralRules rules, MicroPropsGenerator parent) {
* @param mixedUnit The mixed measure unit to construct a
* MixedUnitLongNameHandler for.
* @param width Specifies the desired unit rendering.
- * @param rules Does not take ownership.
- * @param parent Does not take ownership.
+ * @param rules PluralRules instance.
+ * @param parent MicroPropsGenerator instance.
*/
public static MixedUnitLongNameHandler forMeasureUnit(ULocale locale, MeasureUnit mixedUnit,
NumberFormatter.UnitWidth width, PluralRules rules,
/**
* Produces a plural-appropriate Modifier for a mixed unit: `quantity` is
* taken as the final smallest unit, while the larger unit values must be
- * provided via `micros.mixedMeasures`.
+ * provided by `micros.mixedMeasures`, micros being the MicroProps instance
+ * returned by the parent.
+ *
+ * This function must not be called if this instance has no parent: call
+ * processQuantityWithMicros() instead.
*/
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
assert (fMixedUnitData.size() > 1);
MicroProps micros;
- // if (parent != null)
micros = parent.processQuantity(quantity);
micros.modOuter = getMixedUnitModifier(quantity, micros);
return micros;
}
- // Required for ModifierStore. And ModifierStore is required by
- // SimpleModifier constructor's last parameter. We assert his will never get
- // called though.
+ /**
+ * Produces a plural-appropriate Modifier for a mixed unit: `quantity` is
+ * taken as the final smallest unit, while the larger unit values must be
+ * provided via `micros.mixedMeasures`.
+ *
+ * Does not call parent.processQuantity, so cannot get a MicroProps instance
+ * that way. Instead, the instance is passed in as a parameter.
+ */
+ public MicroProps processQuantityWithMicros(DecimalQuantity quantity, MicroProps micros) {
+ assert (fMixedUnitData.size() > 1);
+ micros.modOuter = getMixedUnitModifier(quantity, micros);
+ return micros;
+ }
+
+ /**
+ * Required for ModifierStore. And ModifierStore is required by
+ * SimpleModifier constructor's last parameter. We assert his will never get
+ * called though.
+ */
@Override
public Modifier getModifier(Modifier.Signum signum, StandardPlural plural) {
// TODO(units): investigate this method while investigating where
// LongNameHandler.getModifier() gets used. To be sure it remains
// unreachable:
-
+ assert false : "should be unreachable";
return null;
}
- // For a mixed unit, returns a Modifier that takes only one parameter: the
- // smallest and final unit of the set. The bigger units' values and labels
- // get baked into this Modifier, together with the unit label of the final
- // unit.
+ /**
+ * For a mixed unit, returns a Modifier that takes only one parameter: the
+ * smallest and final unit of the set. The bigger units' values and labels
+ * get baked into this Modifier, together with the unit label of the final
+ * unit.
+ */
private Modifier getMixedUnitModifier(DecimalQuantity quantity, MicroProps micros) {
- // TODO(icu-units#21): mixed units without usage() is not yet supported.
- // That should be the only reason why this happens, so delete this whole if
- // once fixed:
if (micros.mixedMeasures.size() == 0) {
+ assert false : "Mixed unit: we must have more than one unit value";
throw new UnsupportedOperationException();
}
-
// Algorithm:
//
// For the mixed-units measurement of: "3 yard, 1 foot, 2.6 inch", we should
String simpleFormat = LongNameHandler.getWithPlural(this.fMixedUnitData.get(i), pluralForm);
SimpleFormatter compiledFormatter = SimpleFormatter.compileMinMaxArguments(simpleFormat, 0, 1);
-
FormattedStringBuilder appendable = new FormattedStringBuilder();
this.fIntegerFormatter.formatImpl(fdec, appendable);
outputMeasuresList.add(compiledFormatter.format(appendable.toString()));
- // TODO: fix this issue https://github.com/icu-units/icu/issues/67
+ // TODO(icu-units#67): fix field positions
}
String[] finalSimpleFormats = this.fMixedUnitData.get(this.fMixedUnitData.size() - 1);
StandardPlural finalPlural = RoundingUtils.getPluralSafe(micros.rounder, rules, quantity);
String finalSimpleFormat = LongNameHandler.getWithPlural(finalSimpleFormats, finalPlural);
SimpleFormatter finalFormatter = SimpleFormatter.compileMinMaxArguments(finalSimpleFormat, 0, 1);
- finalFormatter.format("{0}", outputMeasuresList.get(outputMeasuresList.size() -1));
+ outputMeasuresList.add(finalFormatter.format("{0}"));
// Combine list into a "premixed" pattern
String premixedFormatPattern = this.fListFormatter.format(outputMeasuresList);
- SimpleFormatter premixedCompiled = SimpleFormatter.compileMinMaxArguments(premixedFormatPattern, 0, 1);
+ StringBuilder sb = new StringBuilder();
+ String premixedCompiled =
+ SimpleFormatterImpl.compileToStringMinMaxArguments(premixedFormatPattern, sb, 0, 1);
- // Return a SimpleModifier for the "premixed" pattern
+ // TODO(icu-units#67): fix field positions
Modifier.Parameters params = new Modifier.Parameters();
params.obj = this;
params.signum = Modifier.Signum.POS_ZERO;
params.plural = finalPlural;
-
- return new SimpleModifier(premixedCompiled.getTextWithNoArguments(), null, false, params);
- /*TODO: it was SimpleModifier(premixedCompiled, kUndefinedField, false, {this, SIGNUM_POS_ZERO, finalPlural});*/
+ // Return a SimpleModifier for the "premixed" pattern
+ return new SimpleModifier(premixedCompiled, null, false, params);
}
}
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
-
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.units.ComplexUnitsConverter;
import java.util.List;
/**
- * A MicroPropsGenerator which converts a measurement from a simple MeasureUnit
- * to a Mixed MeasureUnit.
+ * A MicroPropsGenerator which converts a measurement from one MeasureUnit to
+ * another. In particular, the output MeasureUnit may be a mixed unit. (The
+ * input unit may not be a mixed unit.)
*/
public class UnitConversionHandler implements MicroPropsGenerator {
private MeasureUnit fOutputUnit;
private ComplexUnitsConverter fComplexUnitConverter;
- public UnitConversionHandler(MeasureUnit outputUnit, MicroPropsGenerator parent) {
+ /**
+ * Constructor.
+ *
+ * @param inputUnit Specifies the input MeasureUnit. Mixed units are not
+ * supported as input (because input is just a single decimal quantity).
+ * @param outputUnit Specifies the output MeasureUnit.
+ * @param parent The parent MicroPropsGenerator.
+ */
+ public UnitConversionHandler(MeasureUnit inputUnit,
+ MeasureUnit outputUnit,
+ MicroPropsGenerator parent) {
this.fOutputUnit = outputUnit;
this.fParent = parent;
-
- List<MeasureUnit> singleUnits = outputUnit.splitToSingleUnits();
-
- assert outputUnit.getComplexity() == MeasureUnit.Complexity.MIXED;
- assert singleUnits.size() > 1;
-
+ MeasureUnitImpl inputUnitImpl = MeasureUnitImpl.forIdentifier(inputUnit.getIdentifier());
MeasureUnitImpl outputUnitImpl = MeasureUnitImpl.forIdentifier(outputUnit.getIdentifier());
- // TODO(icu-units#97): The input unit should be the largest unit, not the first unit, in the identifier.
- this.fComplexUnitConverter =
- new ComplexUnitsConverter(
- new MeasureUnitImpl(outputUnitImpl.getSingleUnits().get(0)),
- outputUnitImpl,
- new UnitsData().getConversionRates());
+ this.fComplexUnitConverter = new ComplexUnitsConverter(inputUnitImpl, outputUnitImpl,
+ new UnitsData().getConversionRates());
}
/**
*/
@Override
public MicroProps processQuantity(DecimalQuantity quantity) {
- /*TODO: Questions : shall we check the parent if it is equals null */
- MicroProps result = this.fParent == null?
- this.fParent.processQuantity(quantity):
- new MicroProps(false);
+ MicroProps result = this.fParent.processQuantity(quantity);
quantity.roundToInfinity(); // Enables toDouble
List<Measure> measures = this.fComplexUnitConverter.convert(quantity.toBigDecimal());
result.outputUnit = this.fOutputUnit;
- result.mixedMeasures = new ArrayList<>();
UsagePrefsHandler.mixedMeasuresToMicros(measures, quantity, result);
return result;
// © 2020 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
-
package com.ibm.icu.impl.number;
import com.ibm.icu.impl.IllegalIcuArgumentException;
return Precision.increment(num.divide(den, MathContext.DECIMAL128));
}
+ /**
+ * Populates micros.mixedMeasures and modifies quantity, based on the values
+ * in measures.
+ */
protected static void mixedMeasuresToMicros(List<Measure> measures, DecimalQuantity quantity, MicroProps micros) {
+ micros.mixedMeasures = new ArrayList<>();
if (measures.size() > 1) {
// For debugging
assert (micros.outputUnit.getComplexity() == MeasureUnit.Complexity.MIXED);
- // Check that we received measurements with the expected MeasureUnits:
- List<MeasureUnit> singleUnits = micros.outputUnit.splitToSingleUnits();
-
- assert measures.size() == singleUnits.size();
+ // Check that we received the expected number of measurements:
+ assert measures.size() == micros.outputUnit.splitToSingleUnits().size();
// Mixed units: except for the last value, we pass all values to the
// LongNameHandler via micros->mixedMeasures.
final List<Measure> routedMeasures = routed.measures;
micros.outputUnit = routed.outputUnit.build();
- micros.mixedMeasures = new ArrayList<>();
UsagePrefsHandler.mixedMeasuresToMicros(routedMeasures, quantity, micros);
assert micros.rounder != null;
- // TODO: use the user precision if the user already set precision.
- if (precisionSkeleton != null && precisionSkeleton.length() > 0) {
- micros.rounder = parseSkeletonToPrecision(precisionSkeleton);
- } else {
- // We use the same rounding mode as COMPACT notation: known to be a
- // human-friendly rounding mode: integers, but add a decimal digit
- // as needed to ensure we have at least 2 significant digits.
- micros.rounder = Precision.integer().withMinDigits(2);
+ if (micros.rounder instanceof Precision.BogusRounder) {
+ Precision.BogusRounder rounder = (Precision.BogusRounder)micros.rounder;
+ if (precisionSkeleton != null && precisionSkeleton.length() > 0) {
+ micros.rounder = rounder.into(parseSkeletonToPrecision(precisionSkeleton));
+ } else {
+ // We use the same rounding mode as COMPACT notation: known to be a
+ // human-friendly rounding mode: integers, but add a decimal digit
+ // as needed to ensure we have at least 2 significant digits.
+ micros.rounder = rounder.into(Precision.integer().withMinDigits(2));
+ }
}
return micros;
|| !(isPercent || isPermille)
|| isCompactNotation
);
-
- // TODO(icu-units#95): Add the logic in this file that sets the rounder to bogus/pass-through if isMixedUnit is true.
boolean isMixedUnit = isCldrUnit && macros.unit.getType() == null &&
- macros.unit.getComplexity() == MeasureUnit.Complexity.MIXED;
+ macros.unit.getComplexity() == MeasureUnit.Complexity.MIXED;
+
PluralRules rules = macros.rules;
// Select the numbering system.
}
chain = usagePrefsHandler = new UsagePrefsHandler(macros.loc, macros.unit, macros.usage, chain);
} else if (isMixedUnit) {
- chain = new UnitConversionHandler(macros.unit, chain);
+ // TODO(icu-units#97): The input unit should be the largest unit, not the first unit, in the identifier.
+ MeasureUnit inputUnit = macros.unit.splitToSingleUnits().get(0);
+ chain = new UnitConversionHandler(inputUnit, macros.unit, chain);
}
// Multiplier
micros.rounder = Precision.COMPACT_STRATEGY;
} else if (isCurrency) {
micros.rounder = Precision.MONETARY_STANDARD;
+ } else if (macros.usage != null) {
+ // Bogus Precision - it will get set in the UsagePrefsHandler instead
+ micros.rounder = Precision.BOGUS_PRECISION;
} else {
micros.rounder = Precision.DEFAULT_MAX_FRAC_6;
}
static final int KEY_SCALE = 13;
static final int KEY_THRESHOLD = 14;
static final int KEY_PER_UNIT = 15;
- static final int KEY_MAX = 16;
- static final int KEY_USAGE = 17;
+ static final int KEY_USAGE = 16;
+ static final int KEY_MAX = 17;
private final NumberFormatterSettings<?> parent;
private final int key;
* <p>
* The default is to render without units (equivalent to {@link NoUnit#BASE}).
*
- * <P>
+ * <p>
* If the input usage is correctly set the output unit <b>will change</b>
* according to `usage`, `locale` and `unit` value.
* </p>
return create(KEY_SCALE, scale);
}
- /**
+ /**
* Specifies the usage for which numbers will be formatted ("person-height",
* "road", "rainfall", etc.)
*
+ * <p>
* When a `usage` is specified, the output unit will change depending on the
* `Locale` and the unit quantity. For example, formatting length
* measurements specified in meters:
*
- * `NumberFormatter.with().usage("person").unit(MeasureUnit.METER).locale(new ULocale("en-US"))`
- * * When formatting 0.25, the output will be "10 inches".
- * * When formatting 1.50, the output will be "4 feet and 11 inches".
+ * <p>
+ * <pre>
+ * NumberFormatter.with().usage("person").unit(MeasureUnit.METER).locale(new ULocale("en-US"))
+ * </pre>
+ * <ul>
+ * <li> When formatting 0.25, the output will be "10 inches".
+ * <li> When formatting 1.50, the output will be "4 feet and 11 inches".
+ * </li>
*
+ * <p>
* The input unit specified via unit() determines the type of measurement
* being formatted (e.g. "length" when the unit is "foot"). The usage
* requested will be looked for only within this category of measurement
* units.
*
+ * <p>
* The output unit can be found via FormattedNumber.getOutputUnit().
*
+ * <p>
* If the usage has multiple parts (e.g. "land-agriculture-grain") and does
* not match a known usage preference, the last part will be dropped
* repeatedly until a match is found (e.g. trying "land-agriculture", then
* "land"). If a match is still not found, usage will fall back to
* "default".
*
+ * <p>
* Setting usage to an empty string clears the usage (disables usage-based
* localized formatting).
*
+ * <p>
+ * Setting a usage string but not a correct input unit will result in an
+ * U_ILLEGAL_ARGUMENT_ERROR.
*
+ * <p>
* When using usage, specifying rounding or precision is unnecessary.
* Specifying a precision in some manner will override the default
* formatting.
*
- *
* @param usage A usage parameter from the units resource.
* @return The fluent chain
* @throws IllegalArgumentException in case of Setting a usage string but not a correct input unit.
- * @draft ICU 67
+ * @draft ICU 68
* @provisional This API might change or be removed in a future release.
*/
public T usage(String usage) {
} else if (macros.unit == MeasureUnit.PERMILLE) {
sb.append("permille");
return true;
- } else {
+ } else if (macros.unit.getType() != null) {
sb.append("measure-unit/");
BlueprintHelpers.generateMeasureUnitOption(macros.unit, sb);
return true;
+ } else {
+ // TODO(icu-units#35): add support for not-built-in units.
+ throw new UnsupportedOperationException();
}
}
*
* @stable ICU 62
* @see NumberFormatter
- * @internal
*/
public abstract class Precision {
// PACKAGE-PRIVATE APIS //
//////////////////////////
+ /**
+ * @internal
+ * @deprecated ICU internal only.
+ */
+ @Deprecated
+ public static final BogusRounder BOGUS_PRECISION = new BogusRounder();
+
static final InfiniteRounderImpl NONE = new InfiniteRounderImpl();
static final FractionRounderImpl FIXED_FRAC_0 = new FractionRounderImpl(0, 0);
// INTERNALS //
///////////////
+ /**
+ * An BogusRounder's MathContext into precision.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public static class BogusRounder extends Precision {
+ @Override
+ public void apply(DecimalQuantity value) {
+ throw new AssertionError("BogusRounder must not be applied");
+ }
+
+ @Override
+ BogusRounder createCopy() {
+ BogusRounder copy = new BogusRounder();
+ copy.mathContext = mathContext;
+ return copy;
+ }
+
+ /**
+ * Copies the BogusRounder's MathContext into precision.
+ *
+ * @internal
+ * @deprecated This API is ICU internal only.
+ */
+ @Deprecated
+ public Precision into(Precision precision) {
+ Precision copy = precision.createCopy();
+ copy.mathContext = mathContext;
+ return copy;
+ }
+ }
+
static class InfiniteRounderImpl extends Precision {
public InfiniteRounderImpl() {
import com.ibm.icu.dev.test.TestFmwk;
import com.ibm.icu.dev.test.format.FormattedValueTest;
import com.ibm.icu.dev.test.serializable.SerializableTestUtility;
+import com.ibm.icu.impl.IllegalIcuArgumentException;
import com.ibm.icu.impl.number.Grouper;
import com.ibm.icu.impl.number.LocalizedNumberFormatterAsFormat;
import com.ibm.icu.impl.number.MacroProps;
1,
"kelvin");
+ assertFormatSingle(
+ "Person unit not in short form",
+ "measure-unit/duration-year-person unit-width-full-name",
+ "unit/year-person unit-width-full-name",
+ NumberFormatter.with()
+ .unit(MeasureUnit.YEAR_PERSON)
+ .unitWidth(UnitWidth.FULL_NAME),
+ ULocale.forLanguageTag("es-MX"),
+ 5,
+ "5 a\u00F1os");
+
// TODO(icu-units#35): skeleton generation.
assertFormatSingle(
"Mixed unit",
"0.008765 m/s",
"0 m/s");
+ // TODO(icu-units#35): does not normalize as desired: while "unit/*" does
+ // get split into unit/perUnit, ".unit(*)" and "measure-unit/*" don't:
+ assertFormatSingle(
+ "Built-in unit, meter-per-second",
+ "measure-unit/speed-meter-per-second",
+ "~unit/meter-per-second",
+ NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND),
+ new ULocale("en-GB"),
+ 2.4,
+ "2.4 m/s");
+
assertFormatDescending(
"Pounds Per Square Mile Short (secondary unit has per-format)",
"measure-unit/mass-pound per-measure-unit/area-square-mile",
"0.008765 J/fur",
"0 J/fur");
- // TODO(icu-units#35): does not normalize as desired: while "unit/*" does
- // get split into unit/perUnit, ".unit(*)" and "measure-unit/*" don't:
- assertFormatSingle(
- "Built-in unit, meter-per-second",
- "measure-unit/speed-meter-per-second",
- "~unit/meter-per-second",
- NumberFormatter.with().unit(MeasureUnit.METER_PER_SECOND),
- new ULocale("en-GB"),
- 2.4,
- "2.4 m/s");
+ // // TODO(ICU-20941): Support constructions such as this one.
+ // assertFormatDescending(
+ // "Joules Per Furlong Short with unit identifier via API",
+ // "measure-unit/energy-joule per-measure-unit/length-furlong",
+ // "unit/joule-per-furlong",
+ // NumberFormatter.with().unit(MeasureUnit.forIdentifier("joule-per-furlong")),
+ // ULocale.ENGLISH,
+ // "87,650 J/fur",
+ // "8,765 J/fur",
+ // "876.5 J/fur",
+ // "87.65 J/fur",
+ // "8.765 J/fur",
+ // "0.8765 J/fur",
+ // "0.08765 J/fur",
+ // "0.008765 J/fur",
+ // "0 J/fur");
// TODO(icu-units#59): THIS UNIT TEST DEMONSTRATES UNDESIRABLE BEHAVIOUR!
// When specifying built-in types, one can give both a unit and a perUnit.
FormattedNumber formattedNum;
String uTestCase;
+ try {
+ NumberFormatter.with().usage("road").locale(ULocale.ENGLISH).format(1);
+ fail("should give an error, usage() without unit() is invalid");
+ } catch (IllegalIcuArgumentException e) {
+ // Pass
+ }
+
unloc_formatter = NumberFormatter.with().usage("road").unit(MeasureUnit.METER);
uTestCase = "unitUsage() en-ZA road";
uTestCase = "unitUsage() en-GB road";
formatter = unloc_formatter.locale(new ULocale("en-GB"));
formattedNum = formatter.format(321d);
-
- // status.errIfFailureAndReset("unitUsage() en-GB road, formatDouble(...)");
-
assertTrue(
uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
MeasureUnit.YARD.equals(formattedNum.getOutputUnit()));
- // status.errIfFailureAndReset("unitUsage() en-GB road, getOutputUnit(...)");
assertEquals(uTestCase, "350 yd", formattedNum.toString());
- //status.errIfFailureAndReset("unitUsage() en-GB road, toString(...)");
{
final Object[][] expectedFieldPositions = {
{NumberFormat.Field.INTEGER, 0, 3},
uTestCase = "unitUsage() en-US road";
formatter = unloc_formatter.locale(new ULocale("en-US"));
formattedNum = formatter.format(321d);
- // status.errIfFailureAndReset("unitUsage() en-US road, formatDouble(...)");
-
assertTrue(
uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
MeasureUnit.FOOT == formattedNum.getOutputUnit());
- // status.errIfFailureAndReset("unitUsage() en-US road, getOutputUnit(...)");
-
assertEquals(uTestCase, "1,050 ft", formattedNum.toString());
- // status.errIfFailureAndReset("unitUsage() en-US road, toString(...)");
{
final Object[][] expectedFieldPositions = {
{NumberFormat.Field.GROUPING_SEPARATOR, 1, 2},
uTestCase = "unitUsage() en-GB person";
formatter = unloc_formatter.locale(new ULocale("en-GB"));
formattedNum = formatter.format(80d);
- // status.errIfFailureAndReset("unitUsage() en-GB person formatDouble");
-
assertTrue(
uTestCase + ", got outputUnit: \"" + formattedNum.getOutputUnit().getIdentifier() + "\"",
MeasureUnit.forIdentifier("stone-and-pound").equals(formattedNum.getOutputUnit()));
- // status.errIfFailureAndReset("unitUsage() en-GB person - formattedNum.getOutputUnit(status)");
-
assertEquals(uTestCase, "12 st, 8.4 lb", formattedNum.toString());
- //status.errIfFailureAndReset("unitUsage() en-GB person, toString(...)");
{
final Object[][] expectedFieldPositions = {
// // Desired output: TODO(icu-units#67)
"0 pounds, 0.31 ounces",
"0 pounds, 0 ounces");
- // TODO: this is about the user overriding the usage precision.
- // TODO: should be done!
-// assertFormatDescendingBig(
-// "Scientific notation with Usage: possible when using a reasonable Precision",
-// "scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
-// "scientific @### usage/default unit/square-meter unit-width-full-name",
-// NumberFormatter.with()
-// .unit(MeasureUnit.SQUARE_METER)
-// .usage("default")
-// .notation(Notation.scientific())
-// .precision(Precision.minMaxSignificantDigits(1, 4))
-// .unitWidth(UnitWidth.FULL_NAME),
-// new ULocale("en-ZA"),
-// "8,765E1 square kilometres",
-// "8,765E0 square kilometres",
-// "8,765E1 hectares",
-// "8,765E0 hectares",
-// "8,765E3 square metres",
-// "8,765E2 square metres",
-// "8,765E1 square metres",
-// "8,765E0 square metres",
-// "0E0 square centimetres");
- }
+ assertFormatDescendingBig(
+ "Scientific notation with Usage: possible when using a reasonable Precision",
+ "scientific @### usage/default measure-unit/area-square-meter unit-width-full-name",
+ "scientific @### usage/default unit/square-meter unit-width-full-name",
+ NumberFormatter.with()
+ .unit(MeasureUnit.SQUARE_METER)
+ .usage("default")
+ .notation(Notation.scientific())
+ .precision(Precision.minMaxSignificantDigits(1, 4))
+ .unitWidth(UnitWidth.FULL_NAME),
+ new ULocale("en-ZA"),
+ "8,765E1 square kilometres",
+ "8,765E0 square kilometres",
+ "8,765E1 hectares",
+ "8,765E0 hectares",
+ "8,765E3 square metres",
+ "8,765E2 square metres",
+ "8,765E1 square metres",
+ "8,765E0 square metres",
+ "0E0 square centimetres");
+
+ assertFormatSingle(
+ "Rounding Mode propagates: rounding down",
+ "usage/road measure-unit/length-centimeter rounding-mode-floor",
+ "usage/road unit/centimeter rounding-mode-floor",
+ NumberFormatter.with()
+ .unit(MeasureUnit.forIdentifier("centimeter"))
+ .usage("road")
+ .roundingMode(RoundingMode.FLOOR),
+ new ULocale("en-ZA"),
+ 34500,
+ "300 m");
+
+ assertFormatSingle(
+ "Rounding Mode propagates: rounding up",
+ "usage/road measure-unit/length-centimeter rounding-mode-ceiling",
+ "usage/road unit/centimeter rounding-mode-ceiling",
+ NumberFormatter.with()
+ .unit(MeasureUnit.forIdentifier("centimeter"))
+ .usage("road")
+ .roundingMode(RoundingMode.CEILING),
+ new ULocale("en-ZA"),
+ 30500,
+ "350 m");
+}
@Test
321,
"300 m");
- // TODO(younies): enable this test case
-// assertFormatSingle(
-// "Precision can be overridden: override takes precedence",
-// "usage/road measure-unit/length-meter @#",
-// "usage/road unit/meter @#",
-// NumberFormatter.with()
-// .unit(MeasureUnit.METER)
-// .usage("road")
-// .precision(Precision.maxSignificantDigits(2)),
-// new ULocale("en-ZA"),
-// 321,
-// "320 m");
+ assertFormatSingle(
+ "Precision can be overridden: override takes precedence",
+ "usage/road measure-unit/length-meter @#",
+ "usage/road unit/meter @#",
+ NumberFormatter.with()
+ .unit(MeasureUnit.METER)
+ .usage("road")
+ .precision(Precision.maxSignificantDigits(2)),
+ new ULocale("en-ZA"),
+ 321,
+ "320 m");
assertFormatSingle(
"Compact notation with Usage: bizarre, but possible (short)",
987654321L,
"988K km");
+ assertFormatSingle(
+ "Compact notation with Usage: bizarre, but possible (short, precision override)",
+ "compact-short usage/road measure-unit/length-meter @#",
+ "compact-short usage/road unit/meter @#",
+ NumberFormatter.with()
+ .unit(MeasureUnit.METER)
+ .usage("road")
+ .notation(Notation.compactShort())
+ .precision(Precision.maxSignificantDigits(2)),
+ new ULocale("en-ZA"),
+ 987654321L,
+ "990K km");
+
+ assertFormatSingle(
+ "Compact notation with Usage: unusual but possible (long)",
+ "compact-long usage/road measure-unit/length-meter @#",
+ "compact-long usage/road unit/meter @#",
+ NumberFormatter.with()
+ .unit(MeasureUnit.METER)
+ .usage("road")
+ .notation(Notation.compactLong())
+ .precision(Precision.maxSignificantDigits(2)),
+ new ULocale("en-ZA"),
+ 987654321,
+ "990 thousand km");
+
+ assertFormatSingle(
+ "Compact notation with Usage: unusual but possible (long, precision override)",
+ "compact-long usage/road measure-unit/length-meter @#",
+ "compact-long usage/road unit/meter @#",
+ NumberFormatter.with()
+ .unit(MeasureUnit.METER)
+ .usage("road")
+ .notation(Notation.compactLong())
+ .precision(Precision.maxSignificantDigits(2)),
+ new ULocale("en-ZA"),
+ 987654321,
+ "990 thousand km");
+ assertFormatSingle(
+ "Scientific notation, not recommended, requires precision override for road",
+ "scientific usage/road measure-unit/length-meter",
+ "scientific usage/road unit/meter",
+ NumberFormatter.with()
+ .unit(MeasureUnit.METER)
+ .usage("road")
+ .notation(Notation.scientific()),
+ new ULocale("en-ZA"),
+ 321.45,
+ // Rounding to the nearest "50" is not exponent-adjusted in scientific notation:
+ "0E2 m");
- // TODO(younies): enable override precision test cases.
-// assertFormatSingle(
-// "Compact notation with Usage: bizarre, but possible (short, precision override)",
-// "compact-short usage/road measure-unit/length-meter @#",
-// "compact-short usage/road unit/meter @#",
-// NumberFormatter.with()
-// .unit(MeasureUnit.METER)
-// .usage("road")
-// .notation(Notation.compactShort())
-// .precision(Precision.maxSignificantDigits(2)),
-// new ULocale("en-ZA"),
-// 987654321L,
-// "990K km");
-
- // TODO(younies): enable override precision test cases.
-// assertFormatSingle(
-// "Compact notation with Usage: unusual but possible (long)",
-// "compact-long usage/road measure-unit/length-meter @#",
-// "compact-long usage/road unit/meter @#",
-// NumberFormatter.with()
-// .unit(MeasureUnit.METER)
-// .usage("road")
-// .notation(Notation.compactLong())
-// .precision(Precision.maxSignificantDigits(2)),
-// new ULocale("en-ZA"),
-// 987654321,
-// "990 thousand km");
-
- // TODO(younies): enable override precision test cases.
-// assertFormatSingle(
-// "Compact notation with Usage: unusual but possible (long, precision override)",
-// "compact-long usage/road measure-unit/length-meter @#",
-// "compact-long usage/road unit/meter @#",
-// NumberFormatter.with()
-// .unit(MeasureUnit.METER)
-// .usage("road")
-// .notation(Notation.compactLong())
-// .precision(Precision.maxSignificantDigits(2)),
-// new ULocale("en-ZA"),
-// 987654321,
-// "990 thousand km");
-
- // TODO(younies): enable override precision test cases.
-// assertFormatSingle(
-// "Scientific notation, not recommended, requires precision override for road",
-// "scientific usage/road measure-unit/length-meter",
-// "scientific usage/road unit/meter",
-// NumberFormatter.with().unit(MeasureUnit.METER).usage("road").notation(Notation.scientific()),
-// new ULocale("en-ZA"),
-// 321.45,
-// // Rounding to the nearest "50" is not exponent-adjusted in scientific notation:
-// "0E2 m");
-
- // TODO(younies): enable override precision test cases.
-// assertFormatSingle(
-// "Scientific notation with Usage: possible when using a reasonable Precision",
-// "scientific usage/road measure-unit/length-meter @###",
-// "scientific usage/road unit/meter @###",
-// NumberFormatter.with()
-// .unit(MeasureUnit.METER)
-// .usage("road")
-// .notation(Notation.scientific())
-// .precision(Precision.maxSignificantDigits(4)),
-// new ULocale("en-ZA"),
-// 321.45, // 0.45 rounds down, 0.55 rounds up.
-// "3,214E2 m");
+ assertFormatSingle(
+ "Scientific notation with Usage: possible when using a reasonable Precision",
+ "scientific usage/road measure-unit/length-meter @###",
+ "scientific usage/road unit/meter @###",
+ NumberFormatter.with()
+ .unit(MeasureUnit.METER)
+ .usage("road")
+ .notation(Notation.scientific())
+ .precision(Precision.maxSignificantDigits(4)),
+ new ULocale("en-ZA"),
+ 321.45, // 0.45 rounds down, 0.55 rounds up.
+ "3,214E2 m");
assertFormatSingle(
"Scientific notation with Usage: possible when using a reasonable Precision",