number_scientific.cpp (6820B)
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 <cstdlib> 9 #include "number_scientific.h" 10 #include "number_utils.h" 11 #include "formatted_string_builder.h" 12 #include "unicode/unum.h" 13 #include "number_microprops.h" 14 15 using namespace icu; 16 using namespace icu::number; 17 using namespace icu::number::impl; 18 19 // NOTE: The object lifecycle of ScientificModifier and ScientificHandler differ greatly in Java and C++. 20 // 21 // During formatting, we need to provide an object with state (the exponent) as the inner modifier. 22 // 23 // In Java, where the priority is put on reducing object creations, the unsafe code path re-uses the 24 // ScientificHandler as a ScientificModifier, and the safe code path pre-computes 25 ScientificModifier 25 // instances. This scheme reduces the number of object creations by 1 in both safe and unsafe. 26 // 27 // In C++, MicroProps provides a pre-allocated ScientificModifier, and ScientificHandler simply populates 28 // the state (the exponent) into that ScientificModifier. There is no difference between safe and unsafe. 29 30 ScientificModifier::ScientificModifier() : fExponent(0), fHandler(nullptr) {} 31 32 void ScientificModifier::set(int32_t exponent, const ScientificHandler *handler) { 33 // ScientificModifier should be set only once. 34 U_ASSERT(fHandler == nullptr); 35 fExponent = exponent; 36 fHandler = handler; 37 } 38 39 int32_t ScientificModifier::apply(FormattedStringBuilder &output, int32_t /*leftIndex*/, int32_t rightIndex, 40 UErrorCode &status) const { 41 // FIXME: Localized exponent separator location. 42 int i = rightIndex; 43 // Append the exponent separator and sign 44 i += output.insert( 45 i, 46 fHandler->fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kExponentialSymbol), 47 {UFIELD_CATEGORY_NUMBER, UNUM_EXPONENT_SYMBOL_FIELD}, 48 status); 49 if (fExponent < 0 && fHandler->fSettings.fExponentSignDisplay != UNUM_SIGN_NEVER) { 50 i += output.insert( 51 i, 52 fHandler->fSymbols 53 ->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kMinusSignSymbol), 54 {UFIELD_CATEGORY_NUMBER, UNUM_EXPONENT_SIGN_FIELD}, 55 status); 56 } else if (fExponent >= 0 && fHandler->fSettings.fExponentSignDisplay == UNUM_SIGN_ALWAYS) { 57 i += output.insert( 58 i, 59 fHandler->fSymbols 60 ->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol), 61 {UFIELD_CATEGORY_NUMBER, UNUM_EXPONENT_SIGN_FIELD}, 62 status); 63 } 64 // Append the exponent digits (using a simple inline algorithm) 65 int32_t disp = std::abs(fExponent); 66 for (int j = 0; j < fHandler->fSettings.fMinExponentDigits || disp > 0; j++, disp /= 10) { 67 auto d = static_cast<int8_t>(disp % 10); 68 i += utils::insertDigitFromSymbols( 69 output, 70 i - j, 71 d, 72 *fHandler->fSymbols, 73 {UFIELD_CATEGORY_NUMBER, UNUM_EXPONENT_FIELD}, 74 status); 75 } 76 return i - rightIndex; 77 } 78 79 int32_t ScientificModifier::getPrefixLength() const { 80 // TODO: Localized exponent separator location. 81 return 0; 82 } 83 84 int32_t ScientificModifier::getCodePointCount() const { 85 // NOTE: This method is only called one place, NumberRangeFormatterImpl. 86 // The call site only cares about != 0 and != 1. 87 // Return a very large value so that if this method is used elsewhere, we should notice. 88 return 999; 89 } 90 91 bool ScientificModifier::isStrong() const { 92 // Scientific is always strong 93 return true; 94 } 95 96 bool ScientificModifier::containsField(Field field) const { 97 (void)field; 98 // This method is not used for inner modifiers. 99 UPRV_UNREACHABLE_EXIT; 100 } 101 102 void ScientificModifier::getParameters(Parameters& output) const { 103 // Not part of any plural sets 104 output.obj = nullptr; 105 } 106 107 bool ScientificModifier::strictEquals(const Modifier& other) const { 108 const auto* _other = dynamic_cast<const ScientificModifier*>(&other); 109 if (_other == nullptr) { 110 return false; 111 } 112 // TODO: Check for locale symbols and settings as well? Could be less efficient. 113 return fExponent == _other->fExponent; 114 } 115 116 // Note: Visual Studio does not compile this function without full name space. Why? 117 icu::number::impl::ScientificHandler::ScientificHandler(const Notation *notation, const DecimalFormatSymbols *symbols, 118 const MicroPropsGenerator *parent) : 119 fSettings(notation->fUnion.scientific), fSymbols(symbols), fParent(parent) {} 120 121 void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps µs, 122 UErrorCode &status) const { 123 fParent->processQuantity(quantity, micros, status); 124 if (U_FAILURE(status)) { return; } 125 126 // Do not apply scientific notation to special doubles 127 if (quantity.isInfinite() || quantity.isNaN()) { 128 micros.modInner = µs.helpers.emptyStrongModifier; 129 return; 130 } 131 132 // Treat zero as if it had magnitude 0 133 int32_t exponent; 134 if (quantity.isZeroish()) { 135 if (fSettings.fRequireMinInt && micros.rounder.isSignificantDigits()) { 136 // Show "00.000E0" on pattern "00.000E0" 137 micros.rounder.apply(quantity, fSettings.fEngineeringInterval, status); 138 exponent = 0; 139 } else { 140 micros.rounder.apply(quantity, status); 141 exponent = 0; 142 } 143 } else { 144 exponent = -micros.rounder.chooseMultiplierAndApply(quantity, *this, status); 145 } 146 147 // Use MicroProps's helper ScientificModifier and save it as the modInner. 148 ScientificModifier &mod = micros.helpers.scientificModifier; 149 mod.set(exponent, this); 150 micros.modInner = &mod; 151 152 // Change the exponent only after we select appropriate plural form 153 // for formatting purposes so that we preserve expected formatted 154 // string behavior. 155 quantity.adjustExponent(exponent); 156 157 // We already performed rounding. Do not perform it again. 158 micros.rounder = RoundingImpl::passThrough(); 159 } 160 161 int32_t ScientificHandler::getMultiplier(int32_t magnitude) const { 162 int32_t interval = fSettings.fEngineeringInterval; 163 int32_t digitsShown; 164 if (fSettings.fRequireMinInt) { 165 // For patterns like "000.00E0" and ".00E0" 166 digitsShown = interval; 167 } else if (interval <= 1) { 168 // For patterns like "0.00E0" and "@@@E0" 169 digitsShown = 1; 170 } else { 171 // For patterns like "##0.00" 172 digitsShown = ((magnitude % interval + interval) % interval) + 1; 173 } 174 return digitsShown - magnitude - 1; 175 } 176 177 #endif /* #if !UCONFIG_NO_FORMATTING */