summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/i18n/number_utils.cpp
blob: ad7053214077b0d108f8bc0e0642fba6eb4f2780 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
// © 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_EXIT;
    }
    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;
    }
}

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

bool DecNum::isSpecial() const {
    return decNumberIsSpecial(fData.getAlias());
}

bool DecNum::isInfinity() const {
    return decNumberIsInfinite(fData.getAlias());
}

bool DecNum::isNaN() const {
    return decNumberIsNaN(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 */