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