• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "charstr.h"
9 #include "uassert.h"
10 #include "unicode/numberformatter.h"
11 #include "number_types.h"
12 #include "number_decimalquantity.h"
13 #include "double-conversion.h"
14 #include "number_roundingutils.h"
15 #include "number_skeletons.h"
16 #include "number_decnum.h"
17 #include "putilimp.h"
18 #include "string_segment.h"
19 
20 using namespace icu;
21 using namespace icu::number;
22 using namespace icu::number::impl;
23 
24 
25 using double_conversion::DoubleToStringConverter;
26 using icu::StringSegment;
27 
parseIncrementOption(const StringSegment & segment,Precision & outPrecision,UErrorCode & status)28 void number::impl::parseIncrementOption(const StringSegment &segment,
29                                         Precision &outPrecision,
30                                         UErrorCode &status) {
31     // Need to do char <-> UChar conversion...
32     U_ASSERT(U_SUCCESS(status));
33     CharString buffer;
34     SKELETON_UCHAR_TO_CHAR(buffer, segment.toTempUnicodeString(), 0, segment.length(), status);
35 
36     // Utilize DecimalQuantity/decNumber to parse this for us.
37     DecimalQuantity dq;
38     UErrorCode localStatus = U_ZERO_ERROR;
39     DecNum decnum;
40     decnum.setTo({buffer.data(), buffer.length()}, localStatus);
41     dq.setToDecNum(decnum, localStatus);
42     if (U_FAILURE(localStatus) || decnum.isSpecial()) {
43         // throw new SkeletonSyntaxException("Invalid rounding increment", segment, e);
44         status = U_NUMBER_SKELETON_SYNTAX_ERROR;
45         return;
46     }
47     double increment = dq.toDouble();
48 
49     // We also need to figure out how many digits. Do a brute force string operation.
50     int decimalOffset = 0;
51     while (decimalOffset < segment.length() && segment.charAt(decimalOffset) != '.') {
52         decimalOffset++;
53     }
54     if (decimalOffset == segment.length()) {
55         outPrecision = Precision::increment(increment);
56     } else {
57         int32_t fractionLength = segment.length() - decimalOffset - 1;
58         outPrecision = Precision::increment(increment).withMinFraction(fractionLength);
59     }
60 }
61 
62 namespace {
63 
getRoundingMagnitudeFraction(int maxFrac)64 int32_t getRoundingMagnitudeFraction(int maxFrac) {
65     if (maxFrac == -1) {
66         return INT32_MIN;
67     }
68     return -maxFrac;
69 }
70 
getRoundingMagnitudeSignificant(const DecimalQuantity & value,int maxSig)71 int32_t getRoundingMagnitudeSignificant(const DecimalQuantity &value, int maxSig) {
72     if (maxSig == -1) {
73         return INT32_MIN;
74     }
75     int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
76     return magnitude - maxSig + 1;
77 }
78 
getDisplayMagnitudeFraction(int minFrac)79 int32_t getDisplayMagnitudeFraction(int minFrac) {
80     if (minFrac == 0) {
81         return INT32_MAX;
82     }
83     return -minFrac;
84 }
85 
getDisplayMagnitudeSignificant(const DecimalQuantity & value,int minSig)86 int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) {
87     int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
88     return magnitude - minSig + 1;
89 }
90 
91 }
92 
93 
94 MultiplierProducer::~MultiplierProducer() = default;
95 
96 
doubleFractionLength(double input,int8_t * singleDigit)97 digits_t roundingutils::doubleFractionLength(double input, int8_t* singleDigit) {
98     char buffer[DoubleToStringConverter::kBase10MaximalLength + 1];
99     bool sign; // unused; always positive
100     int32_t length;
101     int32_t point;
102     DoubleToStringConverter::DoubleToAscii(
103             input,
104             DoubleToStringConverter::DtoaMode::SHORTEST,
105             0,
106             buffer,
107             sizeof(buffer),
108             &sign,
109             &length,
110             &point
111     );
112 
113     if (singleDigit == nullptr) {
114         // no-op
115     } else if (length == 1) {
116         *singleDigit = buffer[0] - '0';
117     } else {
118         *singleDigit = -1;
119     }
120 
121     return static_cast<digits_t>(length - point);
122 }
123 
124 
unlimited()125 Precision Precision::unlimited() {
126     return Precision(RND_NONE, {});
127 }
128 
integer()129 FractionPrecision Precision::integer() {
130     return constructFraction(0, 0);
131 }
132 
fixedFraction(int32_t minMaxFractionPlaces)133 FractionPrecision Precision::fixedFraction(int32_t minMaxFractionPlaces) {
134     if (minMaxFractionPlaces >= 0 && minMaxFractionPlaces <= kMaxIntFracSig) {
135         return constructFraction(minMaxFractionPlaces, minMaxFractionPlaces);
136     } else {
137         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
138     }
139 }
140 
minFraction(int32_t minFractionPlaces)141 FractionPrecision Precision::minFraction(int32_t minFractionPlaces) {
142     if (minFractionPlaces >= 0 && minFractionPlaces <= kMaxIntFracSig) {
143         return constructFraction(minFractionPlaces, -1);
144     } else {
145         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
146     }
147 }
148 
maxFraction(int32_t maxFractionPlaces)149 FractionPrecision Precision::maxFraction(int32_t maxFractionPlaces) {
150     if (maxFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig) {
151         return constructFraction(0, maxFractionPlaces);
152     } else {
153         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
154     }
155 }
156 
minMaxFraction(int32_t minFractionPlaces,int32_t maxFractionPlaces)157 FractionPrecision Precision::minMaxFraction(int32_t minFractionPlaces, int32_t maxFractionPlaces) {
158     if (minFractionPlaces >= 0 && maxFractionPlaces <= kMaxIntFracSig &&
159         minFractionPlaces <= maxFractionPlaces) {
160         return constructFraction(minFractionPlaces, maxFractionPlaces);
161     } else {
162         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
163     }
164 }
165 
fixedSignificantDigits(int32_t minMaxSignificantDigits)166 Precision Precision::fixedSignificantDigits(int32_t minMaxSignificantDigits) {
167     if (minMaxSignificantDigits >= 1 && minMaxSignificantDigits <= kMaxIntFracSig) {
168         return constructSignificant(minMaxSignificantDigits, minMaxSignificantDigits);
169     } else {
170         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
171     }
172 }
173 
minSignificantDigits(int32_t minSignificantDigits)174 Precision Precision::minSignificantDigits(int32_t minSignificantDigits) {
175     if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) {
176         return constructSignificant(minSignificantDigits, -1);
177     } else {
178         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
179     }
180 }
181 
maxSignificantDigits(int32_t maxSignificantDigits)182 Precision Precision::maxSignificantDigits(int32_t maxSignificantDigits) {
183     if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) {
184         return constructSignificant(1, maxSignificantDigits);
185     } else {
186         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
187     }
188 }
189 
minMaxSignificantDigits(int32_t minSignificantDigits,int32_t maxSignificantDigits)190 Precision Precision::minMaxSignificantDigits(int32_t minSignificantDigits, int32_t maxSignificantDigits) {
191     if (minSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig &&
192         minSignificantDigits <= maxSignificantDigits) {
193         return constructSignificant(minSignificantDigits, maxSignificantDigits);
194     } else {
195         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
196     }
197 }
198 
trailingZeroDisplay(UNumberTrailingZeroDisplay trailingZeroDisplay) const199 Precision Precision::trailingZeroDisplay(UNumberTrailingZeroDisplay trailingZeroDisplay) const {
200     Precision result(*this); // copy constructor
201     result.fTrailingZeroDisplay = trailingZeroDisplay;
202     return result;
203 }
204 
increment(double roundingIncrement)205 IncrementPrecision Precision::increment(double roundingIncrement) {
206     if (roundingIncrement > 0.0) {
207         return constructIncrement(roundingIncrement, 0);
208     } else {
209         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
210     }
211 }
212 
currency(UCurrencyUsage currencyUsage)213 CurrencyPrecision Precision::currency(UCurrencyUsage currencyUsage) {
214     return constructCurrency(currencyUsage);
215 }
216 
withSignificantDigits(int32_t minSignificantDigits,int32_t maxSignificantDigits,UNumberRoundingPriority priority) const217 Precision FractionPrecision::withSignificantDigits(
218         int32_t minSignificantDigits,
219         int32_t maxSignificantDigits,
220         UNumberRoundingPriority priority) const {
221     if (fType == RND_ERROR) { return *this; } // no-op in error state
222     if (minSignificantDigits >= 1 &&
223             maxSignificantDigits >= minSignificantDigits &&
224             maxSignificantDigits <= kMaxIntFracSig) {
225         return constructFractionSignificant(
226             *this,
227             minSignificantDigits,
228             maxSignificantDigits,
229             priority);
230     } else {
231         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
232     }
233 }
234 
withMinDigits(int32_t minSignificantDigits) const235 Precision FractionPrecision::withMinDigits(int32_t minSignificantDigits) const {
236     if (fType == RND_ERROR) { return *this; } // no-op in error state
237     if (minSignificantDigits >= 1 && minSignificantDigits <= kMaxIntFracSig) {
238         return constructFractionSignificant(
239             *this,
240             1,
241             minSignificantDigits,
242             UNUM_ROUNDING_PRIORITY_RELAXED);
243     } else {
244         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
245     }
246 }
247 
withMaxDigits(int32_t maxSignificantDigits) const248 Precision FractionPrecision::withMaxDigits(int32_t maxSignificantDigits) const {
249     if (fType == RND_ERROR) { return *this; } // no-op in error state
250     if (maxSignificantDigits >= 1 && maxSignificantDigits <= kMaxIntFracSig) {
251         return constructFractionSignificant(*this,
252             1,
253             maxSignificantDigits,
254             UNUM_ROUNDING_PRIORITY_STRICT);
255     } else {
256         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
257     }
258 }
259 
260 // Private method on base class
withCurrency(const CurrencyUnit & currency,UErrorCode & status) const261 Precision Precision::withCurrency(const CurrencyUnit &currency, UErrorCode &status) const {
262     if (fType == RND_ERROR) { return *this; } // no-op in error state
263     U_ASSERT(fType == RND_CURRENCY);
264     const char16_t *isoCode = currency.getISOCurrency();
265     double increment = ucurr_getRoundingIncrementForUsage(isoCode, fUnion.currencyUsage, &status);
266     int32_t minMaxFrac = ucurr_getDefaultFractionDigitsForUsage(
267             isoCode, fUnion.currencyUsage, &status);
268     Precision retval = (increment != 0.0)
269         ? static_cast<Precision>(constructIncrement(increment, minMaxFrac))
270         : static_cast<Precision>(constructFraction(minMaxFrac, minMaxFrac));
271     retval.fTrailingZeroDisplay = fTrailingZeroDisplay;
272     return retval;
273 }
274 
275 // Public method on CurrencyPrecision subclass
withCurrency(const CurrencyUnit & currency) const276 Precision CurrencyPrecision::withCurrency(const CurrencyUnit &currency) const {
277     UErrorCode localStatus = U_ZERO_ERROR;
278     Precision result = Precision::withCurrency(currency, localStatus);
279     if (U_FAILURE(localStatus)) {
280         return {localStatus};
281     }
282     return result;
283 }
284 
withMinFraction(int32_t minFrac) const285 Precision IncrementPrecision::withMinFraction(int32_t minFrac) const {
286     if (fType == RND_ERROR) { return *this; } // no-op in error state
287     if (minFrac >= 0 && minFrac <= kMaxIntFracSig) {
288         return constructIncrement(fUnion.increment.fIncrement, minFrac);
289     } else {
290         return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR};
291     }
292 }
293 
constructFraction(int32_t minFrac,int32_t maxFrac)294 FractionPrecision Precision::constructFraction(int32_t minFrac, int32_t maxFrac) {
295     FractionSignificantSettings settings;
296     settings.fMinFrac = static_cast<digits_t>(minFrac);
297     settings.fMaxFrac = static_cast<digits_t>(maxFrac);
298     settings.fMinSig = -1;
299     settings.fMaxSig = -1;
300     PrecisionUnion union_;
301     union_.fracSig = settings;
302     return {RND_FRACTION, union_};
303 }
304 
constructSignificant(int32_t minSig,int32_t maxSig)305 Precision Precision::constructSignificant(int32_t minSig, int32_t maxSig) {
306     FractionSignificantSettings settings;
307     settings.fMinFrac = -1;
308     settings.fMaxFrac = -1;
309     settings.fMinSig = static_cast<digits_t>(minSig);
310     settings.fMaxSig = static_cast<digits_t>(maxSig);
311     PrecisionUnion union_;
312     union_.fracSig = settings;
313     return {RND_SIGNIFICANT, union_};
314 }
315 
316 Precision
constructFractionSignificant(const FractionPrecision & base,int32_t minSig,int32_t maxSig,UNumberRoundingPriority priority)317 Precision::constructFractionSignificant(
318         const FractionPrecision &base,
319         int32_t minSig,
320         int32_t maxSig,
321         UNumberRoundingPriority priority) {
322     FractionSignificantSettings settings = base.fUnion.fracSig;
323     settings.fMinSig = static_cast<digits_t>(minSig);
324     settings.fMaxSig = static_cast<digits_t>(maxSig);
325     settings.fPriority = priority;
326     PrecisionUnion union_;
327     union_.fracSig = settings;
328     return {RND_FRACTION_SIGNIFICANT, union_};
329 }
330 
constructIncrement(double increment,int32_t minFrac)331 IncrementPrecision Precision::constructIncrement(double increment, int32_t minFrac) {
332     IncrementSettings settings;
333     // Note: For number formatting, fIncrement is used for RND_INCREMENT but not
334     // RND_INCREMENT_ONE or RND_INCREMENT_FIVE. However, fIncrement is used in all
335     // three when constructing a skeleton.
336     settings.fIncrement = increment;
337     settings.fMinFrac = static_cast<digits_t>(minFrac);
338     // One of the few pre-computed quantities:
339     // Note: it is possible for minFrac to be more than maxFrac... (misleading)
340     int8_t singleDigit;
341     settings.fMaxFrac = roundingutils::doubleFractionLength(increment, &singleDigit);
342     PrecisionUnion union_;
343     union_.increment = settings;
344     if (singleDigit == 1) {
345         // NOTE: In C++, we must return the correct value type with the correct union.
346         // It would be invalid to return a RND_FRACTION here because the methods on the
347         // IncrementPrecision type assume that the union is backed by increment data.
348         return {RND_INCREMENT_ONE, union_};
349     } else if (singleDigit == 5) {
350         return {RND_INCREMENT_FIVE, union_};
351     } else {
352         return {RND_INCREMENT, union_};
353     }
354 }
355 
constructCurrency(UCurrencyUsage usage)356 CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) {
357     PrecisionUnion union_;
358     union_.currencyUsage = usage;
359     return {RND_CURRENCY, union_};
360 }
361 
362 
RoundingImpl(const Precision & precision,UNumberFormatRoundingMode roundingMode,const CurrencyUnit & currency,UErrorCode & status)363 RoundingImpl::RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode,
364                            const CurrencyUnit& currency, UErrorCode& status)
365         : fPrecision(precision), fRoundingMode(roundingMode), fPassThrough(false) {
366     if (precision.fType == Precision::RND_CURRENCY) {
367         fPrecision = precision.withCurrency(currency, status);
368     }
369 }
370 
passThrough()371 RoundingImpl RoundingImpl::passThrough() {
372     return {};
373 }
374 
isSignificantDigits() const375 bool RoundingImpl::isSignificantDigits() const {
376     return fPrecision.fType == Precision::RND_SIGNIFICANT;
377 }
378 
379 int32_t
chooseMultiplierAndApply(impl::DecimalQuantity & input,const impl::MultiplierProducer & producer,UErrorCode & status)380 RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
381                                   UErrorCode &status) {
382     // Do not call this method with zero, NaN, or infinity.
383     U_ASSERT(!input.isZeroish());
384 
385     // Perform the first attempt at rounding.
386     int magnitude = input.getMagnitude();
387     int multiplier = producer.getMultiplier(magnitude);
388     input.adjustMagnitude(multiplier);
389     apply(input, status);
390 
391     // If the number rounded to zero, exit.
392     if (input.isZeroish() || U_FAILURE(status)) {
393         return multiplier;
394     }
395 
396     // If the new magnitude after rounding is the same as it was before rounding, then we are done.
397     // This case applies to most numbers.
398     if (input.getMagnitude() == magnitude + multiplier) {
399         return multiplier;
400     }
401 
402     // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000:
403     // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't,
404     // we do not need to make any more adjustments.
405     int _multiplier = producer.getMultiplier(magnitude + 1);
406     if (multiplier == _multiplier) {
407         return multiplier;
408     }
409 
410     // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000".
411     // Fix the magnitude and re-apply the rounding strategy.
412     input.adjustMagnitude(_multiplier - multiplier);
413     apply(input, status);
414     return _multiplier;
415 }
416 
417 /** This is the method that contains the actual rounding logic. */
apply(impl::DecimalQuantity & value,UErrorCode & status) const418 void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const {
419     if (U_FAILURE(status)) {
420         return;
421     }
422     if (fPassThrough) {
423         return;
424     }
425     int32_t resolvedMinFraction = 0;
426     switch (fPrecision.fType) {
427         case Precision::RND_BOGUS:
428         case Precision::RND_ERROR:
429             // Errors should be caught before the apply() method is called
430             status = U_INTERNAL_PROGRAM_ERROR;
431             break;
432 
433         case Precision::RND_NONE:
434             value.roundToInfinity();
435             break;
436 
437         case Precision::RND_FRACTION:
438             value.roundToMagnitude(
439                     getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac),
440                     fRoundingMode,
441                     status);
442             resolvedMinFraction =
443                     uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac));
444             break;
445 
446         case Precision::RND_SIGNIFICANT:
447             value.roundToMagnitude(
448                     getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig),
449                     fRoundingMode,
450                     status);
451             resolvedMinFraction =
452                     uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig));
453             // Make sure that digits are displayed on zero.
454             if (value.isZeroish() && fPrecision.fUnion.fracSig.fMinSig > 0) {
455                 value.setMinInteger(1);
456             }
457             break;
458 
459         case Precision::RND_FRACTION_SIGNIFICANT: {
460             int32_t roundingMag1 = getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac);
461             int32_t roundingMag2 = getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig);
462             int32_t roundingMag;
463             if (fPrecision.fUnion.fracSig.fPriority == UNUM_ROUNDING_PRIORITY_RELAXED) {
464                 roundingMag = uprv_min(roundingMag1, roundingMag2);
465             } else {
466                 roundingMag = uprv_max(roundingMag1, roundingMag2);
467             }
468             value.roundToMagnitude(roundingMag, fRoundingMode, status);
469 
470             int32_t displayMag1 = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac);
471             int32_t displayMag2 = getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig);
472             int32_t displayMag = uprv_min(displayMag1, displayMag2);
473             resolvedMinFraction = uprv_max(0, -displayMag);
474 
475             break;
476         }
477 
478         case Precision::RND_INCREMENT:
479             value.roundToIncrement(
480                     fPrecision.fUnion.increment.fIncrement,
481                     fRoundingMode,
482                     status);
483             resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
484             break;
485 
486         case Precision::RND_INCREMENT_ONE:
487             value.roundToMagnitude(
488                     -fPrecision.fUnion.increment.fMaxFrac,
489                     fRoundingMode,
490                     status);
491             resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
492             break;
493 
494         case Precision::RND_INCREMENT_FIVE:
495             value.roundToNickel(
496                     -fPrecision.fUnion.increment.fMaxFrac,
497                     fRoundingMode,
498                     status);
499             resolvedMinFraction = fPrecision.fUnion.increment.fMinFrac;
500             break;
501 
502         case Precision::RND_CURRENCY:
503             // Call .withCurrency() before .apply()!
504             UPRV_UNREACHABLE_EXIT;
505 
506         default:
507             UPRV_UNREACHABLE_EXIT;
508     }
509 
510     if (fPrecision.fTrailingZeroDisplay == UNUM_TRAILING_ZERO_AUTO ||
511             // PLURAL_OPERAND_T returns fraction digits as an integer
512             value.getPluralOperand(PLURAL_OPERAND_T) != 0) {
513         value.setMinFraction(resolvedMinFraction);
514     }
515 }
516 
apply(impl::DecimalQuantity & value,int32_t minInt,UErrorCode)517 void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) {
518     // This method is intended for the one specific purpose of helping print "00.000E0".
519     // Question: Is it useful to look at trailingZeroDisplay here?
520     U_ASSERT(isSignificantDigits());
521     U_ASSERT(value.isZeroish());
522     value.setMinFraction(fPrecision.fUnion.fracSig.fMinSig - minInt);
523 }
524 
525 #endif /* #if !UCONFIG_NO_FORMATTING */
526