summaryrefslogtreecommitdiffstats
path: root/intl/icu/source/i18n/number_scientific.cpp
blob: d365d982d4ce852b51379b3ed33f5ef938c22ba9 (plain)
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
// © 2017 and later: Unicode, Inc. and others.
// License & terms of use: http://www.unicode.org/copyright.html

#include "unicode/utypes.h"

#if !UCONFIG_NO_FORMATTING

#include <cstdlib>
#include "number_scientific.h"
#include "number_utils.h"
#include "formatted_string_builder.h"
#include "unicode/unum.h"
#include "number_microprops.h"

using namespace icu;
using namespace icu::number;
using namespace icu::number::impl;

// NOTE: The object lifecycle of ScientificModifier and ScientificHandler differ greatly in Java and C++.
//
// During formatting, we need to provide an object with state (the exponent) as the inner modifier.
//
// In Java, where the priority is put on reducing object creations, the unsafe code path re-uses the
// ScientificHandler as a ScientificModifier, and the safe code path pre-computes 25 ScientificModifier
// instances.  This scheme reduces the number of object creations by 1 in both safe and unsafe.
//
// In C++, MicroProps provides a pre-allocated ScientificModifier, and ScientificHandler simply populates
// the state (the exponent) into that ScientificModifier. There is no difference between safe and unsafe.

ScientificModifier::ScientificModifier() : fExponent(0), fHandler(nullptr) {}

void ScientificModifier::set(int32_t exponent, const ScientificHandler *handler) {
    // ScientificModifier should be set only once.
    U_ASSERT(fHandler == nullptr);
    fExponent = exponent;
    fHandler = handler;
}

int32_t ScientificModifier::apply(FormattedStringBuilder &output, int32_t /*leftIndex*/, int32_t rightIndex,
                                  UErrorCode &status) const {
    // FIXME: Localized exponent separator location.
    int i = rightIndex;
    // Append the exponent separator and sign
    i += output.insert(
            i,
            fHandler->fSymbols->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kExponentialSymbol),
            {UFIELD_CATEGORY_NUMBER, UNUM_EXPONENT_SYMBOL_FIELD},
            status);
    if (fExponent < 0 && fHandler->fSettings.fExponentSignDisplay != UNUM_SIGN_NEVER) {
        i += output.insert(
                i,
                fHandler->fSymbols
                        ->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kMinusSignSymbol),
                {UFIELD_CATEGORY_NUMBER, UNUM_EXPONENT_SIGN_FIELD},
                status);
    } else if (fExponent >= 0 && fHandler->fSettings.fExponentSignDisplay == UNUM_SIGN_ALWAYS) {
        i += output.insert(
                i,
                fHandler->fSymbols
                        ->getSymbol(DecimalFormatSymbols::ENumberFormatSymbol::kPlusSignSymbol),
                {UFIELD_CATEGORY_NUMBER, UNUM_EXPONENT_SIGN_FIELD},
                status);
    }
    // Append the exponent digits (using a simple inline algorithm)
    int32_t disp = std::abs(fExponent);
    for (int j = 0; j < fHandler->fSettings.fMinExponentDigits || disp > 0; j++, disp /= 10) {
        auto d = static_cast<int8_t>(disp % 10);
        i += utils::insertDigitFromSymbols(
                output,
                i - j,
                d,
                *fHandler->fSymbols,
                {UFIELD_CATEGORY_NUMBER, UNUM_EXPONENT_FIELD},
                status);
    }
    return i - rightIndex;
}

int32_t ScientificModifier::getPrefixLength() const {
    // TODO: Localized exponent separator location.
    return 0;
}

int32_t ScientificModifier::getCodePointCount() const {
    // NOTE: This method is only called one place, NumberRangeFormatterImpl.
    // The call site only cares about != 0 and != 1.
    // Return a very large value so that if this method is used elsewhere, we should notice.
    return 999;
}

bool ScientificModifier::isStrong() const {
    // Scientific is always strong
    return true;
}

bool ScientificModifier::containsField(Field field) const {
    (void)field;
    // This method is not used for inner modifiers.
    UPRV_UNREACHABLE_EXIT;
}

void ScientificModifier::getParameters(Parameters& output) const {
    // Not part of any plural sets
    output.obj = nullptr;
}

bool ScientificModifier::semanticallyEquivalent(const Modifier& other) const {
    auto* _other = dynamic_cast<const ScientificModifier*>(&other);
    if (_other == nullptr) {
        return false;
    }
    // TODO: Check for locale symbols and settings as well? Could be less efficient.
    return fExponent == _other->fExponent;
}

// Note: Visual Studio does not compile this function without full name space. Why?
icu::number::impl::ScientificHandler::ScientificHandler(const Notation *notation, const DecimalFormatSymbols *symbols,
	const MicroPropsGenerator *parent) : 
	fSettings(notation->fUnion.scientific), fSymbols(symbols), fParent(parent) {}

void ScientificHandler::processQuantity(DecimalQuantity &quantity, MicroProps &micros,
                                        UErrorCode &status) const {
    fParent->processQuantity(quantity, micros, status);
    if (U_FAILURE(status)) { return; }

    // Do not apply scientific notation to special doubles
    if (quantity.isInfinite() || quantity.isNaN()) {
        micros.modInner = &micros.helpers.emptyStrongModifier;
        return;
    }

    // Treat zero as if it had magnitude 0
    int32_t exponent;
    if (quantity.isZeroish()) {
        if (fSettings.fRequireMinInt && micros.rounder.isSignificantDigits()) {
            // Show "00.000E0" on pattern "00.000E0"
            micros.rounder.apply(quantity, fSettings.fEngineeringInterval, status);
            exponent = 0;
        } else {
            micros.rounder.apply(quantity, status);
            exponent = 0;
        }
    } else {
        exponent = -micros.rounder.chooseMultiplierAndApply(quantity, *this, status);
    }

    // Use MicroProps's helper ScientificModifier and save it as the modInner.
    ScientificModifier &mod = micros.helpers.scientificModifier;
    mod.set(exponent, this);
    micros.modInner = &mod;

    // Change the exponent only after we select appropriate plural form
    // for formatting purposes so that we preserve expected formatted
    // string behavior.
    quantity.adjustExponent(exponent);

    // We already performed rounding. Do not perform it again.
    micros.rounder = RoundingImpl::passThrough();
}

int32_t ScientificHandler::getMultiplier(int32_t magnitude) const {
    int32_t interval = fSettings.fEngineeringInterval;
    int32_t digitsShown;
    if (fSettings.fRequireMinInt) {
        // For patterns like "000.00E0" and ".00E0"
        digitsShown = interval;
    } else if (interval <= 1) {
        // For patterns like "0.00E0" and "@@@E0"
        digitsShown = 1;
    } else {
        // For patterns like "##0.00"
        digitsShown = ((magnitude % interval + interval) % interval) + 1;
    }
    return digitsShown - magnitude - 1;
}

#endif /* #if !UCONFIG_NO_FORMATTING */