]> git.saurik.com Git - apple/icu.git/blob - icuSources/i18n/number_rounding.cpp
ICU-64243.0.1.tar.gz
[apple/icu.git] / icuSources / i18n / number_rounding.cpp
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 int32_t getRoundingMagnitudeFraction(int maxFrac) {
26 if (maxFrac == -1) {
27 return INT32_MIN;
28 }
29 return -maxFrac;
30 }
31
32 int32_t getRoundingMagnitudeSignificant(const DecimalQuantity &value, int maxSig) {
33 if (maxSig == -1) {
34 return INT32_MIN;
35 }
36 int magnitude = value.isZero() ? 0 : value.getMagnitude();
37 return magnitude - maxSig + 1;
38 }
39
40 int32_t getDisplayMagnitudeFraction(int minFrac) {
41 if (minFrac == 0) {
42 return INT32_MAX;
43 }
44 return -minFrac;
45 }
46
47 int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) {
48 int magnitude = value.isZero() ? 0 : value.getMagnitude();
49 return magnitude - minSig + 1;
50 }
51
52 }
53
54
55 MultiplierProducer::~MultiplierProducer() = default;
56
57
58 digits_t roundingutils::doubleFractionLength(double input, int8_t* singleDigit) {
59 char buffer[DoubleToStringConverter::kBase10MaximalLength + 1];
60 bool sign; // unused; always positive
61 int32_t length;
62 int32_t point;
63 DoubleToStringConverter::DoubleToAscii(
64 input,
65 DoubleToStringConverter::DtoaMode::SHORTEST,
66 0,
67 buffer,
68 sizeof(buffer),
69 &sign,
70 &length,
71 &point
72 );
73
74 if (singleDigit == nullptr) {
75 // no-op
76 } else if (length == 1) {
77 *singleDigit = buffer[0] - '0';
78 } else {
79 *singleDigit = -1;
80 }
81
82 return static_cast<digits_t>(length - point);
83 }
84
85
86 Precision Precision::unlimited() {
87 return Precision(RND_NONE, {}, kDefaultMode);
88 }
89
90 FractionPrecision Precision::integer() {
91 return constructFraction(0, 0);
92 }
93
94 FractionPrecision Precision::fixedFraction(int32_t minMaxFractionPlaces) {
95 if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= kMaxIntFracSig) {
96 return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces);
97 } else {
98 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
99 }
100 }
101
102 FractionPrecision Precision::minFraction(int32_t minFractionPlaces) {
103 if (minFractionPlaces >= 0 && minFractionPlaces <= kMaxIntFracSig) {
104 return constructFraction(minFractionPlaces, -1);
105 } else {
106 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
107 }
108 }
109
110 FractionPrecision Precision::maxFraction(int32_t maxFractionPlaces) {
111 if (maxFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig) {
112 return constructFraction(0, maxFractionPlaces);
113 } else {
114 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
115 }
116 }
117
118 FractionPrecision Precision::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) {
119 if (minFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig &&
120 minFractionPlaces <= maxFractionPlaces) {
121 return constructFraction(minFractionPlaces, maxFractionPlaces);
122 } else {
123 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
124 }
125 }
126
127 Precision Precision::fixedSignificantDigits(int32_t minMaxSignificantDigits) {
128 if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= kMaxIntFracSig) {
129 return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits);
130 } else {
131 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
132 }
133 }
134
135 Precision Precision::minSignificantDigits(int32_t minSignificantDigits) {
136 if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) {
137 return constructSignificant(minSignificantDigits, -1);
138 } else {
139 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
140 }
141 }
142
143 Precision Precision::maxSignificantDigits(int32_t maxSignificantDigits) {
144 if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) {
145 return constructSignificant(1, maxSignificantDigits);
146 } else {
147 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
148 }
149 }
150
151 Precision Precision::minMaxSignificantDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) {
152 if (minSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig &&
153 minSignificantDigits <= maxSignificantDigits) {
154 return constructSignificant(minSignificantDigits, maxSignificantDigits);
155 } else {
156 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
157 }
158 }
159
160 IncrementPrecision Precision::increment(double roundingIncrement) {
161 if (roundingIncrement > 0.0) {
162 return constructIncrement(roundingIncrement, 0);
163 } else {
164 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
165 }
166 }
167
168 CurrencyPrecision Precision::currency(UCurrencyUsage currencyUsage) {
169 return constructCurrency(currencyUsage);
170 }
171
172 Precision FractionPrecision::withMinDigits(int32_t minSignificantDigits) const {
173 if (fType == RND_ERROR) { return *this; } // no-op in error state
174 if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) {
175 return constructFractionSignificant(*this, minSignificantDigits, -1);
176 } else {
177 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
178 }
179 }
180
181 Precision FractionPrecision::withMaxDigits(int32_t maxSignificantDigits) const {
182 if (fType == RND_ERROR) { return *this; } // no-op in error state
183 if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) {
184 return constructFractionSignificant(*this, -1, maxSignificantDigits);
185 } else {
186 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
187 }
188 }
189
190 // Private method on base class
191 Precision Precision::withCurrency(const CurrencyUnit &currency, UErrorCode &status) const {
192 if (fType == RND_ERROR) { return *this; } // no-op in error state
193 U_ASSERT(fType == RND_CURRENCY);
194 const char16_t *isoCode = currency.getISOCurrency();
195 double increment = ucurr_getRoundingIncrementForUsage(isoCode, fUnion.currencyUsage, &status);
196 int32_t minMaxFrac = ucurr_getDefaultFractionDigitsForUsage(
197 isoCode, fUnion.currencyUsage, &status);
198 if (increment != 0.0) {
199 return constructIncrement(increment, minMaxFrac);
200 } else {
201 return constructFraction(minMaxFrac, minMaxFrac);
202 }
203 }
204
205 // Public method on CurrencyPrecision subclass
206 Precision CurrencyPrecision::withCurrency(const CurrencyUnit &currency) const {
207 UErrorCode localStatus = U_ZERO_ERROR;
208 Precision result = Precision::withCurrency(currency, localStatus);
209 if (U_FAILURE(localStatus)) {
210 return {localStatus};
211 }
212 return result;
213 }
214
215 Precision IncrementPrecision::withMinFraction(int32_t minFrac) const {
216 if (fType == RND_ERROR) { return *this; } // no-op in error state
217 if (minFrac >= 0 && minFrac <= kMaxIntFracSig) {
218 return constructIncrement(fUnion.increment.fIncrement, minFrac);
219 } else {
220 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
221 }
222 }
223
224 FractionPrecision Precision::constructFraction(int32_t minFrac, int32_t maxFrac) {
225 FractionSignificantSettings settings;
226 settings.fMinFrac = static_cast<digits_t>(minFrac);
227 settings.fMaxFrac = static_cast<digits_t>(maxFrac);
228 settings.fMinSig = -1;
229 settings.fMaxSig = -1;
230 PrecisionUnion union_;
231 union_.fracSig = settings;
232 return {RND_FRACTION, union_, kDefaultMode};
233 }
234
235 Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) {
236 FractionSignificantSettings settings;
237 settings.fMinFrac = -1;
238 settings.fMaxFrac = -1;
239 settings.fMinSig = static_cast<digits_t>(minSig);
240 settings.fMaxSig = static_cast<digits_t>(maxSig);
241 PrecisionUnion union_;
242 union_.fracSig = settings;
243 return {RND_SIGNIFICANT, union_, kDefaultMode};
244 }
245
246 Precision Precision::constructIncrementSignificant(double increment, int32_t minSig, int32_t maxSig) { // Apple rdar://52538227
247 IncrementSignificantSettings settings;
248 settings.fIncrement = increment;
249 settings.fMinSig = static_cast<digits_t>(minSig);
250 settings.fMaxSig = static_cast<digits_t>(maxSig);
251 PrecisionUnion union_;
252 union_.incrSig = settings;
253 return {RND_INCREMENT_SIGNIFICANT, union_, kDefaultMode};
254 }
255
256 Precision
257 Precision::constructFractionSignificant(const FractionPrecision &base, int32_t minSig, int32_t maxSig) {
258 FractionSignificantSettings settings = base.fUnion.fracSig;
259 settings.fMinSig = static_cast<digits_t>(minSig);
260 settings.fMaxSig = static_cast<digits_t>(maxSig);
261 PrecisionUnion union_;
262 union_.fracSig = settings;
263 return {RND_FRACTION_SIGNIFICANT, union_, kDefaultMode};
264 }
265
266 IncrementPrecision Precision::constructIncrement(double increment, int32_t minFrac) {
267 IncrementSettings settings;
268 // Note: For number formatting, fIncrement is used for RND_INCREMENT but not
269 // RND_INCREMENT_ONE or RND_INCREMENT_FIVE. However, fIncrement is used in all
270 // three when constructing a skeleton.
271 settings.fIncrement = increment;
272 settings.fMinFrac = static_cast<digits_t>(minFrac);
273 // One of the few pre-computed quantities:
274 // Note: it is possible for minFrac to be more than maxFrac... (misleading)
275 int8_t singleDigit;
276 settings.fMaxFrac = roundingutils::doubleFractionLength(increment, &singleDigit);
277 PrecisionUnion union_;
278 union_.increment = settings;
279 if (singleDigit == 1) {
280 // NOTE: In C++, we must return the correct value type with the correct union.
281 // It would be invalid to return a RND_FRACTION here because the methods on the
282 // IncrementPrecision type assume that the union is backed by increment data.
283 return {RND_INCREMENT_ONE, union_, kDefaultMode};
284 } else if (singleDigit == 5) {
285 return {RND_INCREMENT_FIVE, union_, kDefaultMode};
286 } else {
287 return {RND_INCREMENT, union_, kDefaultMode};
288 }
289 }
290
291 CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) {
292 PrecisionUnion union_;
293 union_.currencyUsage = usage;
294 return {RND_CURRENCY, union_, kDefaultMode};
295 }
296
297
298 RoundingImpl::RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode,
299 const CurrencyUnit& currency, UErrorCode& status)
300 : fPrecision(precision), fRoundingMode(roundingMode), fPassThrough(false) {
301 if (precision.fType == Precision::RND_CURRENCY) {
302 fPrecision = precision.withCurrency(currency, status);
303 }
304 }
305
306 RoundingImpl RoundingImpl::passThrough() {
307 RoundingImpl retval;
308 retval.fPassThrough = true;
309 return retval;
310 }
311
312 bool RoundingImpl::isSignificantDigits() const {
313 return fPrecision.fType == Precision::RND_SIGNIFICANT;
314 }
315
316 int32_t
317 RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
318 UErrorCode &status) {
319 // Do not call this method with zero.
320 U_ASSERT(!input.isZero());
321
322 // Perform the first attempt at rounding.
323 int magnitude = input.getMagnitude();
324 int multiplier = producer.getMultiplier(magnitude);
325 input.adjustMagnitude(multiplier);
326 apply(input, status);
327
328 // If the number rounded to zero, exit.
329 if (input.isZero() || U_FAILURE(status)) {
330 return multiplier;
331 }
332
333 // If the new magnitude after rounding is the same as it was before rounding, then we are done.
334 // This case applies to most numbers.
335 if (input.getMagnitude() == magnitude + multiplier) {
336 return multiplier;
337 }
338
339 // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000:
340 // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't,
341 // we do not need to make any more adjustments.
342 int _multiplier = producer.getMultiplier(magnitude + 1);
343 if (multiplier == _multiplier) {
344 return multiplier;
345 }
346
347 // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000".
348 // Fix the magnitude and re-apply the rounding strategy.
349 input.adjustMagnitude(_multiplier - multiplier);
350 apply(input, status);
351 return _multiplier;
352 }
353
354 /** This is the method that contains the actual rounding logic. */
355 void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const {
356 if (fPassThrough) {
357 return;
358 }
359 switch (fPrecision.fType) {
360 case Precision::RND_BOGUS:
361 case Precision::RND_ERROR:
362 // Errors should be caught before the apply() method is called
363 status = U_INTERNAL_PROGRAM_ERROR;
364 break;
365
366 case Precision::RND_NONE:
367 value.roundToInfinity();
368 break;
369
370 case Precision::RND_FRACTION:
371 value.roundToMagnitude(
372 getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac),
373 fRoundingMode,
374 status);
375 value.setMinFraction(
376 uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)));
377 break;
378
379 case Precision::RND_SIGNIFICANT:
380 value.roundToMagnitude(
381 getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig),
382 fRoundingMode,
383 status);
384 value.setMinFraction(
385 uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)));
386 // Make sure that digits are displayed on zero.
387 if (value.isZero() && fPrecision.fUnion.fracSig.fMinSig > 0) {
388 value.setMinInteger(1);
389 }
390 break;
391
392 case Precision::RND_FRACTION_SIGNIFICANT: {
393 int32_t displayMag = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac);
394 int32_t roundingMag = getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac);
395 if (fPrecision.fUnion.fracSig.fMinSig == -1) {
396 // Max Sig override
397 int32_t candidate = getRoundingMagnitudeSignificant(
398 value,
399 fPrecision.fUnion.fracSig.fMaxSig);
400 roundingMag = uprv_max(roundingMag, candidate);
401 } else {
402 // Min Sig override
403 int32_t candidate = getDisplayMagnitudeSignificant(
404 value,
405 fPrecision.fUnion.fracSig.fMinSig);
406 roundingMag = uprv_min(roundingMag, candidate);
407 }
408 value.roundToMagnitude(roundingMag, fRoundingMode, status);
409 value.setMinFraction(uprv_max(0, -displayMag));
410 break;
411 }
412
413 case Precision::RND_INCREMENT:
414 value.roundToIncrement(
415 fPrecision.fUnion.increment.fIncrement,
416 fRoundingMode,
417 status);
418 value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
419 break;
420
421 case Precision::RND_INCREMENT_ONE:
422 value.roundToMagnitude(
423 -fPrecision.fUnion.increment.fMaxFrac,
424 fRoundingMode,
425 status);
426 value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
427 break;
428
429 case Precision::RND_INCREMENT_FIVE:
430 value.roundToNickel(
431 -fPrecision.fUnion.increment.fMaxFrac,
432 fRoundingMode,
433 status);
434 value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
435 break;
436
437 case Precision::RND_CURRENCY:
438 // Call .withCurrency() before .apply()!
439 UPRV_UNREACHABLE;
440
441 case Precision::RND_INCREMENT_SIGNIFICANT: // Apple addition rdar://52538227
442 // FIrst round to increment
443 value.roundToIncrement(
444 fPrecision.fUnion.incrSig.fIncrement,
445 fRoundingMode,
446 status);
447 // Then round to significant digits
448 value.roundToMagnitude(
449 getRoundingMagnitudeSignificant(value, fPrecision.fUnion.incrSig.fMaxSig),
450 fRoundingMode,
451 status);
452 value.setMinFraction(
453 uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.incrSig.fMinSig)));
454 // Make sure that digits are displayed on zero.
455 if (value.isZero() && fPrecision.fUnion.incrSig.fMinSig > 0) {
456 value.setMinInteger(1);
457 }
458 break;
459
460 default:
461 UPRV_UNREACHABLE;
462 }
463 }
464
465 void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) {
466 // This method is intended for the one specific purpose of helping print "00.000E0".
467 U_ASSERT(isSignificantDigits());
468 U_ASSERT(value.isZero());
469 value.setMinFraction(fPrecision.fUnion.fracSig.fMinSig - minInt);
470 }
471
472 #endif /* #if !UCONFIG_NO_FORMATTING */