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