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:
Shane Carr 2018-04-05 21:54:04 +00:00
parent a2a7982216
commit fe0725cd2a
19 changed files with 508 additions and 118 deletions

View 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 \

View 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;

View File

@ -20,6 +20,7 @@
#include "nfsubs.h"
#include "fmtableimp.h"
#include "putilimp.h"
#include "number_decimalquantity.h"
#if U_HAVE_RBNF

View 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++) {

View 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();

View 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);
}

View 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) {

View 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;

View 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());

View 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

View 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 */

View 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

View 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;

View 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;
@ -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);
}
};

View 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);
}

View 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",

View 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.
*/

View 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

View 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",