mirror of
https://github.com/json-c/json-c.git
synced 2026-09-07 08:36:50 +08:00
Merge pull request #930 from dxbjavid/get-double-locale-radix
fix locale-dependent strtod in json_object_get_double
This commit is contained in:
+38
-2
@@ -21,6 +21,9 @@
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#ifdef HAVE_LOCALE_H
|
||||
#include <locale.h>
|
||||
#endif /* HAVE_LOCALE_H */
|
||||
|
||||
#include "arraylist.h"
|
||||
#include "debug.h"
|
||||
@@ -1381,12 +1384,42 @@ double json_object_get_double(const struct json_object *jso)
|
||||
}
|
||||
case json_type_boolean: return JC_BOOL_C(jso)->c_boolean;
|
||||
case json_type_string:
|
||||
{
|
||||
const char *cstr = get_string_component(jso);
|
||||
const char *parse = cstr;
|
||||
char *radixconv = NULL;
|
||||
#ifdef HAVE_LOCALE_H
|
||||
/* A json_type_string holds the value with a '.' radix, as it
|
||||
* appears in JSON, but strtod() honours the current locale. In a
|
||||
* locale whose decimal point is not '.' the '.' is treated as a
|
||||
* stray character, so e.g. "19.95" is read as 0. Parse a copy
|
||||
* with the radix translated so the value comes back unchanged. */
|
||||
const char *decimal_point = localeconv()->decimal_point;
|
||||
if (decimal_point && decimal_point[0] != '.' && decimal_point[1] == '\0')
|
||||
{
|
||||
const char *dot = strchr(cstr, '.');
|
||||
if (dot)
|
||||
{
|
||||
size_t slen = strlen(cstr);
|
||||
radixconv = (char *)malloc(slen + 1);
|
||||
if (radixconv == NULL)
|
||||
{
|
||||
errno = ENOMEM;
|
||||
return 0.0;
|
||||
}
|
||||
memcpy(radixconv, cstr, slen + 1);
|
||||
radixconv[dot - cstr] = decimal_point[0];
|
||||
parse = radixconv;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
errno = 0;
|
||||
cdouble = strtod(get_string_component(jso), &errPtr);
|
||||
cdouble = strtod(parse, &errPtr);
|
||||
|
||||
/* if conversion stopped at the first character, return 0.0 */
|
||||
if (errPtr == get_string_component(jso))
|
||||
if (errPtr == parse)
|
||||
{
|
||||
free(radixconv);
|
||||
errno = EINVAL;
|
||||
return 0.0;
|
||||
}
|
||||
@@ -1398,6 +1431,7 @@ double json_object_get_double(const struct json_object *jso)
|
||||
*/
|
||||
if (*errPtr != '\0')
|
||||
{
|
||||
free(radixconv);
|
||||
errno = EINVAL;
|
||||
return 0.0;
|
||||
}
|
||||
@@ -1415,7 +1449,9 @@ double json_object_get_double(const struct json_object *jso)
|
||||
*/
|
||||
if ((HUGE_VAL == cdouble || -HUGE_VAL == cdouble) && (ERANGE == errno))
|
||||
cdouble = 0.0;
|
||||
free(radixconv);
|
||||
return cdouble;
|
||||
}
|
||||
default: errno = EINVAL; return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +40,18 @@ int main(int argc, char **argv)
|
||||
if (strcmp(buf1, buf2) != 0)
|
||||
printf("ERROR: Original locale not restored \"%s\" != \"%s\"", buf1, buf2);
|
||||
|
||||
// json_object_get_double() on a json_type_string must not depend on the
|
||||
// locale's decimal point. Under a comma-radix locale such as de_DE the
|
||||
// '.' used to be treated as a stray char, so "19.95" came back as 0.
|
||||
// (If the de_DE locale isn't installed we're still in "C" here, where
|
||||
// this passes anyway, so the check is safe to always run.)
|
||||
{
|
||||
json_object *numstr = json_object_new_string("19.95");
|
||||
double d = json_object_get_double(numstr);
|
||||
assert(d > 19.94 && d < 19.96);
|
||||
json_object_put(numstr);
|
||||
}
|
||||
|
||||
#ifdef HAVE_SETLOCALE
|
||||
setlocale(LC_NUMERIC, "C");
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user