1 // © 2017 and later: Unicode, Inc. and others.
2 // License & terms of use: http://www.unicode.org/copyright.html
4 #include "unicode/utypes.h"
6 #if !UCONFIG_NO_FORMATTING
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"
17 using namespace icu::number
;
18 using namespace icu::number::impl
;
21 using double_conversion::DoubleToStringConverter
;
25 int32_t getRoundingMagnitudeFraction(int maxFrac
) {
32 int32_t getRoundingMagnitudeSignificant(const DecimalQuantity
&value
, int maxSig
) {
36 int magnitude
= value
.isZero() ? 0 : value
.getMagnitude();
37 return magnitude
- maxSig
+ 1;
40 int32_t getDisplayMagnitudeFraction(int minFrac
) {
47 int32_t getDisplayMagnitudeSignificant(const DecimalQuantity
&value
, int minSig
) {
48 int magnitude
= value
.isZero() ? 0 : value
.getMagnitude();
49 return magnitude
- minSig
+ 1;
55 MultiplierProducer::~MultiplierProducer() = default;
58 digits_t
roundingutils::doubleFractionLength(double input
) {
59 char buffer
[DoubleToStringConverter::kBase10MaximalLength
+ 1];
60 bool sign
; // unused; always positive
63 DoubleToStringConverter::DoubleToAscii(
65 DoubleToStringConverter::DtoaMode::SHORTEST
,
74 return static_cast<digits_t
>(length
- point
);
78 Precision
Precision::unlimited() {
79 return Precision(RND_NONE
, {}, kDefaultMode
);
82 FractionPrecision
Precision::integer() {
83 return constructFraction(0, 0);
86 FractionPrecision
Precision::fixedFraction(int32_t minMaxFractionPlaces
) {
87 if (minMaxFractionPlaces
>= 0 && minMaxFractionPlaces
<= kMaxIntFracSig
) {
88 return constructFraction(minMaxFractionPlaces
, minMaxFractionPlaces
);
90 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
94 FractionPrecision
Precision::minFraction(int32_t minFractionPlaces
) {
95 if (minFractionPlaces
>= 0 && minFractionPlaces
<= kMaxIntFracSig
) {
96 return constructFraction(minFractionPlaces
, -1);
98 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
102 FractionPrecision
Precision::maxFraction(int32_t maxFractionPlaces
) {
103 if (maxFractionPlaces
>= 0 && maxFractionPlaces
<= kMaxIntFracSig
) {
104 return constructFraction(0, maxFractionPlaces
);
106 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
110 FractionPrecision
Precision::minMaxFraction(int32_t minFractionPlaces
, int32_t maxFractionPlaces
) {
111 if (minFractionPlaces
>= 0 && maxFractionPlaces
<= kMaxIntFracSig
&&
112 minFractionPlaces
<= maxFractionPlaces
) {
113 return constructFraction(minFractionPlaces
, maxFractionPlaces
);
115 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
119 Precision
Precision::fixedSignificantDigits(int32_t minMaxSignificantDigits
) {
120 if (minMaxSignificantDigits
>= 1 && minMaxSignificantDigits
<= kMaxIntFracSig
) {
121 return constructSignificant(minMaxSignificantDigits
, minMaxSignificantDigits
);
123 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
127 Precision
Precision::minSignificantDigits(int32_t minSignificantDigits
) {
128 if (minSignificantDigits
>= 1 && minSignificantDigits
<= kMaxIntFracSig
) {
129 return constructSignificant(minSignificantDigits
, -1);
131 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
135 Precision
Precision::maxSignificantDigits(int32_t maxSignificantDigits
) {
136 if (maxSignificantDigits
>= 1 && maxSignificantDigits
<= kMaxIntFracSig
) {
137 return constructSignificant(1, maxSignificantDigits
);
139 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
143 Precision
Precision::minMaxSignificantDigits(int32_t minSignificantDigits
, int32_t maxSignificantDigits
) {
144 if (minSignificantDigits
>= 1 && maxSignificantDigits
<= kMaxIntFracSig
&&
145 minSignificantDigits
<= maxSignificantDigits
) {
146 return constructSignificant(minSignificantDigits
, maxSignificantDigits
);
148 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
152 IncrementPrecision
Precision::increment(double roundingIncrement
) {
153 if (roundingIncrement
> 0.0) {
154 return constructIncrement(roundingIncrement
, 0);
156 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
160 CurrencyPrecision
Precision::currency(UCurrencyUsage currencyUsage
) {
161 return constructCurrency(currencyUsage
);
164 Precision
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
;
171 Precision
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);
176 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
180 Precision
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
);
185 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
189 // Private method on base class
190 Precision
Precision::withCurrency(const CurrencyUnit
¤cy
, 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
);
200 return constructFraction(minMaxFrac
, minMaxFrac
);
204 // Public method on CurrencyPrecision subclass
205 Precision
CurrencyPrecision::withCurrency(const CurrencyUnit
¤cy
) const {
206 UErrorCode localStatus
= U_ZERO_ERROR
;
207 Precision result
= Precision::withCurrency(currency
, localStatus
);
208 if (U_FAILURE(localStatus
)) {
209 return {localStatus
};
214 Precision
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
);
219 return {U_NUMBER_ARG_OUTOFBOUNDS_ERROR
};
223 FractionPrecision
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
};
234 Precision
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
};
246 Precision::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
};
255 IncrementPrecision
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
};
267 CurrencyPrecision
Precision::constructCurrency(UCurrencyUsage usage
) {
268 PrecisionUnion union_
;
269 union_
.currencyUsage
= usage
;
270 return {RND_CURRENCY
, union_
, kDefaultMode
};
274 RoundingImpl::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
);
282 RoundingImpl
RoundingImpl::passThrough() {
284 retval
.fPassThrough
= true;
288 bool RoundingImpl::isSignificantDigits() const {
289 return fPrecision
.fType
== Precision::RND_SIGNIFICANT
;
293 RoundingImpl::chooseMultiplierAndApply(impl::DecimalQuantity
&input
, const impl::MultiplierProducer
&producer
,
294 UErrorCode
&status
) {
295 // Do not call this method with zero.
296 U_ASSERT(!input
.isZero());
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
);
304 // If the number rounded to zero, exit.
305 if (input
.isZero() || U_FAILURE(status
)) {
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
) {
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
) {
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
);
330 /** This is the method that contains the actual rounding logic. */
331 void RoundingImpl::apply(impl::DecimalQuantity
&value
, UErrorCode
& status
) const {
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
;
342 case Precision::RND_NONE
:
343 value
.roundToInfinity();
346 case Precision::RND_FRACTION
:
347 value
.roundToMagnitude(
348 getRoundingMagnitudeFraction(fPrecision
.fUnion
.fracSig
.fMaxFrac
),
351 value
.setFractionLength(
352 uprv_max(0, -getDisplayMagnitudeFraction(fPrecision
.fUnion
.fracSig
.fMinFrac
)),
356 case Precision::RND_SIGNIFICANT
:
357 value
.roundToMagnitude(
358 getRoundingMagnitudeSignificant(value
, fPrecision
.fUnion
.fracSig
.fMaxSig
),
361 value
.setFractionLength(
362 uprv_max(0, -getDisplayMagnitudeSignificant(value
, fPrecision
.fUnion
.fracSig
.fMinSig
)),
364 // Make sure that digits are displayed on zero.
365 if (value
.isZero() && fPrecision
.fUnion
.fracSig
.fMinSig
> 0) {
366 value
.setIntegerLength(1, INT32_MAX
);
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) {
375 int32_t candidate
= getRoundingMagnitudeSignificant(
377 fPrecision
.fUnion
.fracSig
.fMaxSig
);
378 roundingMag
= uprv_max(roundingMag
, candidate
);
381 int32_t candidate
= getDisplayMagnitudeSignificant(
383 fPrecision
.fUnion
.fracSig
.fMinSig
);
384 roundingMag
= uprv_min(roundingMag
, candidate
);
386 value
.roundToMagnitude(roundingMag
, fRoundingMode
, status
);
387 value
.setFractionLength(uprv_max(0, -displayMag
), INT32_MAX
);
391 case Precision::RND_INCREMENT
:
392 value
.roundToIncrement(
393 fPrecision
.fUnion
.increment
.fIncrement
,
395 fPrecision
.fUnion
.increment
.fMaxFrac
,
397 value
.setFractionLength(fPrecision
.fUnion
.increment
.fMinFrac
, INT32_MAX
);
400 case Precision::RND_CURRENCY
:
401 // Call .withCurrency() before .apply()!
407 void 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
);
414 #endif /* #if !UCONFIG_NO_FORMATTING */