From e79376f2d9c08a0d15268f480c9ae9fe42366b97 Mon Sep 17 00:00:00 2001 From: No Default Name Date: Fri, 29 Apr 2022 23:11:06 +0200 Subject: [PATCH] Make integers be first-class citizens, including full precision in longlong+float case --- cJSON.c | 33 ++++++++++++++++++---------- cJSON.h | 28 +++++++++++++++--------- tests/parse_number.c | 14 +++++++++--- tests/print_number.c | 52 +++++++++++++++++++++++++++++++++++++++----- 4 files changed, 97 insertions(+), 30 deletions(-) diff --git a/cJSON.c b/cJSON.c index 6f5fa73..57b3166 100644 --- a/cJSON.c +++ b/cJSON.c @@ -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)) { diff --git a/cJSON.h b/cJSON.h index 6279654..7cdcf4f 100644 --- a/cJSON.h +++ b/cJSON.h @@ -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 + */ +#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 - */ +#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)) diff --git a/tests/parse_number.c b/tests/parse_number.c index d0d04f5..14bc431 100644 --- a/tests/parse_number.c +++ b/tests/parse_number.c @@ -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 diff --git a/tests/print_number.c b/tests/print_number.c index 617bed1..ae76745 100644 --- a/tests/print_number.c +++ b/tests/print_number.c @@ -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)