From: Shane Carr Date: Thu, 5 Apr 2018 21:54:04 +0000 (+0000) Subject: ICU-13678 Changing Multiplier to use decNumber instead of double, in order to fix... X-Git-Tag: release-62-rc~200^2~53 X-Git-Url: https://granicus.if.org/sourcecode?a=commitdiff_plain;h=fe0725cd2ae9cc5509be39191420af204205fc78;p=icu ICU-13678 Changing Multiplier to use decNumber instead of double, in order to fix some unit tests. Refactored call sites to use a common DecNum wrapper class with constructors for string, double, and BCD. X-SVN-Rev: 41198 --- diff --git a/icu4c/source/i18n/Makefile.in b/icu4c/source/i18n/Makefile.in index 44236b50474..b4fafdf72f8 100644 --- a/icu4c/source/i18n/Makefile.in +++ b/icu4c/source/i18n/Makefile.in @@ -102,7 +102,7 @@ number_affixutils.o number_compact.o number_decimalquantity.o \ number_decimfmtprops.o number_fluent.o number_formatimpl.o number_grouping.o \ number_integerwidth.o number_longnames.o number_modifiers.o number_notation.o \ number_padding.o number_patternmodifier.o number_patternstring.o \ -number_rounding.o number_scientific.o number_stringbuilder.o \ +number_rounding.o number_scientific.o number_stringbuilder.o number_utils.o \ number_mapper.o number_multiplier.o number_currencysymbols.o number_skeletons.o number_capi.o \ double-conversion.o double-conversion-bignum-dtoa.o double-conversion-bignum.o \ double-conversion-cached-powers.o double-conversion-diy-fp.o \ diff --git a/icu4c/source/i18n/fmtable.cpp b/icu4c/source/i18n/fmtable.cpp index e4c119aea67..ac2411a20b4 100644 --- a/icu4c/source/i18n/fmtable.cpp +++ b/icu4c/source/i18n/fmtable.cpp @@ -28,7 +28,6 @@ #include "charstr.h" #include "cmemory.h" #include "cstring.h" -#include "decNumber.h" #include "fmtableimp.h" #include "number_decimalquantity.h" @@ -463,12 +462,12 @@ Formattable::getInt64(UErrorCode& status) const status = U_INVALID_FORMAT_ERROR; return U_INT64_MIN; } else if (fabs(fValue.fDouble) > U_DOUBLE_MAX_EXACT_INT && fDecimalQuantity != NULL) { - int64_t val = fDecimalQuantity->toLong(); - if (val != 0) { - return val; + if (fDecimalQuantity->fitsInLong()) { + return fDecimalQuantity->toLong(); + } else if (fDecimalQuantity->isNegative()) { + return U_INT64_MIN; } else { - status = U_INVALID_FORMAT_ERROR; - return fValue.fDouble > 0 ? U_INT64_MAX : U_INT64_MIN; + return U_INT64_MAX; } } else { return (int64_t)fValue.fDouble; diff --git a/icu4c/source/i18n/nfsubs.cpp b/icu4c/source/i18n/nfsubs.cpp index 81aa2e5ffdc..0911ac0887f 100644 --- a/icu4c/source/i18n/nfsubs.cpp +++ b/icu4c/source/i18n/nfsubs.cpp @@ -20,6 +20,7 @@ #include "nfsubs.h" #include "fmtableimp.h" +#include "putilimp.h" #include "number_decimalquantity.h" #if U_HAVE_RBNF diff --git a/icu4c/source/i18n/number_decimalquantity.cpp b/icu4c/source/i18n/number_decimalquantity.cpp index f092bc10e4d..c39a998f23f 100644 --- a/icu4c/source/i18n/number_decimalquantity.cpp +++ b/icu4c/source/i18n/number_decimalquantity.cpp @@ -8,15 +8,14 @@ #include "uassert.h" #include #include "cmemory.h" -#include "decNumber.h" #include +#include "putilimp.h" #include "number_decimalquantity.h" -#include "decContext.h" -#include "decNumber.h" #include "number_roundingutils.h" #include "double-conversion.h" #include "unicode/plurrule.h" #include "charstr.h" +#include "number_utils.h" using namespace icu; using namespace icu::number; @@ -31,38 +30,6 @@ int8_t NEGATIVE_FLAG = 1; int8_t INFINITY_FLAG = 2; int8_t NAN_FLAG = 4; -static constexpr int32_t DEFAULT_DIGITS = 34; -typedef MaybeStackHeaderAndArray DecNumberWithStorage; - -/** Helper function to convert a decNumber-compatible string into a decNumber. */ -void stringToDecNumber(StringPiece n, DecNumberWithStorage &dn, UErrorCode& status) { - decContext set; - uprv_decContextDefault(&set, DEC_INIT_BASE); - uprv_decContextSetRounding(&set, DEC_ROUND_HALF_EVEN); - set.traps = 0; // no traps, thank you (what does this mean?) - if (n.length() > DEFAULT_DIGITS) { - dn.resize(n.length(), 0); - set.digits = n.length(); - } else { - set.digits = DEFAULT_DIGITS; - } - - // Make sure that the string is NUL-terminated; CharString guarantees this, but not StringPiece. - CharString cs(n, status); - if (U_FAILURE(status)) { return; } - - static_assert(DECDPUN == 1, "Assumes that DECDPUN is set to 1"); - uprv_decNumberFromString(dn.getAlias(), cs.data(), &set); - - // Check for invalid syntax and set the corresponding error code. - if ((set.status & DEC_Conversion_syntax) != 0) { - status = U_DECIMAL_NUMBER_SYNTAX_ERROR; - } else if (set.status != 0) { - // Not a syntax error, but some other error, like an exponent that is too large. - status = U_UNSUPPORTED_ERROR; - } -} - /** Helper function for safe subtraction (no overflow). */ inline int32_t safeSubtract(int32_t a, int32_t b) { // Note: In C++, signed integer subtraction is undefined behavior. @@ -198,22 +165,30 @@ void DecimalQuantity::roundToIncrement(double roundingIncrement, RoundingMode ro roundToMagnitude(-maxFrac, roundingMode, status); } -void DecimalQuantity::multiplyBy(double multiplicand) { +void DecimalQuantity::multiplyBy(const DecNum& multiplicand, UErrorCode& status) { if (isInfinite() || isZero() || isNaN()) { return; } - // Cover a few simple cases... - if (multiplicand == 1) { - return; - } else if (multiplicand == -1) { - negate(); + // Convert to DecNum, multiply, and convert back. + DecNum decnum; + toDecNum(decnum, status); + if (U_FAILURE(status)) { return; } + decnum.multiplyBy(multiplicand, status); + if (U_FAILURE(status)) { return; } + setToDecNum(decnum, status); +} + +void DecimalQuantity::divideBy(const DecNum& divisor, UErrorCode& status) { + if (isInfinite() || isZero() || isNaN()) { return; } - // Do math for all other cases... - // TODO: Should we convert to decNumber instead? - double temp = toDouble(); - temp *= multiplicand; - setToDouble(temp); + // Convert to DecNum, multiply, and convert back. + DecNum decnum; + toDecNum(decnum, status); + if (U_FAILURE(status)) { return; } + decnum.divideBy(divisor, status); + if (U_FAILURE(status)) { return; } + setToDecNum(decnum, status); } void DecimalQuantity::negate() { @@ -347,7 +322,7 @@ void DecimalQuantity::_setToInt(int32_t n) { DecimalQuantity &DecimalQuantity::setToLong(int64_t n) { setBcdToZero(); flags = 0; - if (n < 0) { + if (n < 0 && n > INT64_MIN) { flags |= NEGATIVE_FLAG; n = -n; } @@ -360,12 +335,12 @@ DecimalQuantity &DecimalQuantity::setToLong(int64_t n) { void DecimalQuantity::_setToLong(int64_t n) { if (n == INT64_MIN) { - static const char *int64minStr = "9.223372036854775808E+18"; - DecNumberWithStorage dn; + DecNum decnum; UErrorCode localStatus = U_ZERO_ERROR; - stringToDecNumber(int64minStr, dn, localStatus); + decnum.setTo("9.223372036854775808E+18", localStatus); if (U_FAILURE(localStatus)) { return; } // unexpected - readDecNumberToBcd(dn.getAlias()); + flags |= NEGATIVE_FLAG; + readDecNumberToBcd(decnum); } else if (n <= INT32_MAX) { readIntToBcd(static_cast(n)); } else { @@ -468,27 +443,36 @@ DecimalQuantity &DecimalQuantity::setToDecNumber(StringPiece n, UErrorCode& stat setBcdToZero(); flags = 0; - DecNumberWithStorage dn; - stringToDecNumber(n, dn, status); - if (U_FAILURE(status)) { return *this; } + // Compute the decNumber representation + DecNum decnum; + decnum.setTo(n, status); - // The code path for decNumber is modeled after BigDecimal in Java. - if (decNumberIsNegative(dn.getAlias())) { - flags |= NEGATIVE_FLAG; - } - if (!decNumberIsZero(dn.getAlias())) { - _setToDecNumber(dn.getAlias()); - } + _setToDecNum(decnum, status); return *this; } -void DecimalQuantity::_setToDecNumber(decNumber *n) { - // Java fastpaths for ints here. In C++, just always read directly from the decNumber. - readDecNumberToBcd(n); - compact(); +DecimalQuantity& DecimalQuantity::setToDecNum(const DecNum& decnum, UErrorCode& status) { + setBcdToZero(); + flags = 0; + + _setToDecNum(decnum, status); + return *this; +} + +void DecimalQuantity::_setToDecNum(const DecNum& decnum, UErrorCode& status) { + if (U_FAILURE(status)) { return; } + if (decnum.isNegative()) { + flags |= NEGATIVE_FLAG; + } + if (!decnum.isZero()) { + readDecNumberToBcd(decnum); + compact(); + } } int64_t DecimalQuantity::toLong() const { + // NOTE: Call sites should be guarded by fitsInLong(), like this: + // if (dq.fitsInLong()) { /* use dq.toLong() */ } else { /* use some fallback */ } int64_t result = 0L; for (int32_t magnitude = scale + precision - 1; magnitude >= 0; magnitude--) { result = result * 10 + getDigitPos(magnitude - scale); @@ -578,6 +562,21 @@ double DecimalQuantity::toDoubleFromOriginal() const { return result; } +void DecimalQuantity::toDecNum(DecNum& output, UErrorCode& status) const { + // Special handling for zero + if (precision == 0) { + output.setTo("0", status); + } + + // Use the BCD constructor. We need to do a little bit of work to convert, though. + // The decNumber constructor expects most-significant first, but we store least-significant first. + MaybeStackArray ubcd(precision); + for (int32_t m = 0; m < precision; m++) { + ubcd[precision - m - 1] = static_cast(getDigitPos(m)); + } + output.setTo(ubcd.getAlias(), precision, scale, isNegative(), status); +} + void DecimalQuantity::roundToMagnitude(int32_t magnitude, RoundingMode roundingMode, UErrorCode& status) { // The position in the BCD at which rounding will be performed; digits to the right of position // will be rounded away. @@ -886,7 +885,8 @@ void DecimalQuantity::readLongToBcd(int64_t n) { } } -void DecimalQuantity::readDecNumberToBcd(decNumber *dn) { +void DecimalQuantity::readDecNumberToBcd(const DecNum& decnum) { + const decNumber* dn = decnum.getRawDecNumber(); if (dn->digits > 16) { ensureCapacity(dn->digits); for (int32_t i = 0; i < dn->digits; i++) { diff --git a/icu4c/source/i18n/number_decimalquantity.h b/icu4c/source/i18n/number_decimalquantity.h index 4d8bb270d7a..bcd3aa7cf66 100644 --- a/icu4c/source/i18n/number_decimalquantity.h +++ b/icu4c/source/i18n/number_decimalquantity.h @@ -9,7 +9,6 @@ #include #include "unicode/umachine.h" -#include "decNumber.h" #include "standardplural.h" #include "plurrule_impl.h" #include "number_types.h" @@ -17,6 +16,9 @@ U_NAMESPACE_BEGIN namespace number { namespace impl { +// Forward-declare (maybe don't want number_utils.h included here): +class DecNum; + /** * An class for representing a number to be processed by the decimal formatting pipeline. Includes * methods for rounding, plural rules, and decimal digit extraction. @@ -89,11 +91,18 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { void roundToInfinity(); /** - * Multiply the internal value. + * Multiply the internal value. Uses decNumber. * * @param multiplicand The value by which to multiply. */ - void multiplyBy(double multiplicand); + void multiplyBy(const DecNum& multiplicand, UErrorCode& status); + + /** + * Divide the internal value. Uses decNumber. + * + * @param multiplicand The value by which to multiply. + */ + void divideBy(const DecNum& divisor, UErrorCode& status); /** Flips the sign from positive to negative and back. */ void negate(); @@ -140,6 +149,9 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { /** @return The value contained in this {@link DecimalQuantity} approximated as a double. */ double toDouble() const; + /** Computes a DecNum representation of this DecimalQuantity, saving it to the output parameter. */ + void toDecNum(DecNum& output, UErrorCode& status) const; + DecimalQuantity &setToInt(int32_t n); DecimalQuantity &setToLong(int64_t n); @@ -147,9 +159,11 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { DecimalQuantity &setToDouble(double n); /** decNumber is similar to BigDecimal in Java. */ - DecimalQuantity &setToDecNumber(StringPiece n, UErrorCode& status); + /** Internal method if the caller already has a DecNum. */ + DecimalQuantity &setToDecNum(const DecNum& n, UErrorCode& status); + /** * Appends a digit, optionally with one or more leading zeros, to the end of the value represented * by this DecimalQuantity. @@ -416,7 +430,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { */ void readLongToBcd(int64_t n); - void readDecNumberToBcd(decNumber *dn); + void readDecNumberToBcd(const DecNum& dn); void readDoubleConversionToBcd(const char* buffer, int32_t length, int32_t point); @@ -438,7 +452,7 @@ class U_I18N_API DecimalQuantity : public IFixedDecimal, public UMemory { void _setToDoubleFast(double n); - void _setToDecNumber(decNumber *n); + void _setToDecNum(const DecNum& dn, UErrorCode& status); void convertToAccurateDouble(); diff --git a/icu4c/source/i18n/number_fluent.cpp b/icu4c/source/i18n/number_fluent.cpp index 81cf90a8471..86a5a8cfa76 100644 --- a/icu4c/source/i18n/number_fluent.cpp +++ b/icu4c/source/i18n/number_fluent.cpp @@ -305,6 +305,9 @@ Derived NumberFormatterSettings::macros(impl::MacroProps&& macros)&& { template UnicodeString NumberFormatterSettings::toSkeleton(UErrorCode& status) const { + if (fMacros.copyErrorTo(status)) { + return {}; + } return skeleton::generate(fMacros, status); } diff --git a/icu4c/source/i18n/number_multiplier.cpp b/icu4c/source/i18n/number_multiplier.cpp index 85fbd9e6ac0..10e5508b556 100644 --- a/icu4c/source/i18n/number_multiplier.cpp +++ b/icu4c/source/i18n/number_multiplier.cpp @@ -12,6 +12,8 @@ #include "number_types.h" #include "number_multiplier.h" #include "numparse_validators.h" +#include "number_utils.h" +#include "decNumber.h" using namespace icu; using namespace icu::number; @@ -19,38 +21,108 @@ using namespace icu::number::impl; using namespace icu::numparse::impl; -Multiplier::Multiplier(int32_t magnitude, double arbitrary) - : fMagnitude(magnitude), fArbitrary(arbitrary) {} +Multiplier::Multiplier(int32_t magnitude, DecNum* arbitraryToAdopt) + : fMagnitude(magnitude), fArbitrary(arbitraryToAdopt), fError(U_ZERO_ERROR) { + if (fArbitrary != nullptr) { + // Attempt to convert the DecNum to a magnitude multiplier. + fArbitrary->normalize(); + if (fArbitrary->getRawDecNumber()->digits == 1 && fArbitrary->getRawDecNumber()->lsu[0] == 1 && + !fArbitrary->isNegative()) { + // Success! + fMagnitude = fArbitrary->getRawDecNumber()->exponent; + delete fArbitrary; + fArbitrary = nullptr; + } + } +} + +Multiplier::Multiplier(const Multiplier& other) + : fMagnitude(other.fMagnitude), fArbitrary(nullptr), fError(other.fError) { + if (other.fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + fArbitrary = new DecNum(*other.fArbitrary, localStatus); + } +} + +Multiplier& Multiplier::operator=(const Multiplier& other) { + fMagnitude = other.fMagnitude; + if (other.fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + fArbitrary = new DecNum(*other.fArbitrary, localStatus); + } else { + fArbitrary = nullptr; + } + fError = other.fError; + return *this; +} + +Multiplier::Multiplier(Multiplier&& src) U_NOEXCEPT + : fMagnitude(src.fMagnitude), fArbitrary(src.fArbitrary), fError(src.fError) { + // Take ownership away from src if necessary + src.fArbitrary = nullptr; +} + +Multiplier& Multiplier::operator=(Multiplier&& src) U_NOEXCEPT { + fMagnitude = src.fMagnitude; + fArbitrary = src.fArbitrary; + fError = src.fError; + // Take ownership away from src if necessary + src.fArbitrary = nullptr; + return *this; +} + +Multiplier::~Multiplier() { + delete fArbitrary; +} + Multiplier Multiplier::none() { - return {0, 1}; + return {0, nullptr}; } Multiplier Multiplier::powerOfTen(int32_t power) { - return {power, 1}; + return {power, nullptr}; } Multiplier Multiplier::arbitraryDecimal(StringPiece multiplicand) { - // TODO: Fix this hack UErrorCode localError = U_ZERO_ERROR; - DecimalQuantity dq; - dq.setToDecNumber(multiplicand, localError); - return {0, dq.toDouble()}; + LocalPointer decnum(new DecNum(), localError); + if (U_FAILURE(localError)) { + return {localError}; + } + decnum->setTo(multiplicand, localError); + if (U_FAILURE(localError)) { + return {localError}; + } + return {0, decnum.orphan()}; } Multiplier Multiplier::arbitraryDouble(double multiplicand) { - return {0, multiplicand}; + UErrorCode localError = U_ZERO_ERROR; + LocalPointer decnum(new DecNum(), localError); + if (U_FAILURE(localError)) { + return {localError}; + } + decnum->setTo(multiplicand, localError); + if (U_FAILURE(localError)) { + return {localError}; + } + return {0, decnum.orphan()}; } void Multiplier::applyTo(impl::DecimalQuantity& quantity) const { quantity.adjustMagnitude(fMagnitude); - quantity.multiplyBy(fArbitrary); + if (fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + quantity.multiplyBy(*fArbitrary, localStatus); + } } void Multiplier::applyReciprocalTo(impl::DecimalQuantity& quantity) const { quantity.adjustMagnitude(-fMagnitude); - if (fArbitrary != 0) { - quantity.multiplyBy(1 / fArbitrary); + if (fArbitrary != nullptr) { + UErrorCode localStatus = U_ZERO_ERROR; + quantity.divideBy(*fArbitrary, localStatus); } } @@ -70,7 +142,7 @@ void MultiplierFormatHandler::processQuantity(DecimalQuantity& quantity, MicroPr // NOTE: MultiplierParseHandler is declared in the header numparse_validators.h MultiplierParseHandler::MultiplierParseHandler(::icu::number::Multiplier multiplier) - : fMultiplier(multiplier) {} + : fMultiplier(std::move(multiplier)) {} void MultiplierParseHandler::postProcess(ParsedNumber& result) const { if (!result.quantity.bogus) { diff --git a/icu4c/source/i18n/number_rounding.cpp b/icu4c/source/i18n/number_rounding.cpp index b5e37194f8d..ba77cadef0f 100644 --- a/icu4c/source/i18n/number_rounding.cpp +++ b/icu4c/source/i18n/number_rounding.cpp @@ -11,6 +11,7 @@ #include "number_decimalquantity.h" #include "double-conversion.h" #include "number_roundingutils.h" +#include "putilimp.h" using namespace icu; using namespace icu::number; diff --git a/icu4c/source/i18n/number_skeletons.cpp b/icu4c/source/i18n/number_skeletons.cpp index 0976cb5d5b0..81be03b15a4 100644 --- a/icu4c/source/i18n/number_skeletons.cpp +++ b/icu4c/source/i18n/number_skeletons.cpp @@ -1190,24 +1190,28 @@ void blueprint_helpers::parseMultiplierOption(const StringSegment& segment, Macr 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); + LocalPointer decnum(new DecNum(), status); + if (U_FAILURE(status)) { return; } + decnum->setTo({buffer.data(), buffer.length()}, status); + if (U_FAILURE(status)) { status = U_NUMBER_SKELETON_SYNTAX_ERROR; return; } - macros.multiplier = Multiplier::arbitraryDouble(dq.toDouble()); + + // NOTE: The constructor will optimize the decnum for us if possible. + macros.multiplier = {0, decnum.orphan()}; } -void blueprint_helpers::generateMultiplierOption(int32_t magnitude, double arbitrary, UnicodeString& sb, - UErrorCode&) { +void +blueprint_helpers::generateMultiplierOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb, + UErrorCode& status) { // Utilize DecimalQuantity/double_conversion to format this for us. DecimalQuantity dq; - dq.setToDouble(arbitrary); + if (arbitrary != nullptr) { + dq.setToDecNum(*arbitrary, status); + } else { + dq.setToInt(1); + } dq.adjustMagnitude(magnitude); dq.roundToInfinity(); sb.append(dq.toPlainString()); diff --git a/icu4c/source/i18n/number_skeletons.h b/icu4c/source/i18n/number_skeletons.h index 7954b99f2b2..b3e7287f543 100644 --- a/icu4c/source/i18n/number_skeletons.h +++ b/icu4c/source/i18n/number_skeletons.h @@ -22,6 +22,11 @@ struct SeenMacroProps; // namespace for enums and entrypoint functions namespace skeleton { +/////////////////////////////////////////////////////////////////////////////////////// +// NOTE: For an example of how to add a new stem to the number skeleton parser, see: // +// http://bugs.icu-project.org/trac/changeset/41193 // +/////////////////////////////////////////////////////////////////////////////////////// + /** * While parsing a skeleton, this enum records what type of option we expect to find next. */ @@ -240,7 +245,8 @@ void generateNumberingSystemOption(const NumberingSystem& ns, UnicodeString& sb, void parseMultiplierOption(const StringSegment& segment, MacroProps& macros, UErrorCode& status); -void generateMultiplierOption(int32_t magnitude, double arbitrary, UnicodeString& sb, UErrorCode& status); +void generateMultiplierOption(int32_t magnitude, const DecNum* arbitrary, UnicodeString& sb, + UErrorCode& status); } // namespace blueprint_helpers diff --git a/icu4c/source/i18n/number_utils.cpp b/icu4c/source/i18n/number_utils.cpp new file mode 100644 index 00000000000..c2f74e5bb67 --- /dev/null +++ b/icu4c/source/i18n/number_utils.cpp @@ -0,0 +1,186 @@ +// © 2018 and later: Unicode, Inc. and others. +// License & terms of use: http://www.unicode.org/copyright.html + +#include +#include +#include "unicode/utypes.h" + +#if !UCONFIG_NO_FORMATTING && !UPRV_INCOMPLETE_CPP11_SUPPORT + +// Allow implicit conversion from char16_t* to UnicodeString for this file: +// Helpful in toString methods and elsewhere. +#define UNISTR_FROM_STRING_EXPLICIT + +#include "number_types.h" +#include "number_utils.h" +#include "charstr.h" +#include "decContext.h" +#include "decNumber.h" +#include "double-conversion.h" + +using namespace icu; +using namespace icu::number; +using namespace icu::number::impl; + +using icu::double_conversion::DoubleToStringConverter; + + +DecNum::DecNum() { + uprv_decContextDefault(&fContext, DEC_INIT_BASE); + uprv_decContextSetRounding(&fContext, DEC_ROUND_HALF_EVEN); + fContext.traps = 0; // no traps, thank you (what does this even mean?) +} + +DecNum::DecNum(const DecNum& other, UErrorCode& status) + : fContext(other.fContext) { + // Allocate memory for the new DecNum. + U_ASSERT(fContext.digits == other.fData.getCapacity()); + if (fContext.digits > kDefaultDigits) { + void* p = fData.resize(fContext.digits, 0); + if (p == nullptr) { + status = U_MEMORY_ALLOCATION_ERROR; + return; + } + } + + // Copy the data from the old DecNum to the new one. + uprv_memcpy(fData.getAlias(), other.fData.getAlias(), sizeof(decNumber)); + uprv_memcpy(fData.getArrayStart(), + other.fData.getArrayStart(), + other.fData.getArrayLimit() - other.fData.getArrayStart()); +} + +void DecNum::setTo(StringPiece str, UErrorCode& status) { + // We need NUL-terminated for decNumber; CharString guarantees this, but not StringPiece. + CharString cstr(str, status); + if (U_FAILURE(status)) { return; } + _setTo(cstr.data(), str.length(), status); +} + +void DecNum::setTo(const char* str, UErrorCode& status) { + _setTo(str, static_cast(uprv_strlen(str)), status); +} + +void DecNum::setTo(double d, UErrorCode& status) { + // Need to check for NaN and Infinity before going into DoubleToStringConverter + if (std::isnan(d) != 0 || std::isfinite(d) == 0) { + status = U_UNSUPPORTED_ERROR; + return; + } + + // First convert from double to string, then string to DecNum. + // Allocate enough room for: all digits, "E-324", and NUL-terminator. + char buffer[DoubleToStringConverter::kBase10MaximalLength + 6]; + bool sign; // unused; always positive + int32_t length; + int32_t point; + DoubleToStringConverter::DoubleToAscii( + d, + DoubleToStringConverter::DtoaMode::SHORTEST, + 0, + buffer, + sizeof(buffer), + &sign, + &length, + &point + ); + + // Read initial result as a string. + _setTo(buffer, length, status); + + // Set exponent and bitmask. Note that DoubleToStringConverter does not do negatives. + fData.getAlias()->exponent += point - length; + fData.getAlias()->bits |= static_cast(std::signbit(d) ? DECNEG : 0); +} + +void DecNum::_setTo(const char* str, int32_t maxDigits, UErrorCode& status) { + if (maxDigits > kDefaultDigits) { + fData.resize(maxDigits, 0); + fContext.digits = maxDigits; + } else { + fContext.digits = kDefaultDigits; + } + + static_assert(DECDPUN == 1, "Assumes that DECDPUN is set to 1"); + uprv_decNumberFromString(fData.getAlias(), str, &fContext); + + // For consistency with Java BigDecimal, no support for DecNum that is NaN or Infinity! + if (decNumberIsSpecial(fData.getAlias())) { + status = U_UNSUPPORTED_ERROR; + return; + } + + // Check for invalid syntax and set the corresponding error code. + if ((fContext.status & DEC_Conversion_syntax) != 0) { + status = U_DECIMAL_NUMBER_SYNTAX_ERROR; + } else if (fContext.status != 0) { + // Not a syntax error, but some other error, like an exponent that is too large. + status = U_UNSUPPORTED_ERROR; + } +} + +void +DecNum::setTo(const uint8_t* bcd, int32_t length, int32_t scale, bool isNegative, UErrorCode& status) { + if (length > kDefaultDigits) { + fData.resize(length, 0); + fContext.digits = length; + } else { + fContext.digits = kDefaultDigits; + } + + // "digits is of type int32_t, and must have a value in the range 1 through 999,999,999." + if (length < 1 || length > 999999999) { + // Too large for decNumber + status = U_UNSUPPORTED_ERROR; + return; + } + // "The exponent field holds the exponent of the number. Its range is limited by the requirement that + // "the range of the adjusted exponent of the number be balanced and fit within a whole number of + // "decimal digits (in this implementation, be –999,999,999 through +999,999,999). The adjusted + // "exponent is the exponent that would result if the number were expressed with a single digit before + // "the decimal point, and is therefore given by exponent+digits-1." + if (scale > 999999999 - length + 1 || scale < -999999999 - length + 1) { + // Too large for decNumber + status = U_UNSUPPORTED_ERROR; + return; + } + + fData.getAlias()->digits = length; + fData.getAlias()->exponent = scale; + fData.getAlias()->bits = static_cast(isNegative ? DECNEG : 0); + uprv_decNumberSetBCD(fData, bcd, static_cast(length)); + if (fContext.status != 0) { + // Some error occured while constructing the decNumber. + status = U_INTERNAL_PROGRAM_ERROR; + } +} + +void DecNum::normalize() { + uprv_decNumberReduce(fData, fData, &fContext); +} + +void DecNum::multiplyBy(const DecNum& rhs, UErrorCode& status) { + uprv_decNumberMultiply(fData, fData, rhs.fData, &fContext); + if (fContext.status != 0) { + status = U_INTERNAL_PROGRAM_ERROR; + } +} + +void DecNum::divideBy(const DecNum& rhs, UErrorCode& status) { + uprv_decNumberDivide(fData, fData, rhs.fData, &fContext); + if (fContext.status != 0) { + status = U_INTERNAL_PROGRAM_ERROR; + } +} + +bool DecNum::isNegative() const { + return decNumberIsNegative(fData.getAlias()); +} + +bool DecNum::isZero() const { + return decNumberIsZero(fData.getAlias()); +} + + + +#endif /* #if !UCONFIG_NO_FORMATTING */ diff --git a/icu4c/source/i18n/number_utils.h b/icu4c/source/i18n/number_utils.h index 23c6fcf7ec8..1afd608ae92 100644 --- a/icu4c/source/i18n/number_utils.h +++ b/icu4c/source/i18n/number_utils.h @@ -14,6 +14,8 @@ #include "number_patternstring.h" #include "number_modifiers.h" #include "number_multiplier.h" +#include "decNumber.h" +#include "charstr.h" U_NAMESPACE_BEGIN namespace number { namespace impl { @@ -128,6 +130,50 @@ inline bool unitIsPermille(const MeasureUnit& unit) { return uprv_strcmp("permille", unit.getSubtype()) == 0; } + +/** A very thin C++ wrapper around decNumber.h */ +class DecNum : public UMemory { + public: + DecNum(); // leaves object in valid but undefined state + + // Copy-like constructor; use the default move operators. + DecNum(const DecNum& other, UErrorCode& status); + + /** Sets the decNumber to the StringPiece. */ + void setTo(StringPiece str, UErrorCode& status); + + /** Sets the decNumber to the NUL-terminated char string. */ + void setTo(const char* str, UErrorCode& status); + + /** Uses double_conversion to set this decNumber to the given double. */ + void setTo(double d, UErrorCode& status); + + /** Sets the decNumber to the BCD representation. */ + void setTo(const uint8_t* bcd, int32_t length, int32_t scale, bool isNegative, UErrorCode& status); + + void normalize(); + + void multiplyBy(const DecNum& rhs, UErrorCode& status); + + void divideBy(const DecNum& rhs, UErrorCode& status); + + bool isNegative() const; + + bool isZero() const; + + inline const decNumber* getRawDecNumber() const { + return fData.getAlias(); + } + + private: + static constexpr int32_t kDefaultDigits = 34; + MaybeStackHeaderAndArray fData; + decContext fContext; + + void _setTo(const char* str, int32_t maxDigits, UErrorCode& status); +}; + + } // namespace impl } // namespace number U_NAMESPACE_END diff --git a/icu4c/source/i18n/numparse_decimal.cpp b/icu4c/source/i18n/numparse_decimal.cpp index 3853e5a9a98..28a67d85bac 100644 --- a/icu4c/source/i18n/numparse_decimal.cpp +++ b/icu4c/source/i18n/numparse_decimal.cpp @@ -14,6 +14,7 @@ #include "numparse_unisets.h" #include "numparse_utils.h" #include "unicode/uchar.h" +#include "putilimp.h" using namespace icu; using namespace icu::numparse; diff --git a/icu4c/source/i18n/unicode/numberformatter.h b/icu4c/source/i18n/unicode/numberformatter.h index 54698d382ea..a95e1da2d1f 100644 --- a/icu4c/source/i18n/unicode/numberformatter.h +++ b/icu4c/source/i18n/unicode/numberformatter.h @@ -146,6 +146,7 @@ struct DecimalFormatProperties; class MultiplierFormatHandler; class CurrencySymbols; class GeneratorHelpers; +class DecNum; } // namespace impl @@ -1019,16 +1020,46 @@ class U_I18N_API Multiplier : public UMemory { */ static Multiplier arbitraryDouble(double multiplicand); + // We need a custom destructor for the DecNum, which means we need to declare + // the copy/move constructor/assignment quartet. + + /** @draft ICU 62 */ + Multiplier(const Multiplier& other); + + /** @draft ICU 62 */ + Multiplier& operator=(const Multiplier& other); + + /** @draft ICU 62 */ + Multiplier(Multiplier&& src) U_NOEXCEPT; + + /** @draft ICU 62 */ + Multiplier& operator=(Multiplier&& src) U_NOEXCEPT; + + /** @draft ICU 62 */ + ~Multiplier(); + + /** @internal */ + Multiplier(int32_t magnitude, impl::DecNum* arbitraryToAdopt); + private: int32_t fMagnitude; - double fArbitrary; + impl::DecNum* fArbitrary; + UErrorCode fError; - Multiplier(int32_t magnitude, double arbitrary); + Multiplier(UErrorCode error) : fMagnitude(0), fArbitrary(nullptr), fError(error) {} - Multiplier() : fMagnitude(0), fArbitrary(1) {} + Multiplier() : fMagnitude(0), fArbitrary(nullptr), fError(U_ZERO_ERROR) {} bool isValid() const { - return fMagnitude != 0 || fArbitrary != 1; + return fMagnitude != 0 || fArbitrary != nullptr; + } + + UBool copyErrorTo(UErrorCode &status) const { + if (fError != U_ZERO_ERROR) { + status = fError; + return TRUE; + } + return FALSE; } void applyTo(impl::DecimalQuantity& quantity) const; @@ -1066,16 +1097,16 @@ class U_I18N_API SymbolsWrapper : public UMemory { SymbolsWrapper(const SymbolsWrapper &other); /** @internal */ - SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT; + SymbolsWrapper &operator=(const SymbolsWrapper &other); /** @internal */ - ~SymbolsWrapper(); + SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT; /** @internal */ - SymbolsWrapper &operator=(const SymbolsWrapper &other); + SymbolsWrapper &operator=(SymbolsWrapper&& src) U_NOEXCEPT; /** @internal */ - SymbolsWrapper &operator=(SymbolsWrapper&& src) U_NOEXCEPT; + ~SymbolsWrapper(); #ifndef U_HIDE_INTERNAL_API @@ -1359,7 +1390,7 @@ struct U_I18N_API MacroProps : public UMemory { bool copyErrorTo(UErrorCode &status) const { return notation.copyErrorTo(status) || rounder.copyErrorTo(status) || padder.copyErrorTo(status) || integerWidth.copyErrorTo(status) || - symbols.copyErrorTo(status); + symbols.copyErrorTo(status) || multiplier.copyErrorTo(status); } }; diff --git a/icu4c/source/test/intltest/numbertest_api.cpp b/icu4c/source/test/intltest/numbertest_api.cpp index 947e970d6df..ca759183115 100644 --- a/icu4c/source/test/intltest/numbertest_api.cpp +++ b/icu4c/source/test/intltest/numbertest_api.cpp @@ -7,6 +7,7 @@ #include "charstr.h" #include +#include #include "unicode/unum.h" #include "unicode/numberformatter.h" #include "number_types.h" @@ -2001,6 +2002,14 @@ void NumberFormatterApiTest::multiplier() { Locale::getEnglish(), 2, u"-10.4"); + + assertFormatSingle( + u"Negative One Multiplier", + u"multiply/-1", + NumberFormatter::with().multiplier(Multiplier::arbitraryDouble(-1)), + Locale::getEnglish(), + 444444, + u"-444,444"); } void NumberFormatterApiTest::locale() { @@ -2209,7 +2218,7 @@ void NumberFormatterApiTest::assertFormatDescending(const char16_t* umessage, co for (int32_t i = 0; i < 9; i++) { double d = inputs[i]; UnicodeString actual3 = l3.formatDouble(d, status).toString(); - assertEquals(message + ": Skeleton Path: " + d, expecteds[i], actual3); + assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3); } } else { assertUndefinedSkeleton(f); @@ -2250,7 +2259,7 @@ void NumberFormatterApiTest::assertFormatDescendingBig(const char16_t* umessage, for (int32_t i = 0; i < 9; i++) { double d = inputs[i]; UnicodeString actual3 = l3.formatDouble(d, status).toString(); - assertEquals(message + ": Skeleton Path: " + d, expecteds[i], actual3); + assertEquals(message + ": Skeleton Path: '" + normalized + "': " + d, expecteds[i], actual3); } } else { assertUndefinedSkeleton(f); @@ -2279,7 +2288,7 @@ void NumberFormatterApiTest::assertFormatSingle(const char16_t* umessage, const assertEquals(message + ": Skeleton:", normalized, f.toSkeleton(status)); LocalizedNumberFormatter l3 = NumberFormatter::fromSkeleton(normalized, status).locale(locale); UnicodeString actual3 = l3.formatDouble(input, status).toString(); - assertEquals(message + ": Skeleton Path: " + input, expected, actual3); + assertEquals(message + ": Skeleton Path: '" + normalized + "': " + input, expected, actual3); } else { assertUndefinedSkeleton(f); } diff --git a/icu4c/source/test/intltest/numbertest_skeletons.cpp b/icu4c/source/test/intltest/numbertest_skeletons.cpp index 68138b9b084..28700b3bf9d 100644 --- a/icu4c/source/test/intltest/numbertest_skeletons.cpp +++ b/icu4c/source/test/intltest/numbertest_skeletons.cpp @@ -130,8 +130,10 @@ void NumberSkeletonTest::invalidTokens() { u"round-currency-cash/XXX", u"scientific/ee", u"round-increment/xxx", + u"round-increment/NaN", u"round-increment/0.1.2", u"multiply/xxx", + u"multiply/NaN", u"multiply/0.1.2", u"multiply/français", // non-invariant characters for C++ u"currency/dummy", diff --git a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java index 70df1dbcd1c..1f855830f02 100644 --- a/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java +++ b/icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java @@ -33,6 +33,11 @@ import com.ibm.icu.util.StringTrieBuilder; */ class NumberSkeletonImpl { + /////////////////////////////////////////////////////////////////////////////////////// + // NOTE: For an example of how to add a new stem to the number skeleton parser, see: // + // http://bugs.icu-project.org/trac/changeset/41193 // + /////////////////////////////////////////////////////////////////////////////////////// + /** * While parsing a skeleton, this enum records what type of option we expect to find next. */ diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java index ac0a00f6d13..4505b21cb12 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java @@ -1975,6 +1975,14 @@ public class NumberFormatterApiTest { ULocale.ENGLISH, 2, "-10.4"); + + assertFormatSingle( + "Negative One Multiplier", + "multiply/-1", + NumberFormatter.with().multiplier(Multiplier.arbitrary(-1)), + ULocale.ENGLISH, + 444444, + "-444,444"); } @Test diff --git a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java index 5e884765329..216127923ba 100644 --- a/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java +++ b/icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java @@ -124,8 +124,10 @@ public class NumberSkeletonTest { "round-currency-cash/XXX", "scientific/ee", "round-increment/xxx", + "round-increment/NaN", "round-increment/0.1.2", "multiply/xxx", + "multiply/NaN", "multiply/0.1.2", "multiply/français", // non-invariant characters for C++ "currency/dummy",