UnicodeString& DecimalFormat::getPositivePrefix(UnicodeString& result) const {
ErrorCode localStatus;
- result = fFormatter->formatInt(1, localStatus).getPrefix(localStatus);
+ fFormatter->getAffix(true, false, result, localStatus);
return result;
}
UnicodeString& DecimalFormat::getNegativePrefix(UnicodeString& result) const {
ErrorCode localStatus;
- result = fFormatter->formatInt(-1, localStatus).getPrefix(localStatus);
+ fFormatter->getAffix(true, true, result, localStatus);
return result;
}
UnicodeString& DecimalFormat::getPositiveSuffix(UnicodeString& result) const {
ErrorCode localStatus;
- result = fFormatter->formatInt(1, localStatus).getSuffix(localStatus);
+ fFormatter->getAffix(false, false, result, localStatus);
return result;
}
UnicodeString& DecimalFormat::getNegativeSuffix(UnicodeString& result) const {
ErrorCode localStatus;
- result = fFormatter->formatInt(-1, localStatus).getSuffix(localStatus);
+ fFormatter->getAffix(false, true, result, localStatus);
return result;
}
}
UnicodeString DecimalFormat::getPadCharacterString() const {
- return fProperties->padString;
+ if (fProperties->padString.isBogus()) {
+ // Readonly-alias the static string kFallbackPaddingString
+ return {TRUE, kFallbackPaddingString, -1};
+ } else {
+ return fProperties->padString;
+ }
}
void DecimalFormat::setPadCharacter(const UnicodeString& padChar) {
// Older ICUs called uprv_decNumberToString here, which is not exactly the same as
// DecimalQuantity::toScientificString(). The biggest difference is that uprv_decNumberToString does
// not print scientific notation for magnitudes greater than -5 and smaller than some amount (+5?).
- if (std::abs(fDecimalQuantity->getMagnitude()) < 5) {
+ if (fDecimalQuantity->isZero()) {
+ fDecimalStr->append("0", -1, status);
+ } else if (std::abs(fDecimalQuantity->getMagnitude()) < 5) {
fDecimalStr->appendInvariantChars(fDecimalQuantity->toPlainString(), status);
} else {
fDecimalStr->appendInvariantChars(fDecimalQuantity->toScientificString(), status);
}
}
+void LocalizedNumberFormatter::getAffix(bool isPrefix, bool isNegative, UnicodeString& result,
+ UErrorCode& status) const {
+ NumberStringBuilder nsb;
+ DecimalQuantity dq;
+ if (isNegative) {
+ dq.setToInt(-1);
+ } else {
+ dq.setToInt(1);
+ }
+ int prefixLength = NumberFormatterImpl::getPrefixSuffix(fMacros, dq, nsb, status);
+ result.remove();
+ if (isPrefix) {
+ result.append(nsb.toTempUnicodeString().tempSubStringBetween(0, prefixLength));
+ } else {
+ result.append(nsb.toTempUnicodeString().tempSubStringBetween(prefixLength, nsb.length()));
+ }
+}
+
const impl::NumberFormatterImpl* LocalizedNumberFormatter::getCompiled() const {
return fCompiled;
}
output = fResults->quantity;
}
-const UnicodeString FormattedNumber::getPrefix(UErrorCode& status) const {
- if (fResults == nullptr) {
- status = fErrorCode;
- return {};
- }
- // FIXME
- return {};
-}
-
-const UnicodeString FormattedNumber::getSuffix(UErrorCode& status) const {
- if (fResults == nullptr) {
- status = fErrorCode;
- return {};
- }
- // FIXME
- return {};
-}
-
FormattedNumber::~FormattedNumber() {
delete fResults;
}
impl.applyUnsafe(inValue, outString, status);
}
+int32_t NumberFormatterImpl::getPrefixSuffix(const MacroProps& macros, DecimalQuantity& inValue,
+ NumberStringBuilder& outString, UErrorCode& status) {
+ NumberFormatterImpl impl(macros, false, status);
+ return impl.getPrefixSuffixUnsafe(inValue, outString, status);
+}
+
// NOTE: C++ SPECIFIC DIFFERENCE FROM JAVA:
// The "safe" apply method uses a new MicroProps. In the MicroPropsGenerator, fMicros is copied into the new instance.
// The "unsafe" method simply re-uses fMicros, eliminating the extra copy operation.
microsToString(fMicros, inValue, outString, status);
}
+int32_t
+NumberFormatterImpl::getPrefixSuffixUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString,
+ UErrorCode& status) {
+ if (U_FAILURE(status)) { return 0; }
+ fMicroPropsGenerator->processQuantity(inValue, fMicros, status);
+ if (U_FAILURE(status)) { return 0; }
+ // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle).
+ fMicros.modMiddle->apply(outString, 0, 0, status);
+ if (U_FAILURE(status)) { return 0; }
+ return fMicros.modMiddle->getPrefixLength(status);
+}
+
NumberFormatterImpl::NumberFormatterImpl(const MacroProps& macros, bool safe, UErrorCode& status) {
fMicroPropsGenerator = macrosToMicroGenerator(macros, safe, status);
}
applyStatic(const MacroProps ¯os, DecimalQuantity &inValue, NumberStringBuilder &outString,
UErrorCode &status);
+ /**
+ * Prints only the prefix and suffix; used for DecimalFormat getters.
+ *
+ * @return The index into the output at which the prefix ends and the suffix starts; in other words,
+ * the prefix length.
+ */
+ static int32_t getPrefixSuffix(const MacroProps& macros, DecimalQuantity& inValue,
+ NumberStringBuilder& outString, UErrorCode& status);
+
/**
* Evaluates the "safe" MicroPropsGenerator created by "fromMacros".
*/
void applyUnsafe(DecimalQuantity &inValue, NumberStringBuilder &outString, UErrorCode &status);
+ int32_t getPrefixSuffixUnsafe(DecimalQuantity& inValue, NumberStringBuilder& outString,
+ UErrorCode& status);
+
/**
* If rulesPtr is non-null, return it. Otherwise, return a PluralRules owned by this object for the
* specified locale, creating it if necessary.
namespace {
-static UChar32 kFallbackPadChar = 0x0020;
-
int32_t
addPaddingHelper(UChar32 paddingCp, int32_t requiredPadding, NumberStringBuilder &string, int32_t index,
UErrorCode &status) {
if (properties.padString.length() > 0) {
padCp = properties.padString.char32At(0);
} else {
- padCp = kFallbackPadChar;
+ padCp = kFallbackPaddingString[0];
}
return {padCp, properties.formatWidth, properties.padPosition.getOrDefault(UNUM_PAD_BEFORE_PREFIX)};
}
#if U_PLATFORM == U_PF_OS400
#define UNUM_INTERNAL_STACKARRAY_SIZE 144
#else
-#define UNUM_INTERNAL_STACKARRAY_SIZE 128
+#define UNUM_INTERNAL_STACKARRAY_SIZE 80
#endif
/**
*/
FormattedNumber formatDecimalQuantity(const impl::DecimalQuantity& dq, UErrorCode& status) const;
+ /** Internal method for DecimalFormat compatibility.
+ * @internal
+ */
+ void getAffix(bool isPrefix, bool isNegative, UnicodeString& result, UErrorCode& status) const;
+
/**
* Internal method for testing.
* @internal
*
* @param results
* The results object. This method will mutate it to save the results.
+ * @internal
*/
void formatImpl(impl::UFormattedNumberData *results, UErrorCode &status) const;
*/
void getDecimalQuantity(impl::DecimalQuantity& output, UErrorCode& status) const;
- /** @internal */
- const UnicodeString getPrefix(UErrorCode& status) const;
-
- /** @internal */
- const UnicodeString getSuffix(UErrorCode& status) const;
-
#endif
// Don't allow copying of FormattedNumber, but moving is okay.
TESTCASE_AUTO(TestNumberFormatTestTuple);
TESTCASE_AUTO(TestDataDriven);
TESTCASE_AUTO(TestDoubleLimit11439);
- TESTCASE_AUTO(TestFastPathConsistent11524);
TESTCASE_AUTO(TestGetAffixes);
TESTCASE_AUTO(TestToPatternScientific11648);
TESTCASE_AUTO(TestBenchmark);
if (U_FAILURE(status) ||n.getType() != Formattable::kDouble ||
n.getDouble() != 0.25) {
errln((UnicodeString)"Lenient parse failed for \"" + (UnicodeString) lenientPercentTestCases[t]
- + (UnicodeString) "\"; error code = " + u_errorName(status));
+ + (UnicodeString) "\"; error code = " + u_errorName(status)
+ + "; got: " + n.getDouble(status));
status = U_ZERO_ERROR;
}
}
if (U_FAILURE(status) ||n.getType() != Formattable::kDouble ||
n.getDouble() != -0.25) {
errln((UnicodeString)"Lenient parse failed for \"" + (UnicodeString) lenientNegativePercentTestCases[t]
- + (UnicodeString) "\"; error code = " + u_errorName(status));
+ + (UnicodeString) "\"; error code = " + u_errorName(status)
+ + "; got: " + n.getDouble(status));
status = U_ZERO_ERROR;
}
}
return;
}
- if (fmt.getPadCharacterString() != UnicodeString("a")) {
- errln("Padding character should be 'a'.");
- return;
- }
+ assertEquals("Padding character should be 'a'.", u"a", fmt.getPadCharacterString());
// Padding char of fmt ought to be '*' since that is the default and no
// explicit padding char is specified in the new pattern.
fmt.applyPattern("AA#,##0.00ZZ", status);
// Oops this still prints 'a' even though we changed the pattern.
- if (fmt.getPadCharacterString() != UnicodeString(" ")) {
- errln("applyPattern did not clear padding character.");
- }
+ assertEquals("applyPattern did not clear padding character.", u" ", fmt.getPadCharacterString());
}
void NumberFormatTest::TestRoundingScientific10542() {
UnicodeString original;
fmt->format(agent,original);
- assertEquals("Test Currency Usage 1", UnicodeString("PKR124"), original);
+ assertEquals("Test Currency Usage 1", UnicodeString("PKR\u00A0124"), original);
// test the getter here
UCurrencyUsage curUsage = fmt->getCurrencyUsage();
UnicodeString cash_currency;
fmt->format(agent,cash_currency);
- assertEquals("Test Currency Usage 2", UnicodeString("PKR124"), cash_currency);
+ assertEquals("Test Currency Usage 2", UnicodeString("PKR\u00A0124"), cash_currency);
delete fmt;
}
UnicodeString PKR_changed;
fmt->format(agent, PKR_changed);
- assertEquals("Test Currency Usage 6", UnicodeString("PKR124"), PKR_changed);
+ assertEquals("Test Currency Usage 6", UnicodeString("PKR\u00A0124"), PKR_changed);
delete fmt;
}
}
}
}
-void NumberFormatTest::TestFastPathConsistent11524() {
- UErrorCode status = U_ZERO_ERROR;
- NumberFormat *fmt = NumberFormat::createInstance("en", status);
- if (U_FAILURE(status) || fmt == NULL) {
- dataerrln("Failed call to NumberFormat::createInstance() - %s", u_errorName(status));
- return;
- }
- fmt->setMaximumIntegerDigits(INT32_MIN);
- UnicodeString appendTo;
- assertEquals("", "0", fmt->format((int32_t)123, appendTo));
- appendTo.remove();
- assertEquals("", "0", fmt->format((int32_t)12345, appendTo));
- delete fmt;
-}
-
void NumberFormatTest::TestGetAffixes() {
UErrorCode status = U_ZERO_ERROR;
DecimalFormatSymbols sym("en_US", status);
-1E-99999999999999 -0.0
1E2147483648 Inf K
1E2147483647 Inf K
-1E2147483646 1E2147483646
+1E2147483646 1E+2147483646
1E-2147483649 0
1E-2147483648 0
// C and P return zero here
import java.util.Arrays;
import com.ibm.icu.impl.number.DecimalQuantity;
-import com.ibm.icu.impl.number.MicroProps;
import com.ibm.icu.impl.number.NumberStringBuilder;
import com.ibm.icu.text.PluralRules.IFixedDecimal;
import com.ibm.icu.util.ICUUncheckedIOException;
* @see NumberFormatter
*/
public class FormattedNumber {
- NumberStringBuilder nsb;
- DecimalQuantity fq;
- MicroProps micros;
+ final NumberStringBuilder nsb;
+ final DecimalQuantity fq;
- FormattedNumber(NumberStringBuilder nsb, DecimalQuantity fq, MicroProps micros) {
+ FormattedNumber(NumberStringBuilder nsb, DecimalQuantity fq) {
this.nsb = nsb;
this.fq = fq;
- this.micros = micros;
}
/**
return fq.toBigDecimal();
}
- /**
- * @internal
- * @deprecated This API is ICU internal only. Use {@link #populateFieldPosition} or
- * {@link #getFieldIterator} for similar functionality.
- */
- @Deprecated
- public String getPrefix() {
- NumberStringBuilder temp = new NumberStringBuilder();
- // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle).
- micros.modMiddle.apply(temp, 0, 0);
- int prefixLength = micros.modMiddle.getPrefixLength();
- return temp.subSequence(0, prefixLength).toString();
- }
-
- /**
- * @internal
- * @deprecated This API is ICU internal only. Use {@link #populateFieldPosition} or
- * {@link #getFieldIterator} for similar functionality.
- */
- @Deprecated
- public String getSuffix() {
- NumberStringBuilder temp = new NumberStringBuilder();
- // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle).
- int length = micros.modMiddle.apply(temp, 0, 0);
- int prefixLength = micros.modMiddle.getPrefixLength();
- return temp.subSequence(prefixLength, length).toString();
- }
-
/**
* @internal
* @deprecated This API is ICU internal only.
import com.ibm.icu.impl.number.DecimalQuantity;
import com.ibm.icu.impl.number.DecimalQuantity_DualStorageBCD;
import com.ibm.icu.impl.number.MacroProps;
-import com.ibm.icu.impl.number.MicroProps;
import com.ibm.icu.impl.number.NumberStringBuilder;
import com.ibm.icu.math.BigDecimal;
import com.ibm.icu.util.CurrencyAmount;
// Further benchmarking is required.
long currentCount = callCount.incrementAndGet(this);
NumberStringBuilder string = new NumberStringBuilder();
- MicroProps micros;
if (currentCount == macros.threshold.longValue()) {
compiled = NumberFormatterImpl.fromMacros(macros);
- micros = compiled.apply(fq, string);
+ compiled.apply(fq, string);
} else if (compiled != null) {
- micros = compiled.apply(fq, string);
+ compiled.apply(fq, string);
} else {
- micros = NumberFormatterImpl.applyStatic(macros, fq, string);
+ NumberFormatterImpl.applyStatic(macros, fq, string);
+ }
+ return new FormattedNumber(string, fq);
+ }
+
+ /**
+ * @internal
+ * @deprecated This API is ICU internal only. Use {@link FormattedNumber#populateFieldPosition} or
+ * {@link FormattedNumber#getFieldIterator} for similar functionality.
+ */
+ @Deprecated
+ public String getAffix(boolean isPrefix, boolean isNegative) {
+ MacroProps macros = resolve();
+ NumberStringBuilder nsb = new NumberStringBuilder();
+ DecimalQuantity dq = new DecimalQuantity_DualStorageBCD(isNegative ? -1 : 1);
+ int prefixLength = NumberFormatterImpl.getPrefixSuffix(macros, dq, nsb);
+ if (isPrefix) {
+ return nsb.subSequence(0, prefixLength).toString();
+ } else {
+ return nsb.subSequence(prefixLength, nsb.length()).toString();
}
- return new FormattedNumber(string, fq, micros);
}
@Override
/**
* Builds and evaluates an "unsafe" MicroPropsGenerator, which is cheaper but can be used only once.
*/
- public static MicroProps applyStatic(
+ public static void applyStatic(
MacroProps macros,
DecimalQuantity inValue,
NumberStringBuilder outString) {
MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
MicroProps micros = microPropsGenerator.processQuantity(inValue);
microsToString(micros, inValue, outString);
- return micros;
+ }
+
+ /**
+ * Prints only the prefix and suffix; used for DecimalFormat getters.
+ *
+ * @return The index into the output at which the prefix ends and the suffix starts; in other words,
+ * the prefix length.
+ */
+ public static int getPrefixSuffix(
+ MacroProps macros,
+ DecimalQuantity inValue,
+ NumberStringBuilder output) {
+ MicroPropsGenerator microPropsGenerator = macrosToMicroGenerator(macros, false);
+ MicroProps micros = microPropsGenerator.processQuantity(inValue);
+ // #13453: DecimalFormat wants the affixes from the pattern only (modMiddle).
+ micros.modMiddle.apply(output, 0, 0);
+ return micros.modMiddle.getPrefixLength();
}
private static final Currency DEFAULT_CURRENCY = Currency.getInstance("XXX");
this.microPropsGenerator = microPropsGenerator;
}
- public MicroProps apply(DecimalQuantity inValue, NumberStringBuilder outString) {
+ public void apply(DecimalQuantity inValue, NumberStringBuilder outString) {
MicroProps micros = microPropsGenerator.processQuantity(inValue);
microsToString(micros, inValue, outString);
- return micros;
}
//////////
import com.ibm.icu.impl.number.AffixUtils;
import com.ibm.icu.impl.number.DecimalFormatProperties;
import com.ibm.icu.impl.number.DecimalFormatProperties.ParseMode;
+import com.ibm.icu.impl.number.Padder;
import com.ibm.icu.impl.number.Padder.PadPosition;
import com.ibm.icu.impl.number.PatternStringParser;
import com.ibm.icu.impl.number.PatternStringUtils;
* @stable ICU 2.0
*/
public synchronized String getPositivePrefix() {
- return formatter.format(1).getPrefix();
+ return formatter.getAffix(true, false);
}
/**
* @stable ICU 2.0
*/
public synchronized String getNegativePrefix() {
- return formatter.format(-1).getPrefix();
+ return formatter.getAffix(true, true);
}
/**
* @stable ICU 2.0
*/
public synchronized String getPositiveSuffix() {
- return formatter.format(1).getSuffix();
+ return formatter.getAffix(false, false);
}
/**
* @stable ICU 2.0
*/
public synchronized String getNegativeSuffix() {
- return formatter.format(-1).getSuffix();
+ return formatter.getAffix(false, true);
}
/**
public synchronized char getPadCharacter() {
CharSequence paddingString = properties.getPadString();
if (paddingString == null) {
- return '.'; // TODO: Is this the correct behavior?
+ return Padder.FALLBACK_PADDING_STRING.charAt(0);
} else {
return paddingString.charAt(0);
}