/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 ¤cy, 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 ¤cy) 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 */ |