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
This commit is contained in:
parent
a2a7982216
commit
fe0725cd2a
icu4c/source
i18n
Makefile.infmtable.cppnfsubs.cppnumber_decimalquantity.cppnumber_decimalquantity.hnumber_fluent.cppnumber_multiplier.cppnumber_rounding.cppnumber_skeletons.cppnumber_skeletons.hnumber_utils.cppnumber_utils.hnumparse_decimal.cpp
unicode
test/intltest
icu4j/main
classes/core/src/com/ibm/icu/number
tests/core/src/com/ibm/icu/dev/test/number
@ -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 \
|
||||
|
@ -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;
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "nfsubs.h"
|
||||
#include "fmtableimp.h"
|
||||
#include "putilimp.h"
|
||||
#include "number_decimalquantity.h"
|
||||
|
||||
#if U_HAVE_RBNF
|
||||
|
@ -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++) {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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());
|
||||
|
@ -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
|
||||
|
||||
|
186
icu4c/source/i18n/number_utils.cpp
Normal file
186
icu4c/source/i18n/number_utils.cpp
Normal file
@ -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 */
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
@ -1065,18 +1096,18 @@ class U_I18N_API SymbolsWrapper : public UMemory {
|
||||
/** @internal */
|
||||
SymbolsWrapper(const SymbolsWrapper &other);
|
||||
|
||||
/** @internal */
|
||||
SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT;
|
||||
|
||||
/** @internal */
|
||||
~SymbolsWrapper();
|
||||
|
||||
/** @internal */
|
||||
SymbolsWrapper &operator=(const SymbolsWrapper &other);
|
||||
|
||||
/** @internal */
|
||||
SymbolsWrapper(SymbolsWrapper&& src) U_NOEXCEPT;
|
||||
|
||||
/** @internal */
|
||||
SymbolsWrapper &operator=(SymbolsWrapper&& src) U_NOEXCEPT;
|
||||
|
||||
/** @internal */
|
||||
~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);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
Loading…
Reference in New Issue
Block a user