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 
ScientificModifier()30 ScientificModifier::ScientificModifier() : fExponent(0), fHandler(nullptr) {}
31 
set(int32_t exponent,const ScientificHandler * handler)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 
apply(FormattedStringBuilder & output,int32_t,int32_t rightIndex,UErrorCode & status) const39 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 
getPrefixLength() const79 int32_t ScientificModifier::getPrefixLength() const {
80     // TODO: Localized exponent separator location.
81     return 0;
82 }
83 
getCodePointCount() const84 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 
isStrong() const91 bool ScientificModifier::isStrong() const {
92     // Scientific is always strong
93     return true;
94 }
95 
containsField(Field field) const96 bool ScientificModifier::containsField(Field field) const {
97     (void)field;
98     // This method is not used for inner modifiers.
99     UPRV_UNREACHABLE;
100 #ifdef U_STRINGI_PATCHES
101     return false;
102 #endif
103 }
104 
getParameters(Parameters & output) const105 void ScientificModifier::getParameters(Parameters& output) const {
106     // Not part of any plural sets
107     output.obj = nullptr;
108 }
109 
semanticallyEquivalent(const Modifier & other) const110 bool ScientificModifier::semanticallyEquivalent(const Modifier& other) const {
111     auto* _other = dynamic_cast<const ScientificModifier*>(&other);
112     if (_other == nullptr) {
113         return false;
114     }
115     // TODO: Check for locale symbols and settings as well? Could be less efficient.
116     return fExponent == _other->fExponent;
117 }
118 
119 // Note: Visual Studio does not compile this function without full name space. Why?
ScientificHandler(const Notation * notation,const DecimalFormatSymbols * symbols,const MicroPropsGenerator * parent)120 icu::number::impl::ScientificHandler::ScientificHandler(const Notation *notation, const DecimalFormatSymbols *symbols,
121 	const MicroPropsGenerator *parent) :
122 	fSettings(notation->fUnion.scientific), fSymbols(symbols), fParent(parent) {}
123 
processQuantity(DecimalQuantity & quantity,MicroProps & micros,UErrorCode & status) const124 void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
125                                         UErrorCode &status) const {
126     fParent->processQuantity(quantity, micros, status);
127     if (U_FAILURE(status)) { return; }
128 
129     // Do not apply scientific notation to special doubles
130     if (quantity.isInfinite() || quantity.isNaN()) {
131         micros.modInner = &micros.helpers.emptyStrongModifier;
132         return;
133     }
134 
135     // Treat zero as if it had magnitude 0
136     int32_t exponent;
137     if (quantity.isZeroish()) {
138         if (fSettings.fRequireMinInt && micros.rounder.isSignificantDigits()) {
139             // Show "00.000E0" on pattern "00.000E0"
140             micros.rounder.apply(quantity, fSettings.fEngineeringInterval, status);
141             exponent = 0;
142         } else {
143             micros.rounder.apply(quantity, status);
144             exponent = 0;
145         }
146     } else {
147         exponent = -micros.rounder.chooseMultiplierAndApply(quantity, *this, status);
148     }
149 
150     // Use MicroProps's helper ScientificModifier and save it as the modInner.
151     ScientificModifier &mod = micros.helpers.scientificModifier;
152     mod.set(exponent, this);
153     micros.modInner = &mod;
154 
155     // Change the exponent only after we select appropriate plural form
156     // for formatting purposes so that we preserve expected formatted
157     // string behavior.
158     quantity.adjustExponent(exponent);
159 
160     // We already performed rounding. Do not perform it again.
161     micros.rounder = RoundingImpl::passThrough();
162 }
163 
getMultiplier(int32_t magnitude) const164 int32_t ScientificHandler::getMultiplier(int32_t magnitude) const {
165     int32_t interval = fSettings.fEngineeringInterval;
166     int32_t digitsShown;
167     if (fSettings.fRequireMinInt) {
168         // For patterns like "000.00E0" and ".00E0"
169         digitsShown = interval;
170     } else if (interval <= 1) {
171         // For patterns like "0.00E0" and "@@@E0"
172         digitsShown = 1;
173     } else {
174         // For patterns like "##0.00"
175         digitsShown = ((magnitude % interval + interval) % interval) + 1;
176     }
177     return digitsShown - magnitude - 1;
178 }
179 
180 #endif /* #if !UCONFIG_NO_FORMATTING */
181