Coverage Report

Created: 2025-11-07 06:50

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/icu/icu4c/source/i18n/numparse_scientific.cpp
Line
Count
Source
1
// © 2018 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
// Allow implicit conversion from char16_t* to UnicodeString for this file:
9
// Helpful in toString methods and elsewhere.
10
#define UNISTR_FROM_STRING_EXPLICIT
11
12
#include "numparse_types.h"
13
#include "numparse_scientific.h"
14
#include "static_unicode_sets.h"
15
#include "string_segment.h"
16
17
using namespace icu;
18
using namespace icu::numparse;
19
using namespace icu::numparse::impl;
20
21
22
namespace {
23
24
217k
inline const UnicodeSet& minusSignSet() {
25
217k
    return *unisets::get(unisets::MINUS_SIGN);
26
217k
}
27
28
209k
inline const UnicodeSet& plusSignSet() {
29
209k
    return *unisets::get(unisets::PLUS_SIGN);
30
209k
}
31
32
} // namespace
33
34
35
ScientificMatcher::ScientificMatcher(const DecimalFormatSymbols& dfs, const Grouper& grouper)
36
83.7k
        : fExponentSeparatorString(dfs.getConstSymbol(DecimalFormatSymbols::kExponentialSymbol)),
37
83.7k
          fExponentMatcher(dfs, grouper, PARSE_FLAG_INTEGER_ONLY | PARSE_FLAG_GROUPING_DISABLED),
38
83.7k
          fIgnorablesMatcher(PARSE_FLAG_STRICT_IGNORABLES) {
39
40
83.7k
    const UnicodeString& minusSign = dfs.getConstSymbol(DecimalFormatSymbols::kMinusSignSymbol);
41
83.7k
    if (minusSignSet().contains(minusSign)) {
42
76.8k
        fCustomMinusSign.setToBogus();
43
76.8k
    } else {
44
6.92k
        fCustomMinusSign = minusSign;
45
6.92k
    }
46
47
83.7k
    const UnicodeString& plusSign = dfs.getConstSymbol(DecimalFormatSymbols::kPlusSignSymbol);
48
83.7k
    if (plusSignSet().contains(plusSign)) {
49
76.8k
        fCustomPlusSign.setToBogus();
50
76.8k
    } else {
51
6.92k
        fCustomPlusSign = plusSign;
52
6.92k
    }
53
83.7k
}
54
55
208k
bool ScientificMatcher::match(StringSegment& segment, ParsedNumber& result, UErrorCode& status) const {
56
    // Only accept scientific notation after the mantissa.
57
208k
    if (!result.seenNumber()) {
58
43.0k
        return false;
59
43.0k
    }
60
61
    // Only accept one exponent per string.
62
165k
    if (0 != (result.flags & FLAG_HAS_EXPONENT)) {
63
28.2k
        return false;
64
28.2k
    }
65
66
    // First match the scientific separator, and then match another number after it.
67
    // NOTE: This is guarded by the smoke test; no need to check fExponentSeparatorString length again.
68
137k
    int32_t initialOffset = segment.getOffset();
69
137k
    int32_t overlap = segment.getCommonPrefixLength(fExponentSeparatorString);
70
137k
    if (overlap == fExponentSeparatorString.length()) {
71
        // Full exponent separator match.
72
73
        // First attempt to get a code point, returning true if we can't get one.
74
137k
        if (segment.length() == overlap) {
75
297
            return true;
76
297
        }
77
137k
        segment.adjustOffset(overlap);
78
79
        // Allow ignorables before the sign.
80
        // Note: call site is guarded by the segment.length() check above.
81
        // Note: the ignorables matcher should not touch the result.
82
137k
        fIgnorablesMatcher.match(segment, result, status);
83
137k
        if (segment.length() == 0) {
84
2.87k
            segment.setOffset(initialOffset);
85
2.87k
            return true;
86
2.87k
        }
87
88
        // Allow a sign, and then try to match digits.
89
134k
        int8_t exponentSign = 1;
90
134k
        if (segment.startsWith(minusSignSet())) {
91
8.49k
            exponentSign = -1;
92
8.49k
            segment.adjustOffsetByCodePoint();
93
125k
        } else if (segment.startsWith(plusSignSet())) {
94
1.32k
            segment.adjustOffsetByCodePoint();
95
124k
        } else if (segment.startsWith(fCustomMinusSign)) {
96
201
            overlap = segment.getCommonPrefixLength(fCustomMinusSign);
97
201
            if (overlap != fCustomMinusSign.length()) {
98
                // Partial custom sign match
99
105
                segment.setOffset(initialOffset);
100
105
                return true;
101
105
            }
102
96
            exponentSign = -1;
103
96
            segment.adjustOffset(overlap);
104
124k
        } else if (segment.startsWith(fCustomPlusSign)) {
105
0
            overlap = segment.getCommonPrefixLength(fCustomPlusSign);
106
0
            if (overlap != fCustomPlusSign.length()) {
107
                // Partial custom sign match
108
0
                segment.setOffset(initialOffset);
109
0
                return true;
110
0
            }
111
0
            segment.adjustOffset(overlap);
112
0
        }
113
114
        // Return true if the segment is empty.
115
134k
        if (segment.length() == 0) {
116
645
            segment.setOffset(initialOffset);
117
645
            return true;
118
645
        }
119
120
        // Allow ignorables after the sign.
121
        // Note: call site is guarded by the segment.length() check above.
122
        // Note: the ignorables matcher should not touch the result.
123
133k
        fIgnorablesMatcher.match(segment, result, status);
124
133k
        if (segment.length() == 0) {
125
10
            segment.setOffset(initialOffset);
126
10
            return true;
127
10
        }
128
129
        // We are supposed to accept E0 after NaN, so we need to make sure result.quantity is available.
130
133k
        bool wasBogus = result.quantity.bogus;
131
133k
        result.quantity.bogus = false;
132
133k
        int digitsOffset = segment.getOffset();
133
133k
        bool digitsReturnValue = fExponentMatcher.match(segment, result, exponentSign, status);
134
133k
        result.quantity.bogus = wasBogus;
135
136
133k
        if (segment.getOffset() != digitsOffset) {
137
            // At least one exponent digit was matched.
138
65.7k
            result.flags |= FLAG_HAS_EXPONENT;
139
67.7k
        } else {
140
            // No exponent digits were matched
141
67.7k
            segment.setOffset(initialOffset);
142
67.7k
        }
143
133k
        return digitsReturnValue;
144
145
133k
    } else if (overlap == segment.length()) {
146
        // Partial exponent separator match
147
5
        return true;
148
5
    }
149
150
    // No match
151
65
    return false;
152
137k
}
153
154
2.37M
bool ScientificMatcher::smokeTest(const StringSegment& segment) const {
155
2.37M
    return segment.startsWith(fExponentSeparatorString);
156
2.37M
}
157
158
0
UnicodeString ScientificMatcher::toString() const {
159
0
    return u"<Scientific>";
160
0
}
161
162
163
#endif /* #if !UCONFIG_NO_FORMATTING */