1 | #include "string_util.h" |
2 | |
3 | #include <array> |
4 | #ifdef BENCHMARK_STL_ANDROID_GNUSTL |
5 | #include <cerrno> |
6 | #endif |
7 | #include <cmath> |
8 | #include <cstdarg> |
9 | #include <cstdio> |
10 | #include <memory> |
11 | #include <sstream> |
12 | |
13 | #include "arraysize.h" |
14 | #include "benchmark/benchmark.h" |
15 | |
16 | namespace benchmark { |
17 | namespace { |
18 | // kilo, Mega, Giga, Tera, Peta, Exa, Zetta, Yotta. |
19 | const char* const kBigSIUnits[] = {"k" , "M" , "G" , "T" , "P" , "E" , "Z" , "Y" }; |
20 | // Kibi, Mebi, Gibi, Tebi, Pebi, Exbi, Zebi, Yobi. |
21 | const char* const kBigIECUnits[] = {"Ki" , "Mi" , "Gi" , "Ti" , |
22 | "Pi" , "Ei" , "Zi" , "Yi" }; |
23 | // milli, micro, nano, pico, femto, atto, zepto, yocto. |
24 | const char* const kSmallSIUnits[] = {"m" , "u" , "n" , "p" , "f" , "a" , "z" , "y" }; |
25 | |
26 | // We require that all three arrays have the same size. |
27 | static_assert(arraysize(kBigSIUnits) == arraysize(kBigIECUnits), |
28 | "SI and IEC unit arrays must be the same size" ); |
29 | static_assert(arraysize(kSmallSIUnits) == arraysize(kBigSIUnits), |
30 | "Small SI and Big SI unit arrays must be the same size" ); |
31 | |
32 | static const int64_t kUnitsSize = arraysize(kBigSIUnits); |
33 | |
34 | void ToExponentAndMantissa(double val, int precision, double one_k, |
35 | std::string* mantissa, int64_t* exponent) { |
36 | std::stringstream mantissa_stream; |
37 | |
38 | if (val < 0) { |
39 | mantissa_stream << "-" ; |
40 | val = -val; |
41 | } |
42 | |
43 | // Adjust threshold so that it never excludes things which can't be rendered |
44 | // in 'precision' digits. |
45 | const double adjusted_threshold = |
46 | std::max(a: 1.0, b: 1.0 / std::pow(x: 10.0, y: precision)); |
47 | const double big_threshold = (adjusted_threshold * one_k) - 1; |
48 | const double small_threshold = adjusted_threshold; |
49 | // Values in ]simple_threshold,small_threshold[ will be printed as-is |
50 | const double simple_threshold = 0.01; |
51 | |
52 | if (val > big_threshold) { |
53 | // Positive powers |
54 | double scaled = val; |
55 | for (size_t i = 0; i < arraysize(kBigSIUnits); ++i) { |
56 | scaled /= one_k; |
57 | if (scaled <= big_threshold) { |
58 | mantissa_stream << scaled; |
59 | *exponent = i + 1; |
60 | *mantissa = mantissa_stream.str(); |
61 | return; |
62 | } |
63 | } |
64 | mantissa_stream << val; |
65 | *exponent = 0; |
66 | } else if (val < small_threshold) { |
67 | // Negative powers |
68 | if (val < simple_threshold) { |
69 | double scaled = val; |
70 | for (size_t i = 0; i < arraysize(kSmallSIUnits); ++i) { |
71 | scaled *= one_k; |
72 | if (scaled >= small_threshold) { |
73 | mantissa_stream << scaled; |
74 | *exponent = -static_cast<int64_t>(i + 1); |
75 | *mantissa = mantissa_stream.str(); |
76 | return; |
77 | } |
78 | } |
79 | } |
80 | mantissa_stream << val; |
81 | *exponent = 0; |
82 | } else { |
83 | mantissa_stream << val; |
84 | *exponent = 0; |
85 | } |
86 | *mantissa = mantissa_stream.str(); |
87 | } |
88 | |
89 | std::string ExponentToPrefix(int64_t exponent, bool iec) { |
90 | if (exponent == 0) return "" ; |
91 | |
92 | const int64_t index = (exponent > 0 ? exponent - 1 : -exponent - 1); |
93 | if (index >= kUnitsSize) return "" ; |
94 | |
95 | const char* const* array = |
96 | (exponent > 0 ? (iec ? kBigIECUnits : kBigSIUnits) : kSmallSIUnits); |
97 | |
98 | return std::string(array[index]); |
99 | } |
100 | |
101 | std::string ToBinaryStringFullySpecified(double value, int precision, |
102 | Counter::OneK one_k) { |
103 | std::string mantissa; |
104 | int64_t exponent; |
105 | ToExponentAndMantissa(val: value, precision, |
106 | one_k: one_k == Counter::kIs1024 ? 1024.0 : 1000.0, mantissa: &mantissa, |
107 | exponent: &exponent); |
108 | return mantissa + ExponentToPrefix(exponent, iec: one_k == Counter::kIs1024); |
109 | } |
110 | |
111 | std::string StrFormatImp(const char* msg, va_list args) { |
112 | // we might need a second shot at this, so pre-emptivly make a copy |
113 | va_list args_cp; |
114 | va_copy(args_cp, args); |
115 | |
116 | // TODO(ericwf): use std::array for first attempt to avoid one memory |
117 | // allocation guess what the size might be |
118 | std::array<char, 256> local_buff; |
119 | |
120 | // 2015-10-08: vsnprintf is used instead of snd::vsnprintf due to a limitation |
121 | // in the android-ndk |
122 | auto ret = vsnprintf(s: local_buff.data(), maxlen: local_buff.size(), format: msg, arg: args_cp); |
123 | |
124 | va_end(args_cp); |
125 | |
126 | // handle empty expansion |
127 | if (ret == 0) return std::string{}; |
128 | if (static_cast<std::size_t>(ret) < local_buff.size()) |
129 | return std::string(local_buff.data()); |
130 | |
131 | // we did not provide a long enough buffer on our first attempt. |
132 | // add 1 to size to account for null-byte in size cast to prevent overflow |
133 | std::size_t size = static_cast<std::size_t>(ret) + 1; |
134 | auto buff_ptr = std::unique_ptr<char[]>(new char[size]); |
135 | // 2015-10-08: vsnprintf is used instead of snd::vsnprintf due to a limitation |
136 | // in the android-ndk |
137 | vsnprintf(s: buff_ptr.get(), maxlen: size, format: msg, arg: args); |
138 | return std::string(buff_ptr.get()); |
139 | } |
140 | |
141 | } // end namespace |
142 | |
143 | std::string HumanReadableNumber(double n, Counter::OneK one_k) { |
144 | return ToBinaryStringFullySpecified(value: n, precision: 1, one_k); |
145 | } |
146 | |
147 | std::string StrFormat(const char* format, ...) { |
148 | va_list args; |
149 | va_start(args, format); |
150 | std::string tmp = StrFormatImp(msg: format, args); |
151 | va_end(args); |
152 | return tmp; |
153 | } |
154 | |
155 | std::vector<std::string> StrSplit(const std::string& str, char delim) { |
156 | if (str.empty()) return {}; |
157 | std::vector<std::string> ret; |
158 | size_t first = 0; |
159 | size_t next = str.find(c: delim); |
160 | for (; next != std::string::npos; |
161 | first = next + 1, next = str.find(c: delim, pos: first)) { |
162 | ret.push_back(x: str.substr(pos: first, n: next - first)); |
163 | } |
164 | ret.push_back(x: str.substr(pos: first)); |
165 | return ret; |
166 | } |
167 | |
168 | #ifdef BENCHMARK_STL_ANDROID_GNUSTL |
169 | /* |
170 | * GNU STL in Android NDK lacks support for some C++11 functions, including |
171 | * stoul, stoi, stod. We reimplement them here using C functions strtoul, |
172 | * strtol, strtod. Note that reimplemented functions are in benchmark:: |
173 | * namespace, not std:: namespace. |
174 | */ |
175 | unsigned long stoul(const std::string& str, size_t* pos, int base) { |
176 | /* Record previous errno */ |
177 | const int oldErrno = errno; |
178 | errno = 0; |
179 | |
180 | const char* strStart = str.c_str(); |
181 | char* strEnd = const_cast<char*>(strStart); |
182 | const unsigned long result = strtoul(strStart, &strEnd, base); |
183 | |
184 | const int strtoulErrno = errno; |
185 | /* Restore previous errno */ |
186 | errno = oldErrno; |
187 | |
188 | /* Check for errors and return */ |
189 | if (strtoulErrno == ERANGE) { |
190 | throw std::out_of_range("stoul failed: " + str + |
191 | " is outside of range of unsigned long" ); |
192 | } else if (strEnd == strStart || strtoulErrno != 0) { |
193 | throw std::invalid_argument("stoul failed: " + str + " is not an integer" ); |
194 | } |
195 | if (pos != nullptr) { |
196 | *pos = static_cast<size_t>(strEnd - strStart); |
197 | } |
198 | return result; |
199 | } |
200 | |
201 | int stoi(const std::string& str, size_t* pos, int base) { |
202 | /* Record previous errno */ |
203 | const int oldErrno = errno; |
204 | errno = 0; |
205 | |
206 | const char* strStart = str.c_str(); |
207 | char* strEnd = const_cast<char*>(strStart); |
208 | const long result = strtol(strStart, &strEnd, base); |
209 | |
210 | const int strtolErrno = errno; |
211 | /* Restore previous errno */ |
212 | errno = oldErrno; |
213 | |
214 | /* Check for errors and return */ |
215 | if (strtolErrno == ERANGE || long(int(result)) != result) { |
216 | throw std::out_of_range("stoul failed: " + str + |
217 | " is outside of range of int" ); |
218 | } else if (strEnd == strStart || strtolErrno != 0) { |
219 | throw std::invalid_argument("stoul failed: " + str + " is not an integer" ); |
220 | } |
221 | if (pos != nullptr) { |
222 | *pos = static_cast<size_t>(strEnd - strStart); |
223 | } |
224 | return int(result); |
225 | } |
226 | |
227 | double stod(const std::string& str, size_t* pos) { |
228 | /* Record previous errno */ |
229 | const int oldErrno = errno; |
230 | errno = 0; |
231 | |
232 | const char* strStart = str.c_str(); |
233 | char* strEnd = const_cast<char*>(strStart); |
234 | const double result = strtod(strStart, &strEnd); |
235 | |
236 | /* Restore previous errno */ |
237 | const int strtodErrno = errno; |
238 | errno = oldErrno; |
239 | |
240 | /* Check for errors and return */ |
241 | if (strtodErrno == ERANGE) { |
242 | throw std::out_of_range("stoul failed: " + str + |
243 | " is outside of range of int" ); |
244 | } else if (strEnd == strStart || strtodErrno != 0) { |
245 | throw std::invalid_argument("stoul failed: " + str + " is not an integer" ); |
246 | } |
247 | if (pos != nullptr) { |
248 | *pos = static_cast<size_t>(strEnd - strStart); |
249 | } |
250 | return result; |
251 | } |
252 | #endif |
253 | |
254 | } // end namespace benchmark |
255 | |