int float_validator_open( struct sol_flow_node *node, void *data, const struct sol_flow_node_options *options) { struct float_validator_data *mdata = data; const struct sol_flow_node_type_test_float_validator_options *opts = (const struct sol_flow_node_type_test_float_validator_options *)options; const char *it; char *tail; double *val; SOL_FLOW_NODE_OPTIONS_SUB_API_CHECK(options, SOL_FLOW_NODE_TYPE_TEST_FLOAT_VALIDATOR_OPTIONS_API_VERSION, -EINVAL); mdata->done = false; if (opts->sequence == NULL || *opts->sequence == '\0') { SOL_ERR("Option 'sequence' is either NULL or empty."); return -EINVAL; } sol_vector_init(&mdata->values, sizeof(double)); it = opts->sequence; do { val = sol_vector_append(&mdata->values); SOL_NULL_CHECK_GOTO(val, no_memory); *val = sol_util_strtod_n(it, &tail, -1, false); if (errno) { SOL_WRN("Failed do convert option 'sequence' to double %s: %d", it, errno); goto error; } if (it == tail) { SOL_WRN("Failed to convert option 'sequence' to double %s", it); errno = EINVAL; goto error; } it = tail; } while (*tail != '\0'); return 0; no_memory: errno = ENOMEM; error: sol_vector_clear(&mdata->values); return -errno; }
SOL_API int sol_json_token_get_double(const struct sol_json_token *token, double *value) { char *endptr; int r; /* NOTE: Using a copy to ensure trailing \0 and strtod() so we * properly parse numbers with large precision. * * Splitting the integer, fractional and exponent parts and doing * the math using double numbers will result in rounding errors * when parsing DBL_MAX using "%.64g" formatting. * * Since parsing it is complex (ie: * http://www.netlib.org/fp/dtoa.c), we take the short path to * call our helper around libc's strtod() that limits the amount * of bytes. */ SOL_NULL_CHECK(token, -EINVAL); SOL_NULL_CHECK(value, -EINVAL); *value = sol_util_strtod_n(token->start, &endptr, sol_json_token_get_size(token), false); r = -errno; if (endptr == token->start) r = -EINVAL; else if (isinf(*value)) { SOL_DBG("token '%.*s' is infinite", (int)sol_json_token_get_size(token), token->start); if (*value < 0) *value = -DBL_MAX; else *value = DBL_MAX; r = -ERANGE; } else if (isnan(*value)) { SOL_DBG("token '%.*s' is not a number", (int)sol_json_token_get_size(token), token->start); *value = 0; r = -EINVAL; } else if (fpclassify(*value) == FP_SUBNORMAL) { r = 0; } return r; }
static inline double strtod_no_locale(const char *nptr, char **endptr) { return sol_util_strtod_n(nptr, endptr, -1, false); }
static void test_strtodn(void) { #ifdef HAVE_LOCALE char *oldloc; const char *comma_locales[] = { "pt", "pt_BR", "de", "it", "ru", NULL }; const char *comma_locale; #endif char dbl_max_str[256], neg_dbl_max_str[256]; char dbl_max_str_overflow[256], neg_dbl_max_str_overflow[256]; const struct test { const char *str; double reference; int expected_errno; bool use_locale; int endptr_offset; } *itr, tests[] = { { "0", 0.0, 0, false, -1 }, { "123", 123.0, 0, false, -1 }, { "1.0", 1.0, 0, false, -1 }, { "123.456", 123.456, 0, false, -1 }, { "345e+12", 345e12, 0, false, -1 }, { "345e-12", 345e-12, 0, false, -1 }, { "345E+12", 345e12, 0, false, -1 }, { "345E-12", 345e-12, 0, false, -1 }, { "-1.0", -1.0, 0, false, -1 }, { "-123.456", -123.456, 0, false, -1 }, { "-345e+12", -345e12, 0, false, -1 }, { "-345e-12", -345e-12, 0, false, -1 }, { "-345E+12", -345e12, 0, false, -1 }, { "-345E-12", -345e-12, 0, false, -1 }, { "-345.678e+12", -345.678e12, 0, false, -1 }, { "-345.678e-12", -345.678e-12, 0, false, -1 }, { "-345.678E+12", -345.678e12, 0, false, -1 }, { "-345.678E-12", -345.678e-12, 0, false, -1 }, { dbl_max_str, DBL_MAX, 0, false, -1 }, { neg_dbl_max_str, -DBL_MAX, 0, false, -1 }, { dbl_max_str_overflow, DBL_MAX, ERANGE, false, -1 }, { neg_dbl_max_str_overflow, -DBL_MAX, ERANGE, false, -1 }, { "x", 0, 0, false, 0 }, { "1x", 1.0, 0, false, 1 }, { "12,3", 12.0, 0, false, 2 }, { "", 0, 0, false, 0 }, #ifdef HAVE_LOCALE /* commas as decimal separators */ { "1,0", 1.0, 0, true, -1 }, { "123,456", 123.456, 0, true, -1 }, { "345e+12", 345e12, 0, true, -1 }, { "345e-12", 345e-12, 0, true, -1 }, { "345E+12", 345e12, 0, true, -1 }, { "345E-12", 345e-12, 0, true, -1 }, { "-1,0", -1.0, 0, true, -1 }, { "-123,456", -123.456, 0, true, -1 }, { "-345e+12", -345e12, 0, true, -1 }, { "-345e-12", -345e-12, 0, true, -1 }, { "-345E+12", -345e12, 0, true, -1 }, { "-345E-12", -345e-12, 0, true, -1 }, { "-345,678e+12", -345.678e12, 0, true, -1 }, { "-345,678e-12", -345.678e-12, 0, true, -1 }, { "-345,678E+12", -345.678e12, 0, true, -1 }, { "-345,678E-12", -345.678e-12, 0, true, -1 }, { "12.3", 12.0, 0, true, 2 }, #endif {} }; #ifdef HAVE_LOCALE oldloc = setlocale(LC_ALL, NULL); if (oldloc) oldloc = strdupa(oldloc); setlocale(LC_ALL, "C"); #endif snprintf(dbl_max_str, sizeof(dbl_max_str), "%.64g", DBL_MAX); snprintf(neg_dbl_max_str, sizeof(neg_dbl_max_str), "%.64g", -DBL_MAX); snprintf(dbl_max_str_overflow, sizeof(dbl_max_str_overflow), "%.64g0", DBL_MAX); snprintf(neg_dbl_max_str_overflow, sizeof(neg_dbl_max_str_overflow), "%.64g0", -DBL_MAX); #ifdef HAVE_LOCALE { const char **loc; comma_locale = NULL; for (loc = comma_locales; *loc != NULL; loc++) { if (setlocale(LC_ALL, *loc)) { setlocale(LC_ALL, oldloc); SOL_DBG("Using locale '%s' to produce commas as " "decimal separator. Ex: %0.2f", *loc, 1.23); comma_locale = *loc; break; } } if (!comma_locale) { setlocale(LC_ALL, oldloc); SOL_WRN("Couldn't find a locale with decimal commas"); } } #endif for (itr = tests; itr->str != NULL; itr++) { double value; char buf[512]; char *endptr; size_t slen = strlen(itr->str); int endptr_offset, wanted_endptr_offset; int reterr; snprintf(buf, sizeof(buf), "%s123garbage", itr->str); if (comma_locale) setlocale(LC_ALL, comma_locale); else if (itr->use_locale) { SOL_DBG("SKIP (no comma locale): '%s'", itr->str); continue; } value = sol_util_strtod_n(buf, &endptr, slen, itr->use_locale); reterr = errno; endptr_offset = endptr - buf; if (comma_locale) setlocale(LC_ALL, oldloc); wanted_endptr_offset = itr->endptr_offset; if (wanted_endptr_offset < 0) wanted_endptr_offset = slen; if (itr->expected_errno == 0 && reterr == 0) { if (sol_util_double_eq(itr->reference, value)) { SOL_DBG("OK: parsed '%s' as %g (locale:%u)", itr->str, value, itr->use_locale); } else { SOL_WRN("FAILED: parsed '%s' as %.64g where %.64g was expected" " (difference = %g) (locale:%u)", itr->str, value, itr->reference, itr->reference - value, itr->use_locale); FAIL(); } } else if (itr->expected_errno == 0 && reterr < 0) { SOL_WRN("FAILED: parsing '%s' failed with errno = %d (%s) (locale:%u)", itr->str, reterr, sol_util_strerrora(reterr), itr->use_locale); FAIL(); } else if (itr->expected_errno != 0 && reterr == 0) { SOL_WRN("FAILED: parsing '%s' should fail with errno = %d (%s)" ", but got success with errno = %d (%s), value = %g (locale:%u)", itr->str, itr->expected_errno, sol_util_strerrora(itr->expected_errno), reterr, sol_util_strerrora(reterr), value, itr->use_locale); FAIL(); } else if (itr->expected_errno != 0 && reterr < 0) { if (itr->expected_errno != reterr) { SOL_WRN("FAILED: parsing '%s' should fail with errno = %d (%s)" ", but got errno = %d (%s), value = %g (locale:%u)", itr->str, itr->expected_errno, sol_util_strerrora(itr->expected_errno), reterr, sol_util_strerrora(reterr), value, itr->use_locale); FAIL(); } else if (!sol_util_double_eq(itr->reference, value)) { SOL_WRN("FAILED: parsing '%s' should result in %.64g" ", but got %.64g (difference = %g) (locale:%u)", itr->str, itr->reference, value, itr->reference - value, itr->use_locale); FAIL(); } else { SOL_DBG("OK: parsed '%s' as %g, setting errno = %d (%s) (locale:%u)", itr->str, value, reterr, sol_util_strerrora(reterr), itr->use_locale); } } if (wanted_endptr_offset != endptr_offset) { SOL_WRN("FAILED: parsing '%s' should stop at offset %d, but got %d (locale:%u)", itr->str, wanted_endptr_offset, endptr_offset, itr->use_locale); FAIL(); } } }