Skip to content

Commit 1be0fb2

Browse files
committed
Optimize string conversion
1 parent d6439bb commit 1be0fb2

File tree

3 files changed

+341
-117
lines changed

3 files changed

+341
-117
lines changed

src/scratch/value_functions.cpp

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -587,7 +587,19 @@ extern "C"
587587
return true;
588588
}
589589

590-
return value_toDouble(v1) > value_toDouble(v2);
590+
double n1, n2;
591+
592+
if (v1->type == ValueType::String)
593+
n1 = value_stringToDouble(*v1->stringValue);
594+
else
595+
n1 = value_toDouble(v1);
596+
597+
if (v2->type == ValueType::String)
598+
n2 = value_stringToDouble(*v2->stringValue);
599+
else
600+
n2 = value_toDouble(v2);
601+
602+
return n1 > n2;
591603
}
592604

593605
/*! Returns true if the first value is lower than the second value. */
@@ -613,6 +625,19 @@ extern "C"
613625
}
614626

615627
return value_toDouble(v1) < value_toDouble(v2);
628+
double n1, n2;
629+
630+
if (v1->type == ValueType::String)
631+
n1 = value_stringToDouble(*v1->stringValue);
632+
else
633+
n1 = value_toDouble(v1);
634+
635+
if (v2->type == ValueType::String)
636+
n2 = value_stringToDouble(*v2->stringValue);
637+
else
638+
n2 = value_toDouble(v2);
639+
640+
return n1 < n2;
616641
}
617642
}
618643

src/scratch/value_functions_p.h

Lines changed: 115 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,14 @@
77
#include <limits>
88
#include <ctgmath>
99
#include <iomanip>
10+
#include <charconv>
1011
#include <cassert>
1112

13+
#include "thirdparty/fast_float/fast_float.h"
14+
15+
// Faster than std::isspace()
16+
#define IS_SPACE(x) (x == ' ' || x == '\f' || x == '\n' || x == '\r' || x == '\t' || x == '\v')
17+
1218
namespace libscratchcpp
1319
{
1420

@@ -61,178 +67,176 @@ extern "C"
6167
return (s1.compare(s2) == 0);
6268
}
6369

