You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

270 lines
8.9 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

// © 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
UPRV_UNREACHABLE;
}
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());
}
void DecNum::toString(ByteSink& output, UErrorCode& status) const {
if (U_FAILURE(status)) {
return;
}
// "string must be at least dn->digits+14 characters long"
int32_t minCapacity = fData.getAlias()->digits + 14;
MaybeStackArray<char, 30> buffer(minCapacity, status);
if (U_FAILURE(status)) {
return;
}
uprv_decNumberToString(fData, buffer.getAlias());
output.Append(buffer.getAlias(), static_cast<int32_t>(uprv_strlen(buffer.getAlias())));
}
#endif /* #if !UCONFIG_NO_FORMATTING */