Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/intl/icu/source/i18n/number_rounding.cpp
Line
Count
Source (jump to first uncovered line)
1
// © 2017 and later: Unicode, Inc. and others.
2
// License & terms of use: http://www.unicode.org/copyright.html
3
4
#include "unicode/utypes.h"
5
6
#if !UCONFIG_NO_FORMATTING
7
8
#include "uassert.h"
9
#include "unicode/numberformatter.h"
10
#include "number_types.h"
11
#include "number_decimalquantity.h"
12
#include "double-conversion.h"
13
#include "number_roundingutils.h"
14
#include "putilimp.h"
15
16
using namespace icu;
17
using namespace icu::number;
18
using namespace icu::number::impl;
19
20
21
using double_conversion::DoubleToStringConverter;
22
23
namespace {
24
25
0
int32_t getRoundingMagnitudeFraction(int maxFrac) {
26
0
    if (maxFrac == -1) {
27
0
        return INT32_MIN;
28
0
    }
29
0
    return -maxFrac;
30
0
}
31
32
0
int32_t getRoundingMagnitudeSignificant(const DecimalQuantity &value, int maxSig) {
33
0
    if (maxSig == -1) {
34
0
        return INT32_MIN;
35
0
    }
36
0
    int magnitude = value.isZero() ? 0 : value.getMagnitude();
37
0
    return magnitude - maxSig + 1;
38
0
}
39
40
0
int32_t getDisplayMagnitudeFraction(int minFrac) {
41
0
    if (minFrac == 0) {
42
0
        return INT32_MAX;
43
0
    }
44
0
    return -minFrac;
45
0
}
46
47
0
int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) {
48
0
    int magnitude = value.isZero() ? 0 : value.getMagnitude();
49
0
    return magnitude - minSig + 1;
50
0
}
51
52
}
53
54
55
0
MultiplierProducer::~MultiplierProducer() = default;
56
57
58
0
digits_t roundingutils::doubleFractionLength(double input) {
59
0
    char buffer[DoubleToStringConverter::kBase10MaximalLength + 1];
60
0
    bool sign; // unused; always positive
61
0
    int32_t length;
62
0
    int32_t point;
63
0
    DoubleToStringConverter::DoubleToAscii(
64
0
            input,
65
0
            DoubleToStringConverter::DtoaMode::SHORTEST,
66
0
            0,
67
0
            buffer,
68
0
            sizeof(buffer),
69
0
            &sign,
70
0
            &length,
71
0
            &point
72
0
    );
73
0
74
0
    return static_cast<digits_t>(length - point);
75
0
}
76
77
78
0
Precision Precision::unlimited() {
79
0
    return Precision(RND_NONE, {}, kDefaultMode);
80
0
}
81
82
0
FractionPrecision Precision::integer() {
83
0
    return constructFraction(0, 0);
84
0
}
85
86
0
FractionPrecision Precision::fixedFraction(int32_t minMaxFractionPlaces) {
87
0
    if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= kMaxIntFracSig) {
88
0
        return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces);
89
0
    } else {
90
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
91
0
    }
92
0
}
93
94
0
FractionPrecision Precision::minFraction(int32_t minFractionPlaces) {
95
0
    if (minFractionPlaces >= 0 && minFractionPlaces <= kMaxIntFracSig) {
96
0
        return constructFraction(minFractionPlaces, -1);
97
0
    } else {
98
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
99
0
    }
100
0
}
101
102
0
FractionPrecision Precision::maxFraction(int32_t maxFractionPlaces) {
103
0
    if (maxFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig) {
104
0
        return constructFraction(0, maxFractionPlaces);
105
0
    } else {
106
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
107
0
    }
108
0
}
109
110
0
FractionPrecision Precision::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) {
111
0
    if (minFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig &&
112
0
        minFractionPlaces <= maxFractionPlaces) {
113
0
        return constructFraction(minFractionPlaces, maxFractionPlaces);
114
0
    } else {
115
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
116
0
    }
117
0
}
118
119
0
Precision Precision::fixedSignificantDigits(int32_t minMaxSignificantDigits) {
120
0
    if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= kMaxIntFracSig) {
121
0
        return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits);
