1 /**************************************************************************
2 Copyright:
3 (C) 2008 - 2012 Alexander Shaduri <ashaduri 'at' gmail.com>
4 License: See LICENSE_zlib.txt file
5 ***************************************************************************/
6 /// \file
7 /// \author Alexander Shaduri
8 /// \ingroup hz
9 /// \weakgroup hz
10 /// @{
11
12 #ifndef HZ_STRING_NUM_H
13 #define HZ_STRING_NUM_H
14
15 #include "hz_config.h" // feature macros
16
17 #include <string>
18 #include <sstream>
19 #include <iomanip> // setbase, setprecision, setw
20 #include <ios> // std::fixed, std::internal
21 #include <locale> // std::locale::classic()
22 #include <limits> // std::numeric_limits
23 #include <cerrno> // errno (not std::errno, it may be a macro)
24 #include <cstring> // std::strncmp
25
26 #include "type_properties.h" // type_is_*
27 #include "type_categories.h" // type_check_category
28 // #include "static_assert.h" // HZ_STATIC_ASSERT
29 #include "ascii.h" // ascii_*
30
31
32 /**
33 \file
34 String to number and number to string conversions
35 */
36
37
38
39 namespace hz {
40
41
42 /// Check whether a string represents a numeric value (the value must
43 /// be represented in C locale).
44
45 /// strict == true indicates that the whole string must represent a number exactly.
46 /// strict == false allows the string to contain the number only in its beginning
47 /// (ignores any leading spaces and trailing garbage).
48 /// base has an effect only for integral types. For bool it specifies whether to
49 /// accept "true" and "false" (as opposed to 1 and 0). For others, base should
50 /// be between 2 and 36 inclusive.
51 /// This function has no definition, only specializations.
52 template<typename T> inline
53 bool string_is_numeric(const std::string& s, T& number, bool strict, int base_or_boolalpha);
54
55 /// Short version with default base. (Needed because default base is different for bool and int).
56 template<typename T> inline
57 bool string_is_numeric(const std::string& s, T& number, bool strict = true);
58
59
60 /// A convenience string_is_numeric wrapper.
61 /// Note that in strict mode, T() is returned for invalid values.
62 template<typename T> inline
63 T string_to_number(const std::string& s, bool strict, int base_or_boolalpha);
64
65 /// Short version with default base. (Needed because default base is different for bool and int).
66 template<typename T> inline
67 T string_to_number(const std::string& s, bool strict = true);
68
69
70 /// Convert numeric value to string. alpha_or_base_or_precision means:
71 /// for bool, 0 means 1/0, 1 means true/false;
72 /// for int family (including char), it's the base to format in (8, 10, 16 are definitely supported);
73 /// for float family, it controls the number of digits after comma.
74 template<typename T> inline
75 std::string number_to_string(T number, int alpha_or_base_or_precision, bool fixed_prec = false);
76
77 /// Short version with default base / precision.
78 template<typename T> inline
79 std::string number_to_string(T number);
80
81
82 }
83
84
85
86
87 // ------------------------------------------- Implementation
88
89
90
91 namespace hz {
92
93
94
95 template<typename T> inline
string_to_number(const std::string & s,bool strict,int base_or_boolalpha)96 T string_to_number(const std::string& s, bool strict, int base_or_boolalpha)
97 {
98 T value = T();
99 hz::string_is_numeric(s, value, strict, base_or_boolalpha);
100 return value;
101 }
102
103
104
105 template<typename T> inline
string_to_number(const std::string & s,bool strict)106 T string_to_number(const std::string& s, bool strict)
107 {
108 T value = T();
109 hz::string_is_numeric(s, value, strict);
110 return value;
111 }
112
113
114
115 // Definition of the one without base parameter.
116 template<typename T> inline
string_is_numeric(const std::string & s,T & number,bool strict)117 bool string_is_numeric(const std::string& s, T& number, bool strict)
118 {
119 int base = 0;
120 if (type_is_same<bool, T>::value) {
121 base = 1; // use alpha (true / false), as opposed to 1 / 0.
122
123 } else if (type_is_integral<T>::value) {
124 base = 0; // auto-detect base 10, 16 (0xNUM), 8 (0NUM).
125 }
126 // base is ignored for other types
127
128 return string_is_numeric<T>(s, number, strict, base);
129 }
130
131
132
133 // Note: Parameter "base" is ignored for floating point types.
134 #define DEFINE_STRING_IS_NUMERIC(type) \
135 template<> inline \
136 bool string_is_numeric<type>(const std::string& s, type& number, bool strict, int base) \
137 { \
138 if (s.empty() || (strict && hz::ascii_isspace(s[0]))) /* ascii_strtoi() skips the leading spaces */ \
139 return false; \
140 const char* str = s.c_str(); \
141 char* end = 0; \
142 errno = 0; \
143 type tmp = hz::ascii_strton<type>(str, &end, base); \
144 if ( (strict ? (*end == '\0') : (end != str)) && errno == 0 ) { /* if end is 0 byte, then the whole string was parsed */ \
145 number = tmp; \
146 return true; \
147 } \
148 return false; \
149 }
150
151
152
153
154 // Define string_is_numeric<T>() function specializations for various types:
155
156 // Specialization for bool
157 template<> inline
158 bool string_is_numeric<bool>(const std::string& s, bool& number, bool strict, int boolalpha_enabled)
159 {
160 if (s.empty() || (strict && hz::ascii_isspace(s[0]))) // ascii_strtoi() skips the leading spaces
161 return false;
162 const char* str = s.c_str();
163
164 if (boolalpha_enabled) {
165 // skip spaces. won't do anything in strict mode (we already ruled out spaces there)
166 while (hz::ascii_isspace(*str)) {
167 ++str;
168 }
169
170 // contains "true" at start, or equals to "true" if strict.
171 if (std::strncmp(str, "true", 4) == 0 && (!strict || str[4] == '\0')) { // str is 0-terminated, so no violation here
172 number = true;
173 return true;
174 }
175 // same for "false"
176 if (std::strncmp(str, "false", 5) == 0 && (!strict || str[5] == '\0')) {
177 number = false;
178 return true;
179 }
180 return false;
181 }
182
183 char* end = 0;
184 errno = 0;
185 // we use ascii_strtoi here to support of +001, etc...
186 bool tmp = hz::ascii_strtoi<bool>(str, &end, 0); // auto-base
187 if ( (strict ? (*end == '\0') : (end != str)) && errno == 0 ) { // if end is 0 byte, then the whole string was parsed
188 number = tmp;
189 return true;
190 }
191 return false;
192 }
193
194
195
196
197 DEFINE_STRING_IS_NUMERIC(char)
DEFINE_STRING_IS_NUMERIC(signed char)198 DEFINE_STRING_IS_NUMERIC(signed char)
199 DEFINE_STRING_IS_NUMERIC(unsigned char)
200 DEFINE_STRING_IS_NUMERIC(wchar_t)
201
202 DEFINE_STRING_IS_NUMERIC(short int)
203 DEFINE_STRING_IS_NUMERIC(unsigned short int)
204
205 DEFINE_STRING_IS_NUMERIC(int)
206 DEFINE_STRING_IS_NUMERIC(unsigned int)
207
208 DEFINE_STRING_IS_NUMERIC(long int)
209 DEFINE_STRING_IS_NUMERIC(unsigned long int)
210
211 #if !(defined DISABLE_LL_INT && DISABLE_LL_INT)
212 DEFINE_STRING_IS_NUMERIC(long long int)
213 #endif
214 #if !(defined DISABLE_ULL_INT && DISABLE_ULL_INT)
215 DEFINE_STRING_IS_NUMERIC(unsigned long long int)
216 #endif
217
218 DEFINE_STRING_IS_NUMERIC(float)
219 DEFINE_STRING_IS_NUMERIC(double)
220 DEFINE_STRING_IS_NUMERIC(long double)
221
222
223
224 #undef DEFINE_STRING_IS_NUMERIC
225
226
227
228
229
230 // ------------------------------- number_to_string
231
232
233
234 namespace internal {
235
236
237 template<typename T, typename SpecCat = typename type_check_category<T>::type>
238 struct number_to_string_impl {
239 // static std::string func(T number, int alpha_or_base_or_precision, bool ignored_param)
240 // {
241 // HZ_STATIC_ASSERT(hz::static_false<T>::value, not_a_number);
242 // return std::string();
243 // }
244 };
245
246
247 // bool spec
248 template<typename T>
249 struct number_to_string_impl<T, type_cat_bool> {
250 static std::string func(T number, int boolalpha_enabled, bool ignored_param)
251 {
252 if (boolalpha_enabled)
253 return (number ? "true" : "false");
254 return (number ? "1" : "0");
255 }
256 };
257
258
259 // int spec
260 template<typename T>
261 struct number_to_string_impl<T, type_cat_int> {
262 static std::string func(T number, int base, bool ignored_param)
263 {
264 if (number == 0) {
265 if (base == 16) {
266 return "0x" + std::string(sizeof(T) * 2, '0'); // 0 doesn't print as 0x0000, but as 000000. fix that.
267
268 } else if (base == 8) { // same here, 0 prints as 0.
269 return "00"; // better than simply 0 (at least it's clearly octal).
270 }
271 // base 10 can possibly have some funny formatting, so continue...
272 }
273
274 std::ostringstream ss;
275 ss.imbue(std::locale::classic()); // make it use classic locale
276
277 if (base == 16) {
278 // setfill & internal: leading 0s between 0x and XXXX.
279 // setw: e.g. for int32, we need 4*2 (size * 2 chars for byte) + 2 (0x) width.
280 ss << std::setfill('0') << std::internal << std::setw(static_cast<int>((sizeof(T) * 2) + 2));
281 }
282
283 ss << std::showbase << std::setbase(base) << number;
284
285 return ss.str();
286 }
287 };
288
289
290 // char spec
291 template<typename T>
292 struct number_to_string_impl<T, type_cat_char> {
293 static std::string func(T number, int base, bool ignored_param)
294 {
295 return number_to_string(static_cast<long int>(number), base); // long int should be > (u)char
296 }
297 };
298
299
300 // floats spec
301 template<typename T>
302 struct number_to_string_impl<T, type_cat_float> {
303 static std::string func(T number, int precision, bool fixed_prec)
304 {
305 std::ostringstream ss;
306 ss.imbue(std::locale::classic()); // make it use classic locale
307 // without std::fixed, precision is counted as all digits, as opposed to only after comma.
308 if (fixed_prec)
309 ss << std::fixed;
310 ss << std::setprecision(precision) << number;
311 return ss.str();
312 }
313 };
314
315
316 } // ns internal
317
318
319
320
321 // public function with 2 parameters
322 template<typename T> inline
323 std::string number_to_string(T number, int boolalpha_or_base_or_precision, bool fixed_prec)
324 {
325 return internal::number_to_string_impl<T>::func(number, boolalpha_or_base_or_precision, fixed_prec);
326 }
327
328
329
330 // public function - short version with default base / precision
331 template<typename T> inline
332 std::string number_to_string(T number)
333 {
334 int base = 0;
335 if (type_is_same<bool, T>::value) {
336 base = 1; // alpha (true / false), as opposed to 1 / 0.
337
338 } else if (type_is_integral<T>::value) {
339 base = 10; // default base - 10
340
341 } else if (type_is_floating_point<T>::value) {
342 base = std::numeric_limits<T>::digits10 + 1; // precision. 1 is for sign
343 }
344
345 // don't use fixed prec here, digits10 is for the whole number
346 return internal::number_to_string_impl<T>::func(number, base, false);
347 }
348
349
350
351
352
353
354 } // ns
355
356
357
358
359 #endif
360
361 /// @}
362