if (isNegative()) {
sb.append(u'-');
}
+ if (precision == 0) {
+ sb.append(u'0');
+ }
for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
if (m == -1) { sb.append(u'.'); }
sb.append(getDigit(m) + u'0');
b.add(u"currency", STEM_CURRENCY, status);
b.add(u"integer-width", STEM_INTEGER_WIDTH, status);
b.add(u"numbering-system", STEM_NUMBERING_SYSTEM, status);
+ b.add(u"multiply", STEM_MULTIPLY, status);
if (U_FAILURE(status)) { return; }
// Build the CharsTrie
case STATE_CURRENCY_UNIT:
case STATE_INTEGER_WIDTH:
case STATE_NUMBERING_SYSTEM:
+ case STATE_MULTIPLY:
// segment.setLength(U16_LENGTH(cp)); // for error message
// throw new SkeletonSyntaxException("Stem requires an option", segment);
status = U_NUMBER_SKELETON_SYNTAX_ERROR;
CHECK_NULL(seen, symbols, status);
return STATE_NUMBERING_SYSTEM;
+ case STEM_MULTIPLY:
+ CHECK_NULL(seen, multiplier, status);
+ return STATE_MULTIPLY;
+
default:
U_ASSERT(false);
}
case STATE_NUMBERING_SYSTEM:
blueprint_helpers::parseNumberingSystemOption(segment, macros, status);
return STATE_NULL;
+ case STATE_MULTIPLY:
+ blueprint_helpers::parseMultiplierOption(segment, macros, status);
+ return STATE_NULL;
default:
break;
}
sb.append(u' ');
}
if (U_FAILURE(status)) { return; }
+ if (GeneratorHelpers::multiplier(macros, sb, status)) {
+ sb.append(u' ');
+ }
+ if (U_FAILURE(status)) { return; }
// Unsupported options
if (!macros.padder.isBogus()) {
status = U_UNSUPPORTED_ERROR;
return;
}
- if (macros.multiplier.isValid()) {
- status = U_UNSUPPORTED_ERROR;
- return;
- }
if (macros.rules != nullptr) {
status = U_UNSUPPORTED_ERROR;
return;
sb.append(UnicodeString(ns.getName(), -1, US_INV));
}
+void blueprint_helpers::parseMultiplierOption(const StringSegment& segment, MacroProps& macros,
+ UErrorCode& status) {
+ // Need to do char <-> UChar conversion...
+ CharString buffer;
+ SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
+
+ // Utilize DecimalQuantity/decNumber to parse this for us.
+ // TODO: Parse to a DecNumber directly.
+ DecimalQuantity dq;
+ UErrorCode localStatus = U_ZERO_ERROR;
+ dq.setToDecNumber({buffer.data(), buffer.length()}, localStatus);
+ if (U_FAILURE(localStatus)) {
+ // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e);
+ status = U_NUMBER_SKELETON_SYNTAX_ERROR;
+ return;
+ }
+ macros.multiplier = Multiplier::arbitraryDouble(dq.toDouble());
+}
+
+void blueprint_helpers::generateMultiplierOption(int32_t magnitude, double arbitrary, UnicodeString& sb,
+ UErrorCode&) {
+ // Utilize DecimalQuantity/double_conversion to format this for us.
+ DecimalQuantity dq;
+ dq.setToDouble(arbitrary);
+ dq.adjustMagnitude(magnitude);
+ dq.roundToInfinity();
+ sb.append(dq.toPlainString());
+}
+
bool GeneratorHelpers::notation(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
if (macros.notation.fType == Notation::NTN_COMPACT) {
return true;
}
+bool GeneratorHelpers::multiplier(const MacroProps& macros, UnicodeString& sb, UErrorCode& status) {
+ if (!macros.multiplier.isValid()) {
+ return false; // Default or Bogus
+ }
+ sb.append(u"multiply/", -1);
+ blueprint_helpers::generateMultiplierOption(
+ macros.multiplier.fMagnitude,
+ macros.multiplier.fArbitrary,
+ sb,
+ status);
+ return true;
+}
+
#endif /* #if !UCONFIG_NO_FORMATTING */
STATE_CURRENCY_UNIT,
STATE_INTEGER_WIDTH,
STATE_NUMBERING_SYSTEM,
+ STATE_MULTIPLY,
};
/**
STEM_CURRENCY,
STEM_INTEGER_WIDTH,
STEM_NUMBERING_SYSTEM,
+ STEM_MULTIPLY,
};
/**
void generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, UErrorCode& status);
+void parseMultiplierOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status);
+
+void generateMultiplierOption(int32_t magnitude, double arbitrary, UnicodeString& sb, UErrorCode& status);
+
} // namespace blueprint_helpers
/**
static bool decimal(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
+ static bool multiplier(const MacroProps& macros, UnicodeString& sb, UErrorCode& status);
+
};
/**
bool unitWidth = false;
bool sign = false;
bool decimal = false;
+ bool multiplier = false;
};
} // namespace impl
void NumberFormatterApiTest::multiplier() {
assertFormatDescending(
u"Multiplier None",
- u"",
+ u"multiply/1",
NumberFormatter::with().multiplier(Multiplier::none()),
Locale::getEnglish(),
u"87,650",
assertFormatDescending(
u"Multiplier Power of Ten",
- nullptr,
+ u"multiply/1000000",
NumberFormatter::with().multiplier(Multiplier::powerOfTen(6)),
Locale::getEnglish(),
u"87,650,000,000",
assertFormatDescending(
u"Multiplier Arbitrary Double",
- nullptr,
+ u"multiply/5.2",
NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(5.2)),
Locale::getEnglish(),
u"455,780",
assertFormatDescending(
u"Multiplier Arbitrary BigDecimal",
- nullptr,
+ u"multiply/5.2",
NumberFormatter::with().multiplier(Multiplier::arbitraryDecimal({"5.2", -1})),
Locale::getEnglish(),
u"455,780",
assertFormatDescending(
u"Multiplier Zero",
- nullptr,
+ u"multiply/0",
NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(0)),
Locale::getEnglish(),
u"0",
u"0",
u"0",
u"0");
+
+ assertFormatSingle(
+ u"Multiplier Skeleton Scientific Notation and Percent",
+ u"percent multiply/1E2",
+ NumberFormatter::with().unit(NoUnit::percent()).multiplier(Multiplier::powerOfTen(2)),
+ Locale::getEnglish(),
+ 0.5,
+ u"50%");
+
+ assertFormatSingle(
+ u"Negative Multiplier",
+ u"multiply/-5.2",
+ NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(-5.2)),
+ Locale::getEnglish(),
+ 2,
+ u"-10.4");
}
void NumberFormatterApiTest::locale() {
u"unit-width-hidden",
u"decimal-auto",
u"decimal-always",
+ u"multiply/5.2",
+ u"multiply/-5.2",
+ u"multiply/100",
+ u"multiply/1E2",
+ u"multiply/1",
u"latin",
u"numbering-system/arab",
u"numbering-system/latn",
u"scientific/ee",
u"round-increment/xxx",
u"round-increment/0.1.2",
+ u"multiply/xxx",
+ u"multiply/0.1.2",
+ u"multiply/français", // non-invariant characters for C++
u"currency/dummy",
u"measure-unit/foo",
u"integer-width/xxx",
}
void NumberSkeletonTest::stemsRequiringOption() {
- static const char16_t* stems[] = {u"round-increment", u"currency", u"measure-unit", u"integer-width",};
+ static const char16_t* stems[] = {
+ u"round-increment",
+ u"measure-unit",
+ u"per-unit",
+ u"currency",
+ u"integer-width",
+ u"numbering-system",
+ u"multiply"};
static const char16_t* suffixes[] = {u"", u"/ceiling", u" scientific", u"/ceiling scientific"};
for (auto& stem : stems) {
if (isNegative()) {
sb.append('-');
}
+ if (precision == 0) {
+ sb.append('0');
+ }
for (int m = getUpperDisplayMagnitude(); m >= getLowerDisplayMagnitude(); m--) {
sb.append(getDigit(m));
if (m == 0)
package com.ibm.icu.number;
import java.math.BigDecimal;
+import java.math.BigInteger;
import java.math.MathContext;
import com.ibm.icu.impl.number.DecimalQuantity;
}
private Multiplier(int magnitude, BigDecimal arbitrary, MathContext mc) {
+ if (arbitrary != null) {
+ // Attempt to convert the BigDecimal to a magnitude multiplier.
+ arbitrary = arbitrary.stripTrailingZeros();
+ if (arbitrary.precision() == 1 && arbitrary.unscaledValue().equals(BigInteger.ONE)) {
+ // Success!
+ magnitude = -arbitrary.scale();
+ arbitrary = null;
+ }
+ }
+
this.magnitude = magnitude;
this.arbitrary = arbitrary;
this.mc = mc;
}
}
+ /**
+ * Returns whether the multiplier will change the number.
+ */
+ boolean isValid() {
+ return magnitude != 0 || arbitrary != null;
+ }
+
/**
* @internal
* @deprecated ICU 62 This API is ICU internal only.
STATE_CURRENCY_UNIT,
STATE_INTEGER_WIDTH,
STATE_NUMBERING_SYSTEM,
+ STATE_MULTIPLY,
}
/**
STEM_CURRENCY,
STEM_INTEGER_WIDTH,
STEM_NUMBERING_SYSTEM,
+ STEM_MULTIPLY,
};
/** For mapping from ordinal back to StemEnum in Java. */
b.add("currency", StemEnum.STEM_CURRENCY.ordinal());
b.add("integer-width", StemEnum.STEM_INTEGER_WIDTH.ordinal());
b.add("numbering-system", StemEnum.STEM_NUMBERING_SYSTEM.ordinal());
+ b.add("multiply", StemEnum.STEM_MULTIPLY.ordinal());
// Build the CharsTrie
// TODO: Use SLOW or FAST here?
case STATE_CURRENCY_UNIT:
case STATE_INTEGER_WIDTH:
case STATE_NUMBERING_SYSTEM:
+ case STATE_MULTIPLY:
segment.setLength(Character.charCount(cp)); // for error message
throw new SkeletonSyntaxException("Stem requires an option", segment);
default:
checkNull(macros.symbols, segment);
return ParseState.STATE_NUMBERING_SYSTEM;
+ case STEM_MULTIPLY:
+ checkNull(macros.multiplier, segment);
+ return ParseState.STATE_MULTIPLY;
+
default:
throw new AssertionError();
}
case STATE_NUMBERING_SYSTEM:
BlueprintHelpers.parseNumberingSystemOption(segment, macros);
return ParseState.STATE_NULL;
+ case STATE_MULTIPLY:
+ BlueprintHelpers.parseMultiplierOption(segment, macros);
+ return ParseState.STATE_NULL;
default:
break;
}
if (macros.decimal != null && GeneratorHelpers.decimal(macros, sb)) {
sb.append(' ');
}
+ if (macros.multiplier != null && GeneratorHelpers.multiplier(macros, sb)) {
+ sb.append(' ');
+ }
// Unsupported options
if (macros.padder != null) {
throw new UnsupportedOperationException(
"Cannot generate number skeleton with custom affix provider");
}
- if (macros.multiplier != null) {
- throw new UnsupportedOperationException(
- "Cannot generate number skeleton with custom multiplier");
- }
if (macros.rules != null) {
throw new UnsupportedOperationException(
"Cannot generate number skeleton with custom plural rules");
private static void generateNumberingSystemOption(NumberingSystem ns, StringBuilder sb) {
sb.append(ns.getName());
}
+
+ private static void parseMultiplierOption(StringSegment segment, MacroProps macros) {
+ // Call segment.subSequence() because segment.toString() doesn't create a clean string.
+ String str = segment.subSequence(0, segment.length()).toString();
+ BigDecimal bd;
+ try {
+ bd = new BigDecimal(str);
+ } catch (NumberFormatException e) {
+ throw new SkeletonSyntaxException("Invalid multiplier", segment, e);
+ }
+ // NOTE: If bd is a power of ten, the Multiplier API optimizes it for us.
+ macros.multiplier = Multiplier.arbitrary(bd);
+ }
+
+ private static void generateMultiplierOption(Multiplier multiplier, StringBuilder sb) {
+ BigDecimal bd = multiplier.arbitrary;
+ if (bd == null) {
+ bd = BigDecimal.ONE;
+ }
+ bd = bd.scaleByPowerOfTen(multiplier.magnitude);
+ sb.append(bd.toPlainString());
+ }
}
///// STEM GENERATION HELPER FUNCTIONS /////
return true;
}
+ private static boolean multiplier(MacroProps macros, StringBuilder sb) {
+ if (!macros.multiplier.isValid()) {
+ return false; // Default value
+ }
+ sb.append("multiply/");
+ BlueprintHelpers.generateMultiplierOption(macros.multiplier, sb);
+ return true;
+ }
+
}
///// OTHER UTILITY FUNCTIONS /////
public void multiplier() {
assertFormatDescending(
"Multiplier None",
- null,
+ "multiply/1",
NumberFormatter.with().multiplier(Multiplier.none()),
ULocale.ENGLISH,
"87,650",
assertFormatDescending(
"Multiplier Power of Ten",
- null,
+ "multiply/1000000",
NumberFormatter.with().multiplier(Multiplier.powerOfTen(6)),
ULocale.ENGLISH,
"87,650,000,000",
assertFormatDescending(
"Multiplier Arbitrary Double",
- null,
+ "multiply/5.2",
NumberFormatter.with().multiplier(Multiplier.arbitrary(5.2)),
ULocale.ENGLISH,
"455,780",
assertFormatDescending(
"Multiplier Arbitrary BigDecimal",
- null,
+ "multiply/5.2",
NumberFormatter.with().multiplier(Multiplier.arbitrary(new BigDecimal("5.2"))),
ULocale.ENGLISH,
"455,780",
assertFormatDescending(
"Multiplier Zero",
- null,
+ "multiply/0",
NumberFormatter.with().multiplier(Multiplier.arbitrary(0)),
ULocale.ENGLISH,
"0",
"0",
"0",
"0");
+
+ assertFormatSingle(
+ "Multiplier Skeleton Scientific Notation and Percent",
+ "percent multiply/1E2",
+ NumberFormatter.with().unit(NoUnit.PERCENT).multiplier(Multiplier.powerOfTen(2)),
+ ULocale.ENGLISH,
+ 0.5,
+ "50%");
+
+ assertFormatSingle(
+ "Negative Multiplier",
+ "multiply/-5.2",
+ NumberFormatter.with().multiplier(Multiplier.arbitrary(-5.2)),
+ ULocale.ENGLISH,
+ 2,
+ "-10.4");
}
@Test
"unit-width-hidden",
"decimal-auto",
"decimal-always",
+ "multiply/5.2",
+ "multiply/-5.2",
+ "multiply/100",
+ "multiply/1E2",
+ "multiply/1",
"latin",
"numbering-system/arab",
"numbering-system/latn",
"scientific/ee",
"round-increment/xxx",
"round-increment/0.1.2",
+ "multiply/xxx",
+ "multiply/0.1.2",
+ "multiply/français", // non-invariant characters for C++
"currency/dummy",
"measure-unit/foo",
"integer-width/xxx",
@Test
public void stemsRequiringOption() {
- String[] stems = { "round-increment", "currency", "measure-unit", "integer-width", };
+ String[] stems = {
+ "round-increment",
+ "measure-unit",
+ "per-unit",
+ "currency",
+ "integer-width",
+ "numbering-system",
+ "multiply" };
String[] suffixes = { "", "/ceiling", " scientific", "/ceiling scientific" };
for (String stem : stems) {