122
0
    } else {
123
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
124
0
    }
125
0
}
126
127
0
Precision Precision::minSignificantDigits(int32_t minSignificantDigits) {
128
0
    if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) {
129
0
        return constructSignificant(minSignificantDigits, -1);
130
0
    } else {
131
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
132
0
    }
133
0
}
134
135
0
Precision Precision::maxSignificantDigits(int32_t maxSignificantDigits) {
136
0
    if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) {
137
0
        return constructSignificant(1, maxSignificantDigits);
138
0
    } else {
139
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
140
0
    }
141
0
}
142
143
0
Precision Precision::minMaxSignificantDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) {
144
0
    if (minSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig &&
145
0
        minSignificantDigits <= maxSignificantDigits) {
146
0
        return constructSignificant(minSignificantDigits, maxSignificantDigits);
147
0
    } else {
148
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
149
0
    }
150
0
}
151
152
0
IncrementPrecision Precision::increment(double roundingIncrement) {
153
0
    if (roundingIncrement > 0.0) {
154
0
        return constructIncrement(roundingIncrement, 0);
155
0
    } else {
156
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
157
0
    }
158
0
}
159
160
0
CurrencyPrecision Precision::currency(UCurrencyUsage currencyUsage) {
161
0
    return constructCurrency(currencyUsage);
162
0
}
163
164
0
Precision Precision::withMode(RoundingMode roundingMode) const {
165
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
166
0
    Precision retval = *this;
167
0
    retval.fRoundingMode = roundingMode;
168
0
    return retval;
169
0
}
170
171
0
Precision FractionPrecision::withMinDigits(int32_t minSignificantDigits) const {
172
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
173
0
    if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) {
174
0
        return constructFractionSignificant(*this, minSignificantDigits, -1);
175
0
    } else {
176
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
177
0
    }
178
0
}
179
180
0
Precision FractionPrecision::withMaxDigits(int32_t maxSignificantDigits) const {
181
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
182
0
    if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) {
183
0
        return constructFractionSignificant(*this, -1, maxSignificantDigits);
184
0
    } else {
185
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
186
0
    }
