1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
|
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html
#include <_foundation_unicode/utypes.h>
#if !UCONFIG_NO_FORMATTING
#ifndef __NUMBER_ROUNDINGUTILS_H__
#define __NUMBER_ROUNDINGUTILS_H__
#include "number_types.h"
#include "string_segment.h"
U_NAMESPACE_BEGIN
namespace number {
namespace impl {
namespace roundingutils {
enum Section {
SECTION_LOWER_EDGE = -1,
SECTION_UPPER_EDGE = -2,
SECTION_LOWER = 1,
SECTION_MIDPOINT = 2,
SECTION_UPPER = 3
};
/**
* Converts a rounding mode and metadata about the quantity being rounded to a boolean determining
* whether the value should be rounded toward infinity or toward zero.
*
* <p>The parameters are of type int because benchmarks on an x86-64 processor against OpenJDK
* showed that ints were demonstrably faster than enums in switch statements.
*
* @param isEven Whether the digit immediately before the rounding magnitude is even.
* @param isNegative Whether the quantity is negative.
* @param section Whether the part of the quantity to the right of the rounding magnitude is
* exactly halfway between two digits, whether it is in the lower part (closer to zero), or
* whether it is in the upper part (closer to infinity). See {@link #SECTION_LOWER}, {@link
* #SECTION_MIDPOINT}, and {@link #SECTION_UPPER}.
* @param roundingMode The integer version of the {@link RoundingMode}, which you can get via
* {@link RoundingMode#ordinal}.
* @param status Error code, set to U_FORMAT_INEXACT_ERROR if the rounding mode is kRoundUnnecessary.
* @return true if the number should be rounded toward zero; false if it should be rounded toward
* infinity.
*/
inline bool
getRoundingDirection(bool isEven, bool isNegative, Section section, RoundingMode roundingMode,
UErrorCode &status) {
if (U_FAILURE(status)) {
return false;
}
switch (roundingMode) {
case RoundingMode::UNUM_ROUND_UP:
// round away from zero
return false;
case RoundingMode::UNUM_ROUND_DOWN:
// round toward zero
return true;
case RoundingMode::UNUM_ROUND_CEILING:
// round toward positive infinity
return isNegative;
case RoundingMode::UNUM_ROUND_FLOOR:
// round toward negative infinity
return !isNegative;
case RoundingMode::UNUM_ROUND_HALFUP:
switch (section) {
case SECTION_MIDPOINT:
return false;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
default:
break;
}
break;
case RoundingMode::UNUM_ROUND_HALFDOWN:
switch (section) {
case SECTION_MIDPOINT:
return true;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
default:
break;
}
break;
case RoundingMode::UNUM_ROUND_HALFEVEN:
switch (section) {
case SECTION_MIDPOINT:
return isEven;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
default:
break;
}
break;
case RoundingMode::UNUM_ROUND_HALF_ODD:
switch (section) {
case SECTION_MIDPOINT:
return !isEven;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
default:
break;
}
break;
case RoundingMode::UNUM_ROUND_HALF_CEILING:
switch (section) {
case SECTION_MIDPOINT:
return isNegative;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
default:
break;
}
break;
case RoundingMode::UNUM_ROUND_HALF_FLOOR:
switch (section) {
case SECTION_MIDPOINT:
return !isNegative;
case SECTION_LOWER:
return true;
case SECTION_UPPER:
return false;
default:
break;
}
break;
default:
break;
}
status = U_FORMAT_INEXACT_ERROR;
return false;
}
/**
* Gets whether the given rounding mode's rounding boundary is at the midpoint. The rounding
* boundary is the point at which a number switches from being rounded down to being rounded up.
* For example, with rounding mode HALF_EVEN, HALF_UP, or HALF_DOWN, the rounding boundary is at
* the midpoint, and this function would return true. However, for UP, DOWN, CEILING, and FLOOR,
* the rounding boundary is at the "edge", and this function would return false.
*
* @param roundingMode The integer version of the {@link RoundingMode}.
* @return true if rounding mode is HALF_EVEN, HALF_UP, or HALF_DOWN; false otherwise.
*/
inline bool roundsAtMidpoint(int roundingMode) {
switch (roundingMode) {
case RoundingMode::UNUM_ROUND_UP:
case RoundingMode::UNUM_ROUND_DOWN:
case RoundingMode::UNUM_ROUND_CEILING:
case RoundingMode::UNUM_ROUND_FLOOR:
return false;
default:
return true;
}
}
} // namespace roundingutils
/**
* Encapsulates a Precision and a RoundingMode and performs rounding on a DecimalQuantity.
*
* This class does not exist in Java: instead, the base Precision class is used.
*/
class RoundingImpl {
public:
RoundingImpl() = default; // defaults to pass-through rounder
RoundingImpl(const Precision& precision, UNumberFormatRoundingMode roundingMode,
const CurrencyUnit& currency, UErrorCode& status);
static RoundingImpl passThrough();
/** Required for ScientificFormatter */
bool isSignificantDigits() const;
/**
* Rounding endpoint used by Engineering and Compact notation. Chooses the most appropriate multiplier (magnitude
* adjustment), applies the adjustment, rounds, and returns the chosen multiplier.
*
* <p>
* In most cases, this is simple. However, when rounding the number causes it to cross a multiplier boundary, we
* need to re-do the rounding. For example, to display 999,999 in Engineering notation with 2 sigfigs, first you
* guess the multiplier to be -3. However, then you end up getting 1000E3, which is not the correct output. You then
* change your multiplier to be -6, and you get 1.0E6, which is correct.
*
* @param input The quantity to process.
* @param producer Function to call to return a multiplier based on a magnitude.
* @return The number of orders of magnitude the input was adjusted by this method.
*/
int32_t
chooseMultiplierAndApply(impl::DecimalQuantity &input, const impl::MultiplierProducer &producer,
UErrorCode &status);
void apply(impl::DecimalQuantity &value, UErrorCode &status) const;
/** Version of {@link #apply} that obeys minInt constraints. Used for scientific notation compatibility mode. */
void apply(impl::DecimalQuantity &value, int32_t minInt, UErrorCode status);
private:
Precision fPrecision;
UNumberFormatRoundingMode fRoundingMode;
bool fPassThrough = true; // default value
// Permits access to fPrecision.
friend class units::UnitsRouter;
// Permits access to fPrecision.
friend class UnitConversionHandler;
};
/**
* Parses Precision-related skeleton strings without knowledge of MacroProps
* - see blueprint_helpers::parseIncrementOption().
*
* Referencing MacroProps means needing to pull in the .o files that have the
* destructors for the SymbolsWrapper, StringProp, and Scale classes.
*/
void parseIncrementOption(const StringSegment &segment, Precision &outPrecision, UErrorCode &status);
} // namespace impl
} // namespace number
U_NAMESPACE_END
#endif //__NUMBER_ROUNDINGUTILS_H__
#endif /* #if !UCONFIG_NO_FORMATTING */
|