64-
inline long value_hexToDec(const std::string &s)
70+
inline double value_hexToDec(const char *s, int n, bool *ok)
6571
{
66-
static const std::string digits = "0123456789abcdef";
72+
if (ok)
73+
*ok = false;
74+
75+
// Ignore floats
76+
const char *p = s;
6777

68-
for (char c : s) {
69-
if (digits.find(c) == std::string::npos) {
78+
while (p++ < s + n) {
79+
if (*p == '.')
7080
return 0;
71-
}
7281
}
7382

74-
std::istringstream stream(s);
75-
long ret;
76-
stream >> std::hex >> ret;
77-
return ret;
83+
double result = 0;
84+
auto [ptr, ec] = std::from_chars(s, s + n, result, std::chars_format::hex);
85+
86+
if (ec == std::errc{} && ptr == s + n) {
87+
if (ok)
88+
*ok = true;
89+
90+
return result;
91+
} else
92+
return 0;
7893
}
7994

80-
inline long value_octToDec(const std::string &s)
95+
inline long value_octToDec(const char *s, int n, bool *ok)
8196
{
82-
static const std::string digits = "01234567";
97+
if (ok)
98+
*ok = false;
8399

84-
for (char c : s) {
85-
if (digits.find(c) == std::string::npos) {
86-
return 0;
87-
}
88-
}
100+
char *err;
101+
const double ret = std::strtol(s, &err, 8);
89102

90-
std::istringstream stream(s);
91-
long ret;
92-
stream >> std::oct >> ret;
93-
return ret;
103+
if (*err != 0 && !std::isspace(*err))
104+
return 0;
105+
else {
106+
if (ok)
107+
*ok = true;
108+
109+
return ret;
110+
}
94111
}
95112

96-
inline double value_binToDec(const std::string &s)
113+
inline double value_binToDec(const char *s, int n, bool *ok)
97114
{
98-
static const std::string digits = "01";
115+
if (ok)
116+
*ok = false;
99117

100-
for (char c : s) {
101-
if (digits.find(c) == std::string::npos) {
102-
return 0;
103-
}
104-
}
118+
char *err;
119+
const double ret = std::strtol(s, &err, 2);
120+
121+
if (*err != 0 && !std::isspace(*err))
122+
return 0;
123+
else {
124+
if (ok)
125+
*ok = true;
105126

106-
return std::stoi(s, 0, 2);
127+
return ret;
128+
}
107129
}
108130

109131
inline double value_stringToDouble(const std::string &s, bool *ok = nullptr)
110132
{
111133
if (ok)
112134
*ok = false;
113135

114-
if (s == "Infinity") {
115-
if (ok)
116-
*ok = true;
117-
return std::numeric_limits<double>::infinity();
118-
} else if (s == "-Infinity") {
119-
if (ok)
120-
*ok = true;
121-
return -std::numeric_limits<double>::infinity();
122-
} else if (s == "NaN") {
136+
if (s.empty()) {
123137
if (ok)
124138
*ok = true;
139+
125140
return 0;
126141
}
127142

128-
if (s.size() >= 2 && s[0] == '0') {
129-
std::string sub = s.substr(2, s.size() - 2);
130-
std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower);
143+
const char *begin = s.data();
144+
const char *strEnd = s.data() + s.size();
145+
const char *end = strEnd;
131146

132-
if (s[1] == 'x' || s[1] == 'X') {
133-
return value_hexToDec(sub);
134-
} else if (s[1] == 'o' || s[1] == 'O') {
135-
return value_octToDec(sub);
136-
} else if (s[1] == 'b' || s[1] == 'B') {
137-
return value_binToDec(sub);
138-
}
139-
}
147+
// Trim leading spaces
148+
while (begin < end && IS_SPACE(*begin))
149+
++begin;
140150

141-
static const std::string digits = "0123456789.eE+-";
142-
const std::string *stringPtr = &s;
143-
bool customStr = false;
151+
end = begin + 1;
144152

145-
if (!s.empty() && ((s[0] == ' ') || (s.back() == ' '))) {
146-
std::string *localPtr = new std::string(s);
147-
stringPtr = localPtr;
148-
customStr = true;
153+
// Trim trailing spaces
154+
while (end < strEnd && !IS_SPACE(*(end)))
155+
++end;
149156

150-
while (!localPtr->empty() && (localPtr->at(0) == ' '))
151-
localPtr->erase(0, 1);
157+
// Only whitespace can be after the end
158+
const char *p = end;
152159

153-
while (!localPtr->empty() && (localPtr->back() == ' '))
154-
localPtr->pop_back();
160+
while (p < strEnd) {
161+
if (!IS_SPACE(*p))
162+
return 0;
163+
164+
p++;
155165
}
156166

157-
for (char c : *stringPtr) {
158-
if (digits.find(c) == std::string::npos) {
159-
return 0;
167+
if (end - begin <= 0)
168+
return 0;
169+
170+
if (end - begin > 2 && begin[0] == '0') {
171+
const char prefix = begin[1];
172+
const char *sub = begin + 2;
173+
174+
if (prefix == 'x' || prefix == 'X') {
175+
return value_hexToDec(sub, end - begin - 2, ok);
176+
} else if (prefix == 'o' || prefix == 'O') {
177+
return value_octToDec(sub, end - begin - 2, ok);
178+
} else if (prefix == 'b' || prefix == 'B') {
179+
return value_binToDec(sub, end - begin - 2, ok);
160180
}
161181
}
162182

163-
try {
183+
// Trim leading zeros
184+
while (begin < end && (*begin == '0') && !(begin + 1 < end && begin[1] == '.'))
185+
++begin;
186+
187+
if (end - begin <= 0) {
164188
if (ok)
165189
*ok = true;
166190

167-
// Set locale to C to avoid conversion issues
168-
std::string oldLocale = std::setlocale(LC_NUMERIC, nullptr);
169-
std::setlocale(LC_NUMERIC, "C");
170-
171-
double ret = std::stod(*stringPtr);
191+
return 0;
192+
}
172193

173-
// Restore old locale
174-
std::setlocale(LC_NUMERIC, oldLocale.c_str());
194+
double ret = 0;
195+
auto [ptr, ec] = fast_float::from_chars(begin, end, ret, fast_float::chars_format::json);
175196

176-
if (customStr)
177-
delete stringPtr;
197+
if (ec == std::errc{} && ptr == end) {
198+
if (ok)
199+
*ok = true;
178200

179201
return ret;
180-
} catch (...) {
181-
if (ok)
182-
*ok = false;
202+
} else
183203
return 0;
184-
}
185-
}
186-
187-
inline long value_stringToLong(const std::string &s, bool *ok = nullptr)
188-
{
189-
if (ok)
190-
*ok = false;
191204

205+
// Special values
192206
if (s == "Infinity") {
193207
if (ok)
194208
*ok = true;
195-
return std::numeric_limits<long>::infinity();
209+
return std::numeric_limits<double>::infinity();
196210
} else if (s == "-Infinity") {
197211
if (ok)
198212
*ok = true;
199-
return -std::numeric_limits<long>::infinity();
213+
return -std::numeric_limits<double>::infinity();
200214
} else if (s == "NaN") {
201215
if (ok)
202216
*ok = true;
203-
return 0;
217+
return std::numeric_limits<double>::quiet_NaN();
204218
}
205219

206-
if (s.size() >= 2 && s[0] == '0') {
207-
std::string sub = s.substr(2, s.size() - 2);
208-
std::transform(sub.begin(), sub.end(), sub.begin(), ::tolower);
220+
return 0;
221+
}
209222

210-
if (s[1] == 'x' || s[1] == 'X') {
211-
return value_hexToDec(sub);
212-
} else if (s[1] == 'o' || s[1] == 'O') {
213-
return value_octToDec(sub);
214-
} else if (s[1] == 'b' || s[1] == 'B') {
215-
return value_binToDec(sub);
216-
}
217-
}
223+
inline long value_stringToLong(const std::string &s, bool *ok = nullptr)
224+
{
225+
return value_stringToDouble(s, ok);
226+
}
218227

219-
static const std::string digits = "0123456789.eE+-";
228+
inline bool value_stringIsInt(const char *s, int n)
229+
{
230+
const char *p = s;
220231

221-
for (char c : s) {
222-
if (digits.find(c) == std::string::npos) {
223-
return 0;
224-
}
225-
}
232+
while (p < s + n) {
233+
if (*s == '.' || *s == 'e' || *s == 'E')
234+
return false;
226235

227-
try {
228-
if (ok)
229-
*ok = true;
230-
return std::stol(s);
231-
} catch (...) {
232-
if (ok)
233-
*ok = false;
234-
return 0;
236+
p++;
235237
}
238+
239+
return true;
236240
}
237241
}
238242

@@ -281,7 +285,7 @@ extern "C"
281285
{
282286
bool ok;
283287

284-
if ((str.find_first_of('.') == std::string::npos) && (str.find_first_of('e') == std::string::npos) && (str.find_first_of('E') == std::string::npos)) {
288+
if (value_stringIsInt(str.c_str(), str.size())) {
285289
value_stringToLong(str, &ok);
286290
return ok ? 1 : 0;
287291
} else {

0 commit comments

Comments
 (0)