Make integers be first-class citizens, including full precision in longlong+float case

This commit is contained in:
No Default Name 2022-04-29 23:11:06 +02:00
parent 882361c428
commit e79376f2d9
4 changed files with 97 additions and 30 deletions

33
cJSON.c
View File

@ -105,7 +105,7 @@
#ifdef _WIN32
#define NAN sqrt(-1.0)
#else
#define NAN 0.0/0.0
#define NAN (0.0/0.0)
#endif
#endif
@ -140,6 +140,16 @@ CJSON_PUBLIC(cJSON_float) cJSON_GetNumberValue(const cJSON * const item)
return item->valuedouble;
}
CJSON_PUBLIC(cJSON_int) cJSON_GetIntValue(const cJSON * const item)
{
if (!cJSON_IsNumber(item))
{
return 0;
}
return item->valueint;
}
/* This is a safeguard to prevent copy-pasters from using incompatible C and header files */
#if (CJSON_VERSION_MAJOR != 1) || (CJSON_VERSION_MINOR != 7) || (CJSON_VERSION_PATCH != 15)
#error cJSON.h and cJSON.c have different versions. Make sure that both have the same.
@ -387,6 +397,7 @@ loop_end:
}
item->valuedouble = number;
item->type = cJSON_Number;
/* use saturation in case of overflow */
/* note that even float has range beyond long long (though inexactly) */
@ -402,14 +413,13 @@ loop_end:
{
/* integer is in range, parse as integer to prevent float inexactness */
item->valueint = strtoint(number_c_string);
item->type |= cJSON_PreferInt;
}
else
{
item->valueint = (cJSON_int)number;
}
item->type = cJSON_Number;
input_buffer->offset += (size_t)(after_end - number_c_string);
return true;
}
@ -429,7 +439,7 @@ CJSON_PUBLIC(cJSON_float) cJSON_SetNumberHelper(cJSON *object, cJSON_float numbe
{
object->valueint = (cJSON_int)number;
}
object->type &= ~cJSON_PreferInt;
return object->valuedouble = number;
}
@ -602,22 +612,21 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out
return false;
}
/* This checks for NaN and Infinity */
if (isnan(d) || isinf(d))
{
length = sprintf((char*)number_buffer, "null");
}
else if (has_no_decimals(d) && item->valueint != CJSON_INT_MAX &&
item->valueint != CJSON_INT_MIN && d == (cJSON_float)item->valueint)
if (item->type & cJSON_PreferInt)
{
length = sprintf((char*)number_buffer, intfmt, item->valueint);
}
else if (isnan(d) || isinf(d))
/* note that isnan/isinf will never work when compiling with -ffast-math */
{
length = sprintf((char*)number_buffer, "null");
}
else
{
/* Try 15 (6) decimal places of precision to avoid nonsignificant nonzero digits */
length = sprintf((char*)number_buffer, floatfmt_shorter, d);
/* Check whether the original double can be recovered */
/* Check whether the original value can be recovered */
test = strtofloat((char*)number_buffer, &test_endptr);
if (test_endptr == NULL || test_endptr == (char *)number_buffer || *test_endptr != '\0' || !compare_cJSON_float((cJSON_float)test, d))
{

28
cJSON.h
View File

@ -28,6 +28,19 @@ extern "C"
{
#endif
/* CJSON_INT_USE_LONGLONG
Compile-time option to use "long long" instead of default "int".
Note: Default cmake rules will force C89 and prevent long long.
To use long long, delete build tree, then run:
CFLAGS="-Wall -Werror" cmake -DENABLE_CUSTOM_COMPILER_FLAGS=Off <sourcedir>
*/
#define notCJSON_INT_USE_LONGLONG
/* CJSON_FLOAT_USE_FLOAT
Compile-time option to use "float" instead of default "double".
*/
#define notCJSON_FLOAT_USE_FLOAT
#if !defined(__WINDOWS__) && (defined(WIN32) || defined(WIN64) || defined(_MSC_VER) || defined(_WIN32))
#define __WINDOWS__
#endif
@ -98,12 +111,7 @@ then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJ
#define cJSON_IsReference 256
#define cJSON_StringIsConst 512
#define notCJSON_INT_USE_LONGLONG
/* Note: Default cmake rules will force C89 and prevent long long.
To use long long, delete build tree, then run:
CFLAGS="-Wall -Werror" cmake -DENABLE_CUSTOM_COMPILER_FLAGS=Off <sourcedir>
*/
#define cJSON_PreferInt 1024
#ifdef CJSON_INT_USE_LONGLONG
typedef long long cJSON_int;
@ -111,8 +119,6 @@ typedef long long cJSON_int;
typedef int cJSON_int;
#endif
#define notCJSON_FLOAT_USE_FLOAT
#ifdef CJSON_FLOAT_USE_FLOAT
typedef float cJSON_float;
#else
@ -133,9 +139,10 @@ typedef struct cJSON
/* The item's string, if type==cJSON_String and type == cJSON_Raw */
char *valuestring;
/* writing to valueint is DEPRECATED, use cJSON_SetNumberValue instead */
/* writing to valueint is DEPRECATED, use cJSON_SetIntValue instead */
cJSON_int valueint;
/* The item's number, if type==cJSON_Number */
/* writing to valuedouble is DEPRECATED, use cJSON_SetNumberValue instead */
cJSON_float valuedouble;
/* The item's name string, if this item is the child of, or is in the list of subitems of an object. */
@ -198,6 +205,7 @@ CJSON_PUBLIC(const char *) cJSON_GetErrorPtr(void);
/* Check item type and return its value */
CJSON_PUBLIC(char *) cJSON_GetStringValue(const cJSON * const item);
CJSON_PUBLIC(cJSON_float) cJSON_GetNumberValue(const cJSON * const item);
CJSON_PUBLIC(cJSON_int) cJSON_GetIntValue(const cJSON * const item);
/* These functions check the type of an item */
CJSON_PUBLIC(cJSON_bool) cJSON_IsInvalid(const cJSON * const item);
@ -293,7 +301,7 @@ CJSON_PUBLIC(cJSON*) cJSON_AddObjectToObject(cJSON * const object, const char *
CJSON_PUBLIC(cJSON*) cJSON_AddArrayToObject(cJSON * const object, const char * const name);
/* When assigning an integer value, it needs to be propagated to valuedouble too. */
#define cJSON_SetIntValue(object, number) ((object) ? ((object)->valuedouble = (number), (object)->valueint = (number)) : (number))
#define cJSON_SetIntValue(object, number) ((object != NULL) ? ((object)->valuedouble = (number), (object)->type |= cJSON_PreferInt, (object)->valueint = (number)) : (number))
/* helper for the cJSON_SetNumberValue macro */
CJSON_PUBLIC(cJSON_float) cJSON_SetNumberHelper(cJSON *object, cJSON_float number);
#define cJSON_SetNumberValue(object, number) ((object != NULL) ? cJSON_SetNumberHelper(object, (cJSON_float)number) : (number))

View File

@ -73,8 +73,13 @@ static void parse_number_should_parse_zero(void)
static void parse_number_should_parse_negative_integers(void)
{
assert_parse_number("-1", -1, -1);
assert_parse_number("-32768", -32768, -32768.0);
assert_parse_number("-2147483648", -2147483648, -2147483648.0);
/* not -32768: C allows int as 15bit + signbit, or one's complement */
assert_parse_number("-32767", -32767, -32767.0);
if (sizeof(cJSON_int) >= 4)
assert_parse_number("-2147483648", -2147483648, -2147483648.0);
#ifdef CJSON_INT_USE_LONGLONG
assert_parse_number("-8765432101234567", -8765432101234567LL, -8765432101234567.0);
#else
@ -86,7 +91,10 @@ static void parse_number_should_parse_positive_integers(void)
{
assert_parse_number("1", 1, 1);
assert_parse_number("32767", 32767, 32767.0);
assert_parse_number("2147483647", 2147483647, 2147483647.0);
if (sizeof(cJSON_int) >= 4)
assert_parse_number("2147483647", 2147483647, 2147483647.0);
#ifdef CJSON_INT_USE_LONGLONG
assert_parse_number("8765432101234567", 8765432101234567LL, 8765432101234567.0);
#else

View File

@ -62,9 +62,30 @@ static void assert_print_number(const char *expected, cJSON_float input)
TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, buffer.buffer, "Printed number is not as expected.");
}
static void assert_print_integer(const char *expected, cJSON_int input)
{
unsigned char printed[1024];
unsigned char new_buffer[26];
cJSON item[1];
printbuffer buffer = { 0, 0, 0, 0, 0, 0, { 0, 0, 0 } };
buffer.buffer = printed;
buffer.length = sizeof(printed);
buffer.offset = 0;
buffer.noalloc = true;
buffer.hooks = global_hooks;
buffer.buffer = new_buffer;
memset(item, 0, sizeof(item));
memset(new_buffer, 0, sizeof(new_buffer));
cJSON_SetIntValue(item, input);
TEST_ASSERT_TRUE_MESSAGE(print_number(item, &buffer), "Failed to print integer.");
TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, buffer.buffer, "Printed integer is not as expected.");
}
static void print_number_should_print_zero(void)
{
assert_print_number("0", 0);
assert_print_integer("0", 0);
}
static void print_number_should_print_negative_integers(void)
@ -79,6 +100,18 @@ static void print_number_should_print_negative_integers(void)
/* Approx lowest integer exactly representable in double */
assert_print_number("-8765432101234567", -8765432101234567.0);
#endif
assert_print_integer("-1", -1);
/* not -32768: C allows int as 15bit + signbit, or one's complement */
assert_print_integer("-32767", -32767);
if (sizeof(cJSON_int) >= 4)
assert_print_integer("-2147483647", -2147483647);
#ifdef CJSON_INT_USE_LONGLONG
assert_print_integer("-9223372036854775807", -9223372036854775807LL);
#endif
}
static void print_number_should_print_positive_integers(void)
@ -93,6 +126,16 @@ static void print_number_should_print_positive_integers(void)
/* Approx highest integer exactly representable in double */
assert_print_number("8765432101234567", 8765432101234567.0);
#endif
assert_print_integer("1", 1);
assert_print_integer("32767", 32767);
if (sizeof(cJSON_int) >= 4)
assert_print_integer("2147483647", 2147483647);
#ifdef CJSON_INT_USE_LONGLONG
assert_print_integer("9223372036854775807", 9223372036854775807LL);
#endif
}
static void print_number_should_print_positive_reals(void)
@ -121,11 +164,10 @@ static void print_number_should_print_negative_reals(void)
static void print_number_should_print_non_number(void)
{
TEST_IGNORE();
/* FIXME: Cannot test this easily in C89! */
/* assert_print_number("null", NaN); */
/* assert_print_number("null", INFTY); */
/* assert_print_number("null", -INFTY); */
assert_print_number("null", 0.0/0.0);
assert_print_number("null", -(0.0/0.0));
assert_print_number("null", 1.0/0.0);
assert_print_number("null", (-1.0)/0.0);
}
int CJSON_CDECL main(void)