256 lines
8.4 KiB
C++
256 lines
8.4 KiB
C++
// © 2018 and later: Unicode, Inc. and others.
|
||
// License & terms of use: http://www.unicode.org/copyright.html
|
||
|
||
#include "unicode/utypes.h"
|
||
|
||
#if !UCONFIG_NO_FORMATTING
|
||
|
||
// Allow implicit conversion from char16_t* to UnicodeString for this file:
|
||
// Helpful in toString methods and elsewhere.
|
||
#define UNISTR_FROM_STRING_EXPLICIT
|
||
|
||
#include <stdlib.h>
|
||
#include <cmath>
|
||
#include "number_decnum.h"
|
||
#include "number_types.h"
|
||
#include "number_utils.h"
|
||
#include "charstr.h"
|
||
#include "decContext.h"
|
||
#include "decNumber.h"
|
||
#include "double-conversion.h"
|
||
#include "fphdlimp.h"
|
||
#include "uresimp.h"
|
||
#include "ureslocs.h"
|
||
|
||
using namespace icu;
|
||
using namespace icu::number;
|
||
using namespace icu::number::impl;
|
||
|
||
using icu::double_conversion::DoubleToStringConverter;
|
||
|
||
|
||
namespace {
|
||
|
||
const char16_t*
|
||
doGetPattern(UResourceBundle* res, const char* nsName, const char* patternKey, UErrorCode& publicStatus,
|
||
UErrorCode& localStatus) {
|
||
// Construct the path into the resource bundle
|
||
CharString key;
|
||
key.append("NumberElements/", publicStatus);
|
||
key.append(nsName, publicStatus);
|
||
key.append("/patterns/", publicStatus);
|
||
key.append(patternKey, publicStatus);
|
||
if (U_FAILURE(publicStatus)) {
|
||
return u"";
|
||
}
|
||
return ures_getStringByKeyWithFallback(res, key.data(), nullptr, &localStatus);
|
||
}
|
||
|
||
}
|
||
|
||
|
||
const char16_t* utils::getPatternForStyle(const Locale& locale, const char* nsName, CldrPatternStyle style,
|
||
UErrorCode& status) {
|
||
const char* patternKey;
|
||
switch (style) {
|
||
case CLDR_PATTERN_STYLE_DECIMAL:
|
||
patternKey = "decimalFormat";
|
||
break;
|
||
case CLDR_PATTERN_STYLE_CURRENCY:
|
||
patternKey = "currencyFormat";
|
||
break;
|
||
case CLDR_PATTERN_STYLE_ACCOUNTING:
|
||
patternKey = "accountingFormat";
|
||
break;
|
||
case CLDR_PATTERN_STYLE_PERCENT:
|
||
patternKey = "percentFormat";
|
||
break;
|
||
case CLDR_PATTERN_STYLE_SCIENTIFIC:
|
||
patternKey = "scientificFormat";
|
||
break;
|
||
default:
|
||
patternKey = "decimalFormat"; // silence compiler error
|
||
U_ASSERT(false);
|
||
}
|
||
LocalUResourceBundlePointer res(ures_open(nullptr, locale.getName(), &status));
|
||
if (U_FAILURE(status)) { return u""; }
|
||
|
||
// Attempt to get the pattern with the native numbering system.
|
||
UErrorCode localStatus = U_ZERO_ERROR;
|
||
const char16_t* pattern;
|
||
pattern = doGetPattern(res.getAlias(), nsName, patternKey, status, localStatus);
|
||
if (U_FAILURE(status)) { return u""; }
|
||
|
||
// Fall back to latn if native numbering system does not have the right pattern
|
||
if (U_FAILURE(localStatus) && uprv_strcmp("latn", nsName) != 0) {
|
||
localStatus = U_ZERO_ERROR;
|
||
pattern = doGetPattern(res.getAlias(), "latn", patternKey, status, localStatus);
|
||
if (U_FAILURE(status)) { return u""; }
|
||
}
|
||
|
||
return pattern;
|
||
}
|
||
|
||
|
||
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);
|
||
|
||
// Check for invalid syntax and set the corresponding error code.
|
||
if ((fContext.status & DEC_Conversion_syntax) != 0) {
|
||
status = U_DECIMAL_NUMBER_SYNTAX_ERROR;
|
||
return;
|
||
} else if (fContext.status != 0) {
|
||
// Not a syntax error, but some other error, like an exponent that is too large.
|
||
status = U_UNSUPPORTED_ERROR;
|
||
return;
|
||
}
|
||
|
||
// For consistency with Java BigDecimal, no support for DecNum that is NaN or Infinity!
|
||
if (decNumberIsSpecial(fData.getAlias())) {
|
||
status = U_UNSUPPORTED_ERROR;
|
||
return;
|
||
}
|
||
}
|
||
|
||
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 occurred 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 & DEC_Inexact) != 0) {
|
||
// Ignore.
|
||
} else 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 */
|