• 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 "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 
getRoundingMagnitudeFraction(int maxFrac)25 int32_t getRoundingMagnitudeFraction(int maxFrac) {
26     if (maxFrac == -1) {
27         return INT32_MIN;
28     }
29     return -maxFrac;
30 }
31 
getRoundingMagnitudeSignificant(const DecimalQuantity & value,int maxSig)32 int32_t getRoundingMagnitudeSignificant(const DecimalQuantity &value, int maxSig) {
33     if (maxSig == -1) {
34         return INT32_MIN;
35     }
36     int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
37     return magnitude - maxSig + 1;
38 }
39 
getDisplayMagnitudeFraction(int minFrac)40 int32_t getDisplayMagnitudeFraction(int minFrac) {
41     if (minFrac == 0) {
42         return INT32_MAX;
43     }
44     return -minFrac;
45 }
46 
getDisplayMagnitudeSignificant(const DecimalQuantity & value,int minSig)47 int32_t getDisplayMagnitudeSignificant(const DecimalQuantity &value, int minSig) {
48     int magnitude = value.isZeroish() ? 0 : value.getMagnitude();
49     return magnitude - minSig + 1;
50 }
51 
52 }
53 
54 
55 MultiplierProducer::~MultiplierProducer() = default;
56 
57 
doubleFractionLength(double input,int8_t * singleDigit)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 
unlimited()86 Precision Precision::unlimited() {
87     return Precision(RND_NONE, {}, kDefaultMode);
88 }
89 
integer()90 FractionPrecision Precision::integer() {
91     return constructFraction(0, 0);
92 }
93 
fixedFraction(int32_t minMaxFractionPlaces)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 
minFraction(int32_t minFractionPlaces)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 
maxFraction(int32_t maxFractionPlaces)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 
minMaxFraction(int32_t minFractionPlaces,int32_t maxFractionPlaces)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 
fixedSignificantDigits(int32_t minMaxSignificantDigits)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 
minSignificantDigits(int32_t minSignificantDigits)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 
maxSignificantDigits(int32_t maxSignificantDigits)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 
minMaxSignificantDigits(int32_t minSignificantDigits,int32_t maxSignificantDigits)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 
increment(double roundingIncrement)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 
currency(UCurrencyUsage currencyUsage)168 CurrencyPrecision Precision::currency(UCurrencyUsage currencyUsage) {
169     return constructCurrency(currencyUsage);
170 }
171 
withMinDigits(int32_t minSignificantDigits) const172 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 
withMaxDigits(int32_t maxSignificantDigits) const181 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
withCurrency(const CurrencyUnit & currency,UErrorCode & status) const191 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
withCurrency(const CurrencyUnit & currency) const206 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 
withMinFraction(int32_t minFrac) const215 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 
constructFraction(int32_t minFrac,int32_t maxFrac)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 
constructSignificant(int32_t minSig,int32_t maxSig)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
constructFractionSignificant(const FractionPrecision & base,int32_t minSig,int32_t maxSig)247 Precision::constructFractionSignificant(const FractionPrecision &base, int32_t minSig, int32_t maxSig) {
248     FractionSignificantSettings settings = base.fUnion.fracSig;
249     settings.fMinSig = static_cast<digits_t>(minSig);
250     settings.fMaxSig = static_cast<digits_t>(maxSig);
251     PrecisionUnion union_;
252     union_.fracSig = settings;
253     return {RND_FRACTION_SIGNIFICANT, union_, kDefaultMode};
254 }
255 
constructIncrement(double increment,int32_t minFrac)256 IncrementPrecision Precision::constructIncrement(double increment, int32_t minFrac) {
257     IncrementSettings settings;
258     // Note: For number formatting, fIncrement is used for RND_INCREMENT but not
259     // RND_INCREMENT_ONE or RND_INCREMENT_FIVE. However, fIncrement is used in all
260     // three when constructing a skeleton.
261     settings.fIncrement = increment;
262     settings.fMinFrac = static_cast<digits_t>(minFrac);
263     // One of the few pre-computed quantities:
264     // Note: it is possible for minFrac to be more than maxFrac... (misleading)
265     int8_t singleDigit;
266     settings.fMaxFrac = roundingutils::doubleFractionLength(increment, &singleDigit);
267     PrecisionUnion union_;
268     union_.increment = settings;
269     if (singleDigit == 1) {
270         // NOTE: In C++, we must return the correct value type with the correct union.
271         // It would be invalid to return a RND_FRACTION here because the methods on the
272         // IncrementPrecision type assume that the union is backed by increment data.
273         return {RND_INCREMENT_ONE, union_, kDefaultMode};
274     } else if (singleDigit == 5) {
275         return {RND_INCREMENT_FIVE, union_, kDefaultMode};
276     } else {
277         return {RND_INCREMENT, union_, kDefaultMode};
278     }
279 }
280 
constructCurrency(UCurrencyUsage usage)281 CurrencyPrecision Precision::constructCurrency(UCurrencyUsage usage) {
282     PrecisionUnion union_;
283     union_.currencyUsage = usage;
284     return {RND_CURRENCY, union_, kDefaultMode};
285 }
286 
287 
RoundingImpl(const Precision & precision,UNumberFormatRoundingMode roundingMode,const CurrencyUnit & currency,UErrorCode & status)288 RoundingImpl::RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode,
289                            const CurrencyUnit& currency, UErrorCode& status)
290         : fPrecision(precision), fRoundingMode(roundingMode), fPassThrough(false) {
291     if (precision.fType == Precision::RND_CURRENCY) {
292         fPrecision = precision.withCurrency(currency, status);
293     }
294 }
295 
passThrough()296 RoundingImpl RoundingImpl::passThrough() {
297     RoundingImpl retval;
298     retval.fPassThrough = true;
299     return retval;
300 }
301 
isSignificantDigits() const302 bool RoundingImpl::isSignificantDigits() const {
303     return fPrecision.fType == Precision::RND_SIGNIFICANT;
304 }
305 
306 int32_t
chooseMultiplierAndApply(impl::DecimalQuantity & input,const impl::MultiplierProducer & producer,UErrorCode & status)307 RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
308                                   UErrorCode &status) {
309     // Do not call this method with zero, NaN, or infinity.
310     U_ASSERT(!input.isZeroish());
311 
312     // Perform the first attempt at rounding.
313     int magnitude = input.getMagnitude();
314     int multiplier = producer.getMultiplier(magnitude);
315     input.adjustMagnitude(multiplier);
316     apply(input, status);
317 
318     // If the number rounded to zero, exit.
319     if (input.isZeroish() || U_FAILURE(status)) {
320         return multiplier;
321     }
322 
323     // If the new magnitude after rounding is the same as it was before rounding, then we are done.
324     // This case applies to most numbers.
325     if (input.getMagnitude() == magnitude + multiplier) {
326         return multiplier;
327     }
328 
329     // If the above case DIDN'T apply, then we have a case like 99.9 -> 100 or 999.9 -> 1000:
330     // The number rounded up to the next magnitude. Check if the multiplier changes; if it doesn't,
331     // we do not need to make any more adjustments.
332     int _multiplier = producer.getMultiplier(magnitude + 1);
333     if (multiplier == _multiplier) {
334         return multiplier;
335     }
336 
337     // We have a case like 999.9 -> 1000, where the correct output is "1K", not "1000".
338     // Fix the magnitude and re-apply the rounding strategy.
339     input.adjustMagnitude(_multiplier - multiplier);
340     apply(input, status);
341     return _multiplier;
342 }
343 
344 /** This is the method that contains the actual rounding logic. */
apply(impl::DecimalQuantity & value,UErrorCode & status) const345 void RoundingImpl::apply(impl::DecimalQuantity &value, UErrorCode& status) const {
346     if (fPassThrough) {
347         return;
348     }
349     switch (fPrecision.fType) {
350         case Precision::RND_BOGUS:
351         case Precision::RND_ERROR:
352             // Errors should be caught before the apply() method is called
353             status = U_INTERNAL_PROGRAM_ERROR;
354             break;
355 
356         case Precision::RND_NONE:
357             value.roundToInfinity();
358             break;
359 
360         case Precision::RND_FRACTION:
361             value.roundToMagnitude(
362                     getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac),
363                     fRoundingMode,
364                     status);
365             value.setMinFraction(
366                     uprv_max(0, -getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac)));
367             break;
368 
369         case Precision::RND_SIGNIFICANT:
370             value.roundToMagnitude(
371                     getRoundingMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMaxSig),
372                     fRoundingMode,
373                     status);
374             value.setMinFraction(
375                     uprv_max(0, -getDisplayMagnitudeSignificant(value, fPrecision.fUnion.fracSig.fMinSig)));
376             // Make sure that digits are displayed on zero.
377             if (value.isZeroish() && fPrecision.fUnion.fracSig.fMinSig > 0) {
378                 value.setMinInteger(1);
379             }
380             break;
381 
382         case Precision::RND_FRACTION_SIGNIFICANT: {
383             int32_t displayMag = getDisplayMagnitudeFraction(fPrecision.fUnion.fracSig.fMinFrac);
384             int32_t roundingMag = getRoundingMagnitudeFraction(fPrecision.fUnion.fracSig.fMaxFrac);
385             if (fPrecision.fUnion.fracSig.fMinSig == -1) {
386                 // Max Sig override
387                 int32_t candidate = getRoundingMagnitudeSignificant(
388                         value,
389                         fPrecision.fUnion.fracSig.fMaxSig);
390                 roundingMag = uprv_max(roundingMag, candidate);
391             } else {
392                 // Min Sig override
393                 int32_t candidate = getDisplayMagnitudeSignificant(
394                         value,
395                         fPrecision.fUnion.fracSig.fMinSig);
396                 roundingMag = uprv_min(roundingMag, candidate);
397             }
398             value.roundToMagnitude(roundingMag, fRoundingMode, status);
399             value.setMinFraction(uprv_max(0, -displayMag));
400             break;
401         }
402 
403         case Precision::RND_INCREMENT:
404             value.roundToIncrement(
405                     fPrecision.fUnion.increment.fIncrement,
406                     fRoundingMode,
407                     status);
408             value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
409             break;
410 
411         case Precision::RND_INCREMENT_ONE:
412             value.roundToMagnitude(
413                     -fPrecision.fUnion.increment.fMaxFrac,
414                     fRoundingMode,
415                     status);
416             value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
417             break;
418 
419         case Precision::RND_INCREMENT_FIVE:
420             value.roundToNickel(
421                     -fPrecision.fUnion.increment.fMaxFrac,
422                     fRoundingMode,
423                     status);
424             value.setMinFraction(fPrecision.fUnion.increment.fMinFrac);
425             break;
426 
427         case Precision::RND_CURRENCY:
428             // Call .withCurrency() before .apply()!
429             UPRV_UNREACHABLE;
430 
431         default:
432             UPRV_UNREACHABLE;
433     }
434 }
435 
apply(impl::DecimalQuantity & value,int32_t minInt,UErrorCode)436 void RoundingImpl::apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode /*status*/) {
437     // This method is intended for the one specific purpose of helping print "00.000E0".
438     U_ASSERT(isSignificantDigits());
439     U_ASSERT(value.isZeroish());
440     value.setMinFraction(fPrecision.fUnion.fracSig.fMinSig - minInt);
441 }
442 
443 #endif /* #if !UCONFIG_NO_FORMATTING */
444