Mercurial > libjeffpc
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); + } +}