ICU-20568 Add .unit().usage() support to ICU4J NumberFormatter (2/2)

* Throw UnsupportedOperationException for "unsanctioned units"
* Deal with the parent issue for LongNameMultiplexer
* Fix NullPointerException: UnitConversionHandler.processQuantity must
  call fParent.processQuantity
* toSkeleton not supported for not-built-in units
* Add and use interface
  LongNameMultiplexer.ParentlessMicroPropsGenerator
* Match up C++ and Java unit tests in NumberFormatterApiTest.java
* Permit user-override of precision() for usage(), closes icu-units#95
* Use BogusRounder to propagate mathContext.
* Port C++ change from PR #1322, commit c84ded050a, to Java.
* Test the usage-without-unit error. Document it in
  NumberFormatterSettings.java
* General review and corrections.
This commit is contained in:
Hugo van der Merwe 2020-09-16 19:39:13 +02:00
parent 7ba2b48f7b
commit e3bb5e5f07
17 changed files with 461 additions and 277 deletions

View file

@ -450,10 +450,8 @@ void MixedUnitLongNameHandler::processQuantity(DecimalQuantity &quantity, MicroP
const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &quantity,
MicroProps &micros,
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 &micros.helpers.emptyWeakModifier;
}
@ -498,6 +496,7 @@ const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &
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];
@ -515,6 +514,7 @@ const Modifier *MixedUnitLongNameHandler::getMixedUnitModifier(DecimalQuantity &
return &micros.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});

View file

@ -159,14 +159,13 @@ class MixedUnitLongNameHandler : public MicroPropsGenerator, public ModifierStor
// 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.

View file

@ -67,11 +67,11 @@ class IntMeasures : public MaybeStackArray<int64_t, 2> {
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.

View file

@ -103,6 +103,8 @@ void Usage::set(StringPiece value) {
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;
@ -159,13 +161,13 @@ void UsagePrefsHandler::processQuantity(DecimalQuantity &quantity, MicroProps &m
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, &micros, status);
mixedMeasuresToMicros(routedMeasures, &quantity, &micros, status);
UnicodeString precisionSkeleton = routed.precision;
if (micros.rounder.fPrecision.isBogus()) {

View file

@ -906,6 +906,11 @@ void NumberFormatterApiTest::unitUsage() {
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";
@ -1113,25 +1118,49 @@ void NumberFormatterApiTest::unitUsage() {
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() {

View file

@ -22,11 +22,12 @@ import com.ibm.icu.util.MeasureUnit;
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"
@ -39,7 +40,7 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
}
}
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()];
@ -79,7 +80,7 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
// 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,
@ -199,13 +200,13 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
* <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,
@ -214,6 +215,12 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
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);
@ -314,6 +321,20 @@ public class LongNameHandler implements MicroPropsGenerator, ModifierStore {
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

View file

@ -1,7 +1,5 @@
// © 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;
@ -13,10 +11,27 @@ import com.ibm.icu.util.ULocale;
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.
@ -50,7 +65,7 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
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);
}
}
@ -62,7 +77,6 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
// 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!
@ -70,12 +84,11 @@ public class LongNameMultiplexer implements MicroPropsGenerator {
// 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");
}

View file

@ -12,11 +12,11 @@ import com.ibm.icu.util.MeasureUnit;
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;
@ -55,7 +55,7 @@ public class MicroProps implements Cloneable, MicroPropsGenerator {
// 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;
@ -79,17 +79,19 @@ public class MicroProps implements Cloneable, MicroPropsGenerator {
* 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

View file

@ -19,6 +19,7 @@ package com.ibm.icu.impl.number;
* {@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.
*

View file

@ -1,10 +1,9 @@
// © 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;
@ -13,26 +12,28 @@ import com.ibm.icu.text.PluralRules;
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) {
@ -49,8 +50,8 @@ public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierSt
* @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,
@ -89,43 +90,61 @@ public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierSt
/**
* 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
@ -153,30 +172,30 @@ public class MixedUnitLongNameHandler implements MicroPropsGenerator, ModifierSt
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);
}
}

View file

@ -1,6 +1,5 @@
// © 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;
@ -13,8 +12,9 @@ import java.util.ArrayList;
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 {
@ -22,22 +22,23 @@ 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());
}
/**
@ -45,16 +46,12 @@ public class UnitConversionHandler implements MicroPropsGenerator {
*/
@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;

View file

@ -1,6 +1,5 @@
// © 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;
@ -47,15 +46,18 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
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.
@ -95,7 +97,6 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
final List<Measure> routedMeasures = routed.measures;
micros.outputUnit = routed.outputUnit.build();
micros.mixedMeasures = new ArrayList<>();
UsagePrefsHandler.mixedMeasuresToMicros(routedMeasures, quantity, micros);
@ -103,14 +104,16 @@ public class UsagePrefsHandler implements MicroPropsGenerator {
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;

View file

@ -210,10 +210,9 @@ class NumberFormatterImpl {
|| !(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.
@ -275,7 +274,9 @@ class NumberFormatterImpl {
}
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
@ -290,6 +291,9 @@ class NumberFormatterImpl {
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;
}

View file

@ -44,8 +44,8 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
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;
@ -134,7 +134,7 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
* <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>
@ -492,44 +492,57 @@ public abstract class NumberFormatterSettings<T extends NumberFormatterSettings<
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) {

View file

@ -1468,10 +1468,13 @@ class NumberSkeletonImpl {
} 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();
}
}

View file

@ -20,7 +20,6 @@ import com.ibm.icu.util.Currency.CurrencyUsage;
*
* @stable ICU 62
* @see NumberFormatter
* @internal
*/
public abstract class Precision {
@ -366,6 +365,13 @@ 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);
@ -545,6 +551,40 @@ public abstract class Precision {
// 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() {

View file

@ -21,6 +21,7 @@ import org.junit.Test;
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;
@ -644,6 +645,17 @@ public class NumberFormatterApiTest extends TestFmwk {
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",
@ -738,6 +750,17 @@ public class NumberFormatterApiTest extends TestFmwk {
"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",
@ -770,16 +793,22 @@ public class NumberFormatterApiTest extends TestFmwk {
"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.
@ -839,6 +868,13 @@ public class NumberFormatterApiTest extends TestFmwk {
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";
@ -881,15 +917,10 @@ public class NumberFormatterApiTest extends TestFmwk {
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},
@ -919,15 +950,10 @@ public class NumberFormatterApiTest extends TestFmwk {
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},
@ -958,15 +984,10 @@ public class NumberFormatterApiTest extends TestFmwk {
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)
@ -1053,29 +1074,51 @@ public class NumberFormatterApiTest extends TestFmwk {
"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
@ -1122,18 +1165,17 @@ public class NumberFormatterApiTest extends TestFmwk {
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)",
@ -1147,74 +1189,70 @@ public class NumberFormatterApiTest extends TestFmwk {
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");
// 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");
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(
// "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(
"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: 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",