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; +}