187
0
}
188
189
// Private method on base class
190
0
Precision Precision::withCurrency(const CurrencyUnit &currency, UErrorCode &status) const {
191
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
192
0
    U_ASSERT(fType == RND_CURRENCY);
193
0
    const char16_t *isoCode = currency.getISOCurrency();
194
0
    double increment = ucurr_getRoundingIncrementForUsage(isoCode, fUnion.currencyUsage, &status);
195
0
    int32_t minMaxFrac = ucurr_getDefaultFractionDigitsForUsage(
196
0
            isoCode, fUnion.currencyUsage, &status);
197
0
    if (increment != 0.0) {
198
0
        return constructIncrement(increment, minMaxFrac);
199
0
    } else {
200
0
        return constructFraction(minMaxFrac, minMaxFrac);
201
0
    }
202
0
}
203
204
// Public method on CurrencyPrecision subclass
205
0
Precision CurrencyPrecision::withCurrency(const CurrencyUnit &currency) const {
206
0
    UErrorCode localStatus = U_ZERO_ERROR;
207
0
    Precision result = Precision::withCurrency(currency, localStatus);
208
0
    if (U_FAILURE(localStatus)) {
209
0
        return {localStatus};
210
0
    }
211
0
    return result;
212
0
}
213
214
0
Precision IncrementPrecision::withMinFraction(int32_t minFrac) const {
215
0
    if (fType == RND_ERROR) { return *this; } // no-op in error state
216
0
    if (minFrac >= 0 && minFrac <= kMaxIntFracSig) {
217
0
        return constructIncrement(fUnion.increment.fIncrement, minFrac);
218
0
    } else {
219
0
        return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
220
0
    }
221
0
}
222
223
0
FractionPrecision Precision::constructFraction(int32_t minFrac, int32_t maxFrac) {
224
0
    FractionSignificantSettings settings;
225
0
    settings.fMinFrac = static_cast<digits_t>(minFrac);
226
0
    settings.fMaxFrac = static_cast<digits_t>(maxFrac);
227
0
    settings.fMinSig = -1;
228
0
    settings.fMaxSig = -1;
229
0
    PrecisionUnion union_;
230
0
    union_.fracSig = settings;
231
0
    return {RND_FRACTION, union_, kDefaultMode};
232
0
}
233
234
0
Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) {
235
0
    FractionSignificantSettings settings;
236
0
    settings.fMinFrac = -1;
237
0
    settings.fMaxFrac = -1;
238
0
    settings.fMinSig = static_cast<digits_t>(minSig);
239
0
    settings.fMaxSig = static_cast<digits_t>(maxSig);
240
0
    PrecisionUnion union_;
241
0
    union_.fracSig = settings;
242
0
    return {RND_SIGNIFICANT, union_, kDefaultMode};
243
0
}
244
245
Precision
246
0
Precision::constructFractionSignificant(const FractionPrecision &base, int32_t minSig, int32_t maxSig) {
247
0
    FractionSignificantSettings settings = base.fUnion.fracSig;
248
0
    settings.fMinSig = static_cast<digits_t>(minSig);
249
0
    settings.fMaxSig = static_cast<digits_t>(maxSig);
250
0
    PrecisionUnion union_;
251
0
    union_.fracSig = settings;
252
0
    return {RND_FRACTION_SIGNIFICANT, union_, kDefaultMode};
253
0
}
254
255
0
IncrementPrecision Precision::constructIncrement(double increment, int32_t minFrac) {
256
0
    IncrementSettings settings;
257
0
    settings.fIncrement = increment;
258
0
    settings.fMinFrac = static_cast<digits_t>(minFrac);
259
0
    // One of the few pre-computed quantities:
260
0
    // Note: it is possible for minFrac to be more than maxFrac... (misleading)
261
0
    settings.fMaxFrac = roundingutils::doubleFractionLength(increment);
262
0
    PrecisionUnion union_;
263
0
    union_.increment = settings;
264
0
    return {RND_INCREMENT, union_, kDefaultMode};
265
0
}
266
267
0
CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) {
268
0
    PrecisionUnion union_;
269
0
    union_.currencyUsage = usage;
270
0
    return {RND_CURRENCY, union_, kDefaultMode};
271
0
}
272
273
274
RoundingImpl::RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode,
275
                           const CurrencyUnit& currency, UErrorCode& status)
276
0
        : fPrecision(precision), fRoundingMode(roundingMode), fPassThrough(false) {
277
0
    if (precision.fType == Precision::RND_CURRENCY) {
278
0
        fPrecision = precision.withCurrency(currency, status);
279
0
    }
280
0
}
281
282
0
RoundingImpl RoundingImpl::passThrough() {
283
0
    RoundingImpl retval;
284
0
    retval.fPassThrough = true;
285
0
    return retval;
286
0
}
287
288
0
bool RoundingImpl::isSignificantDigits() const {
289
0
    return fPrecision.fType == Precision::RND_SIGNIFICANT;
290
0
}
291
292
int32_t
293
RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
294
0
                                  UErrorCode &status) {
295
0
    // Do not call this method with zero.
296
0
    U_ASSERT(!input.isZero());
297
0
298
0
    // Perform the first attempt at rounding.
299
0
    int magnitude = input.getMagnitude();
300
0
    int multiplier = producer.getMultiplier(magnitude);
301
0
    input.adjustMagnitude(multiplier);
302
0
    apply(input, status);
303
0
304
0
    // If the number rounded to zero, exit.
305
0
    if (input.isZero() || U_FAILURE(status)) {
306
0
        return multiplier;
307
0
    }
308
0
309
0
    // If the new magnitude after rounding is the same as it was before rounding, then we are done.
310
0
    // This case applies to most numbers.
311
0
    if (input.getMagnitude() == magnitude + multiplier) {
312
0
        return multiplier;
313
0
    }
314
0
315
0
    // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000:
316
0
    // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't,
317
0
    // we do not need to make any more adjustments.
318
0
    int _multiplier = producer.getMultiplier(magnitude + 1);
319
0
    if (multiplier == _multiplier) {
320
0
        return multiplier;
321
0
    }
322
0
323
0
    // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000".
324
0
    // Fix the magnitude and re-apply the rounding strategy.
325
0
    input.adjustMagnitude(_multiplier - multiplier);
326
0
    apply(input, status);
327
0
    return _multiplier;
328
0
}
329
330
/** This is the method that contains the actual rounding logic. */
331
0
void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const {
332
0
    if (fPassThrough) {
333
0
        return;
334
0
    }
335
0
    switch (fPrecision.fType) {
336
0
        case Precision::RND_BOGUS:
337
0
        case Precision::RND_ERROR:
338
0
            // Errors should be caught before the apply() method is called
339
0
            status = U_INTERNAL_PROGRAM_ERROR;
340
0
            break;
341
0
342
0
        case Precision::RND_NONE:
343
0
            value.roundToInfinity();
344
0
            break;
345
0
346
0
        case Precision::RND_FRACTION:
347
0
            value.roundToMagnitude(
348
0
                    getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac),
349
0
                    fRoundingMode,
350
0
                    status);
351
0
            value.setFractionLength(
352
0
                    uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)),
