]> granicus.if.org Git - icu/commitdiff
ICU-13678 Changing Multiplier to use decNumber instead of double, in order to fix...
authorShane Carr <shane@unicode.org>
Thu, 5 Apr 2018 21:54:04 +0000 (21:54 +0000)
committerShane Carr <shane@unicode.org>
Thu, 5 Apr 2018 21:54:04 +0000 (21:54 +0000)
X-SVN-Rev: 41198

19 files changed:
icu4c/source/i18n/Makefile.in
icu4c/source/i18n/fmtable.cpp
icu4c/source/i18n/nfsubs.cpp
icu4c/source/i18n/number_decimalquantity.cpp
icu4c/source/i18n/number_decimalquantity.h
icu4c/source/i18n/number_fluent.cpp
icu4c/source/i18n/number_multiplier.cpp
icu4c/source/i18n/number_rounding.cpp
icu4c/source/i18n/number_skeletons.cpp
icu4c/source/i18n/number_skeletons.h
icu4c/source/i18n/number_utils.cpp [new file with mode: 0644]
icu4c/source/i18n/number_utils.h
icu4c/source/i18n/numparse_decimal.cpp
icu4c/source/i18n/unicode/numberformatter.h
icu4c/source/test/intltest/numbertest_api.cpp
icu4c/source/test/intltest/numbertest_skeletons.cpp
icu4j/main/classes/core/src/com/ibm/icu/number/NumberSkeletonImpl.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberFormatterApiTest.java
icu4j/main/tests/core/src/com/ibm/icu/dev/test/number/NumberSkeletonTest.java

index 44236b50474d0c5421784928a48ca283f97e0c96..b4fafdf72f8479bcfbdd3789dbea2051c38c18bb 100644 (file)
@@ -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 \
index e4c119aea6731a48f82803f869f68d3c7d243f6c..ac2411a20b44d977fb26a3632ca3838fa9936611 100644 (file)
@@ -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;
index 81aa2e5ffdcf714ec85c639fca8a15804c89685e..0911ac0887f07c6e5962b90019450deb3599bc9a 100644 (file)
@@ -20,6 +20,7 @@
 
 #include "nfsubs.h"
 #include "fmtableimp.h"
+#include "putilimp.h"
 #include "number_decimalquantity.h"
 
 #if U_HAVE_RBNF
index f092bc10e4d29f5abb8f857738fa28725663266d..c39a998f23ff1129509e62af5cd0d6acfdcc66db 100644 (file)
@@ -8,15 +8,14 @@
 #include "uassert.h"
 #include <cmath>
 #include "cmemory.h"
-#include "decNumber.h"
 #include <limits>
+#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<decNumber, char, DEFAULT_DIGITS> 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<int32_t>(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<uint8_t, 20> ubcd(precision);
+    for (int32_t m = 0; m < precision; m++) {
+        ubcd[precision - m - 1] = static_cast<uint8_t>(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++) {
index 4d8bb270d7a27ef3216f1606a2dd2a9c18930a09..bcd3aa7cf66baefdcfa142ad6ef4748a8cd5a222 100644 (file)
@@ -9,7 +9,6 @@
 
 #include <cstdint>
 #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();
 
index 81cf90a84718634e8aa52a4d7e7acf1f6e937757..86a5a8cfa764f1e58458332949f0b32e55d0663b 100644 (file)
@@ -305,6 +305,9 @@ Derived NumberFormatterSettings<Derived>::macros(impl::MacroProps&& macros)&& {
 
 template<typename Derived>
 UnicodeString NumberFormatterSettings<Derived>::toSkeleton(UErrorCode& status) const {
+    if (fMacros.copyErrorTo(status)) {
+        return {};
+    }
     return skeleton::generate(fMacros, status);
 }
 
index 85fbd9e6ac0eddae64db2450843363fe3b744d4f..10e5508b556aa7a683a461584e86e034875e8754 100644 (file)
@@ -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> 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> 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) {
index b5e37194f8dd91a9c6841222ea160ab059621d44..ba77cadef0f3b6285b1ed3c36253667a5776a8f0 100644 (file)
@@ -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;
index 0976cb5d5b0226a5aa0037848ab9431185d8f354..81be03b15a4c0be8b6bb199b97a1950ef7ba1d86 100644 (file)
@@ -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> 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());
index 7954b99f2b227abeb929bda88e48f20687b81567..b3e7287f543e5b316e3304c267a5d9a8df6c58fe 100644 (file)
@@ -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 (file)
index 0000000..c2f74e5
--- /dev/null
@@ -0,0 +1,186 @@
+// © 2018 and later: Unicode, Inc. and others.
+// License & terms of use: http://www.unicode.org/copyright.html
+
+#include <stdlib.h>
+#include <cmath>
+#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<int32_t>(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<uint8_t>(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<uint8_t>(isNegative ? DECNEG : 0);
+    uprv_decNumberSetBCD(fData, bcd, static_cast<uint32_t>(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 */
index 23c6fcf7ec87b9609f425bd8e29d418d85567274..1afd608ae92e7a39ba3c67e75e5c90200b94139d 100644 (file)
@@ -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<decNumber, char, kDefaultDigits> fData;
+    decContext fContext;
+
+    void _setTo(const char* str, int32_t maxDigits, UErrorCode& status);
+};
+
+
 } // namespace impl
 } // namespace number
 U_NAMESPACE_END
index 3853e5a9a98fb28b665285c4f504a4d4b124caf1..28a67d85bacc108172ef85fce15cef71a7faed4c 100644 (file)
@@ -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;
index 54698d382eacd2411dbb35dcb5fd91d1cf38bf80..a95e1da2d1fecd99a8b9ba78ff51a9e16c4a0ae0 100644 (file)
@@ -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);
     }
 };
 
index 947e970d6dfb3a7ad202cf2d45b104479331f9d4..ca759183115265500c572be1b54d8e8895723e77 100644 (file)
@@ -7,6 +7,7 @@
 
 #include "charstr.h"
 #include <cstdarg>
+#include <cmath>
 #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);
     }
index 68138b9b08423b73538459660cc1fb59190fb052..28700b3bf9da5186fee485d5b1a185131553c206 100644 (file)
@@ -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",
index 70df1dbcd1cbe5924ba7e4d2a5d866a404d39187..1f855830f02c843320272acbfc055efc4a51d2af 100644 (file)
@@ -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.
      */
index ac0a00f6d131379a53bd874ad0c155d4e6841928..4505b21cb1286c78d869f4b52810f1ef7654965b 100644 (file)
@@ -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
index 5e8847653297c2ce6324399745b960460cbface6..216127923baedb0f44f78aae7271edaeaa8f325e 100644 (file)
@@ -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",