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