changeset 858:797cc20540c6

int: add str2float and str2double These functions are similar to str2u* but instead of parsing an unsigned int, they parse a floating point number. Signed-off-by: Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
author Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
date Thu, 24 Nov 2022 22:28:53 -0500
parents 2c1345a06ba1
children c5a5b39f464b
files include/jeffpc/int.h tests/CMakeLists.txt tests/test_str2float.c
diffstat 3 files changed, 210 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/include/jeffpc/int.h	Tue Oct 04 22:25:33 2022 -0400
+++ b/include/jeffpc/int.h	Thu Nov 24 22:28:53 2022 -0500
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2016-2020 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ * Copyright (c) 2016-2020,2022 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
  *
  * Permission is hereby granted, free of charge, to any person obtaining a copy
  * of this software and associated documentation files (the "Software"), to deal
@@ -85,6 +85,43 @@
 #undef STR_TO_INT
 
 /*
+ * string to float conversion
+ */
+
+#define STR_TO_FLOAT(type, cvtfxn)					\
+static inline int str2##type##_full(const char *restrict s,		\
+				    type *n,				\
+				    char terminator)			\
+{									\
+	char *endptr;							\
+	type tmp;							\
+									\
+	*n = 0;								\
+									\
+	errno = 0;							\
+	tmp = cvtfxn(s, &endptr);					\
+									\
+	if (errno)							\
+		return -errno;						\
+									\
+	/* parsed nothing or input is empty */				\
+	if (endptr == s)						\
+		return -EINVAL;						\
+									\
+	/* first unparsed char must be the expected terminator */	\
+	if (*endptr != (terminator))					\
+		return -EINVAL;						\
+									\
+	*n = tmp;							\
+	return 0;							\
+}
+
+STR_TO_FLOAT(float, strtof)
+STR_TO_FLOAT(double, strtod)
+
+#undef STR_TO_FLOAT
+
+/*
  * These prototypes exist to catch bugs in the code generating macros above.
  */
 static inline int str2u64_full(const char *restrict s, uint64_t *i,
@@ -96,6 +133,11 @@
 static inline int str2u8_full(const char *restrict s, uint8_t *i,
 			      int base, char terminator);
 
+static inline int str2float_full(const char *restrict s, float *n,
+				 char terminator);
+static inline int str2double_full(const char *restrict s, double *n,
+				  char terminator);
+
 /* base [2, 36], nul-terminated */
 #define str2u64_base(s, i, b)	str2u64_full((s), (i), (b), '\0')
 #define str2u32_base(s, i, b)	str2u32_full((s), (i), (b), '\0')
@@ -108,6 +150,10 @@
 #define str2u16(s, i)	str2u16_full((s), (i), 10, '\0')
 #define str2u8(s, i)	str2u8_full((s), (i), 10, '\0')
 
+/* floats (base 2 and 10), nul-terminated */
+#define str2float(s, n)		str2float_full((s), (n), '\0')
+#define str2double(s, n)	str2double_full((s), (n), '\0')
+
 /*
  * Powers of 2 mangling
  */
--- a/tests/CMakeLists.txt	Tue Oct 04 22:25:33 2022 -0400
+++ b/tests/CMakeLists.txt	Thu Nov 24 22:28:53 2022 -0500
@@ -1,5 +1,5 @@
 #
-# Copyright (c) 2016-2020 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+# Copyright (c) 2016-2020,2022 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
 #
 # Permission is hereby granted, free of charge, to any person obtaining a copy
 # of this software and associated documentation files (the "Software"), to deal
@@ -107,6 +107,7 @@
 build_test_bin_and_run(sexpr_compact)
 build_test_bin_and_run(sexpr_eval)
 build_test_bin_and_run(sexpr_iter)
+build_test_bin_and_run(str2float)
 build_test_bin_and_run(str2uint)
 build_test_bin_and_run(tree_bst)
 build_test_bin_and_run(tree_rb)
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/tests/test_str2float.c	Thu Nov 24 22:28:53 2022 -0500
@@ -0,0 +1,161 @@
+/*
+ * Copyright (c) 2019,2022 Josef 'Jeff' Sipek <jeffpc@josefsipek.net>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ * SOFTWARE.
+ */
+
+#include <jeffpc/int.h>
+#include <jeffpc/types.h>
+#include <jeffpc/error.h>
+
+#include "test.c"
+
+struct res {
+	int ret;
+	union {
+		uint32_t i_float;
+		uint64_t i_double;
+		float v_float;
+		double v_double;
+	};
+};
+
+struct run {
+	const char *in;
+	struct res out[2]; /* out[size] */
+};
+
+#define SZ32	0
+#define SZ64	1
+
+#define ENT(v_f, v_d, r_f, r_d) \
+	{ \
+		[SZ32] = { .ret = (r_f), .v_float = (v_f), }, \
+		[SZ64] = { .ret = (r_d), .v_double = (v_d), }, \
+	}
+
+static const struct run runs[] = {
+	{
+		.in  = "",
+		.out = ENT(0, 0, -EINVAL, -EINVAL),
+	},
+	/*
+	 * Check various well-formed inputs
+	 */
+	{
+		.in  = "0",
+		.out = ENT(0, 0, 0, 0),
+	},
+	{
+		.in  = "00",
+		.out = ENT(0, 0, 0, 0),
+	},
+	{
+		.in  = "0x0",
+		.out = ENT(0, 0, 0, 0),
+	},
+	{
+		.in  = "5",
+		.out = ENT(5, 5, 0, 0),
+	},
+	{
+		.in  = "0x8",
+		.out = ENT(8, 8, 0, 0),
+	},
+	{
+		.in  = "A",
+		.out = ENT(0, 0, -EINVAL, -EINVAL),
+	},
+	{
+		.in  = "a",
+		.out = ENT(0, 0, -EINVAL, -EINVAL),
+	},
+	{
+		.in  = "0XA",
+		.out = ENT(0xa, 0xa, 0, 0),
+	},
+	{
+		.in  = "0Xa",
+		.out = ENT(0xa, 0xa, 0, 0),
+	},
+	{
+		.in  = "0xA",
+		.out = ENT(0xa, 0xa, 0, 0),
+	},
+	{
+		.in  = "0xa",
+		.out = ENT(0xa, 0xa, 0, 0),
+	},
+	/*
+	 * FIXME: more test cases
+	 *   - check inputs with a leading + and -
+	 *   - check inputs with decimals
+	 *   - check inputs with leading garbage
+	 *   - check inputs with trailing garbage
+	 */
+};
+
+#define TEST(i, szidx, expr, type)					\
+	do {								\
+		const char *in = runs[i].in;				\
+		const struct res *res = &runs[i].out[szidx];		\
+		struct res got;						\
+		type tmp;						\
+		int ret;						\
+									\
+		fprintf(stderr, "%2d: ... %s -> ", (i), #expr);		\
+									\
+		ret = (expr);						\
+		got.v_##type = tmp;					\
+									\
+		/* print the result we got */				\
+		if (ret) {						\
+			fprintf(stderr, "ret=%d", ret);			\
+		} else {						\
+			static const char *fmts[] = {			\
+				[SZ32] = "%f",				\
+				[SZ64] = "%g",				\
+			};						\
+									\
+			fprintf(stderr, "out=");			\
+			fprintf(stderr, fmts[szidx], tmp);		\
+		}							\
+		fprintf(stderr, "...");					\
+									\
+		check_rets(res->ret, ret, "str2%s", #type);		\
+									\
+		if (!ret) {						\
+			ASSERT3U(got.i_##type, ==, res->i_##type);	\
+			ASSERT3U(got.v_##type, ==, res->v_##type);	\
+		}							\
+									\
+		fprintf(stderr, "ok.\n");				\
+	} while (0)
+
+void test(void)
+{
+	int i;
+
+	for (i = 0; i < ARRAY_LEN(runs); i++) {
+		fprintf(stderr, "%2d: in='%s'...\n", i, runs[i].in);
+
+		TEST(i, SZ32, str2float(in, &tmp), float);
+		TEST(i, SZ64, str2double(in, &tmp), double);
+	}
+}