From c0f79189dd25a72d6b2bfe82940e14b55cf95b38 Mon Sep 17 00:00:00 2001 From: Pekka Paalanen Date: Thu, 31 Jul 2025 18:03:59 +0300 Subject: [PATCH] shared: add safe_strtofloat() This is for parsing multiple numbers from one weston.ini key, which means I cannot use weston_config_section_get_double(). Also going to use float type, which has less range than double. Ironically, the tests fill likely fail on locales that do not use period as the radix character. Signed-off-by: Pekka Paalanen --- shared/string-helpers.h | 31 ++++++++++++++++++++++++ tests/string-test.c | 53 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 84 insertions(+) diff --git a/shared/string-helpers.h b/shared/string-helpers.h index 0c1f1ff6b..d566f8c2f 100644 --- a/shared/string-helpers.h +++ b/shared/string-helpers.h @@ -73,6 +73,37 @@ safe_strtoint(const char *str, int32_t *value) return true; } +/** Convert a localized string to a single-precision floating point value + * + * \param str The string to parse. Leading whitespace is ignored, + * otherwise the string must be exactly a real number in the current locale. + * \param[out] value Pointer to store the result to on success. + * \return True on success. False on failure and errno set to + * either ERANGE (under- or overflow) or EINVAL (the string is not strictly + * a number). + */ +static inline bool +safe_strtofloat(const char *str, float *value) +{ + float ret; + char *end; + + assert(str != NULL); + + errno = 0; + ret = strtof(str, &end); + if (errno != 0) { + return false; + } else if (end == str || *end != '\0') { + errno = EINVAL; + return false; + } + + *value = ret; + + return true; +} + /** * Exactly like asprintf(), but sets *str_out to NULL if it fails. * diff --git a/tests/string-test.c b/tests/string-test.c index 15603e5e5..2c89c5a85 100644 --- a/tests/string-test.c +++ b/tests/string-test.c @@ -88,3 +88,56 @@ TEST(strtol_conversions) return RESULT_OK; } + +TEST(strtof_conversions) +{ + float val; + + test_assert_true(safe_strtofloat("0.0", &val)); + test_assert_f32_eq(val, 0.0); + + test_assert_true(safe_strtofloat("-0.25", &val)); + test_assert_f32_eq(val, -0.25); + + test_assert_true(safe_strtofloat(" 10", &val)); + test_assert_f32_eq(val, 10.0); + + test_assert_true(safe_strtofloat("+2.2e-4", &val)); + test_assert_f32_eq(val, 2.2e-4); + + test_assert_true(safe_strtofloat("3.3e3", &val)); + test_assert_f32_eq(val, 3.3e3); + + test_assert_true(safe_strtofloat("nan", &val)); + test_assert_true(isnan(val)); + + test_assert_true(safe_strtofloat("inf", &val)); + test_assert_f32_eq(val, HUGE_VALF); + + test_assert_true(safe_strtofloat("-inf", &val)); + test_assert_f32_eq(val, -HUGE_VALF); + + + test_assert_false(safe_strtofloat("", &val)); + test_assert_int_eq(errno, EINVAL); + + test_assert_false(safe_strtofloat("x", &val)); + test_assert_int_eq(errno, EINVAL); + + test_assert_false(safe_strtofloat("15k", &val)); + test_assert_int_eq(errno, EINVAL); + + test_assert_false(safe_strtofloat("b2.2", &val)); + test_assert_int_eq(errno, EINVAL); + + test_assert_false(safe_strtofloat("1.3f", &val)); + test_assert_int_eq(errno, EINVAL); + + test_assert_false(safe_strtofloat("1e-500", &val)); + test_assert_int_eq(errno, ERANGE); + + test_assert_false(safe_strtofloat("1e+500", &val)); + test_assert_int_eq(errno, ERANGE); + + return RESULT_OK; +}