353
0
                    INT32_MAX);
354
0
            break;
355
0
356
0
        case Precision::RND_SIGNIFICANT:
357
0
            value.roundToMagnitude(
358
0
                    getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig),
359
0
                    fRoundingMode,
360
0
                    status);
361
0
            value.setFractionLength(
362
0
                    uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)),
363
0
                    INT32_MAX);
364
0
            // Make sure that digits are displayed on zero.
365
0
            if (value.isZero() && fPrecision.fUnion.fracSig.fMinSig > 0) {
366
0
                value.setIntegerLength(1, INT32_MAX);
367
0
            }
368
0
            break;
369
0
370
0
        case Precision::RND_FRACTION_SIGNIFICANT: {
371
0
            int32_t displayMag = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac);
372
0
            int32_t roundingMag = getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac);
373
0
            if (fPrecision.fUnion.fracSig.fMinSig == -1) {
374
0
                // Max Sig override
375
0
                int32_t candidate = getRoundingMagnitudeSignificant(
376
0
                        value,
377
0
                        fPrecision.fUnion.fracSig.fMaxSig);
378
0
                roundingMag = uprv_max(roundingMag, candidate);
379
0
            } else {
380
0
                // Min Sig override
381
0
                int32_t candidate = getDisplayMagnitudeSignificant(
382
0
                        value,
383
0
                        fPrecision.fUnion.fracSig.fMinSig);
384
0
                roundingMag = uprv_min(roundingMag, candidate);
385
0
            }
386
0
            value.roundToMagnitude(roundingMag, fRoundingMode, status);
387
0
            value.setFractionLength(uprv_max(0, -displayMag), INT32_MAX);
388
0
            break;
389
0
        }
390
0
391
0
        case Precision::RND_INCREMENT:
392
0
            value.roundToIncrement(
393
0
                    fPrecision.fUnion.increment.fIncrement,
394
0
                    fRoundingMode,
395
0
                    fPrecision.fUnion.increment.fMaxFrac,
396
0
                    status);
397
0
            value.setFractionLength(fPrecision.fUnion.increment.fMinFrac, INT32_MAX);
398
0
            break;
399
0
400
0
        case Precision::RND_CURRENCY:
401
0
            // Call .withCurrency() before .apply()!
402
0
            U_ASSERT(false);
403
0
            break;
404
0
    }
405
0
}
406
407
0
void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) {
408
0
    // This method is intended for the one specific purpose of helping print "00.000E0".
409
0
    U_ASSERT(isSignificantDigits());
410
0
    U_ASSERT(value.isZero());
411
0
    value.setFractionLength(fPrecision.fUnion.fracSig.fMinSig - minInt, INT32_MAX);
412
0
}
413
414
#endif /* #if !UCONFIG_NO_FORMATTING */