From 5b7756726414f9992739c4983f5ee3587ce33ac9 Mon Sep 17 00:00:00 2001 From: Aaron Vanmaele Date: Tue, 1 Oct 2024 20:46:48 +0200 Subject: [PATCH 1/8] Feature to format floating point numbers --- cJSON.c | 74 ++++++++++++++++++++++++++++++++++++++++- cJSON.h | 40 ++++++++++++++-------- tests/print_number.c | 79 +++++++++++++++++++++++++++++++++++++++++--- 3 files changed, 173 insertions(+), 20 deletions(-) diff --git a/cJSON.c b/cJSON.c index d7c72363..4d76825e 100644 --- a/cJSON.c +++ b/cJSON.c @@ -19,7 +19,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +/* clang-format off */ /* cJSON */ /* JSON parser in C. */ @@ -443,6 +443,23 @@ CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) return copy; } +CJSON_PUBLIC(void) cJSON_SetNumberFormat(cJSON *object, cJSON_bool g_format, int precision) +{ + if ((object->type != cJSON_Number) && precision < 0) + { + return; + } + /* Signal that the number is formatted */ + object->type &= ~(cJSON_NumberIsFormatted); + object->type |= cJSON_NumberIsFormatted; + /* Set format style */ + object->type &= ~(cJSON_NumberFormatStyleFixedPoint); + object->type |= cJSON_NumberFormatStyleSet(g_format); + /* Set precision bits */ + object->type &= ~(cJSON_NumberFormatPrecision); + object->type |= cJSON_NumberFormatPrecisionSet(precision); +} + typedef struct { unsigned char *buffer; @@ -560,16 +577,60 @@ static cJSON_bool compare_double(double a, double b) return (fabs(a - b) <= maxVal * DBL_EPSILON); } +static void remove_trailing_zeros(char *str) { + cJSON_bool is_neg = (*str == '-'); + char *dot = strchr(str, '.'); /* Find the decimal point */ + char *start; + char *end = str + strlen(str) - 1; /* Start at the end of the string */ + /* Check if floating point, if not return */ + if (!dot) + { + return; + } + /* Remove trailing zeros */ + while (end > dot && *end == '0') { + *end = '\0'; /* Replace zero with null terminator */ + end--; + } + /* If the last character is now the decimal point, remove it */ + if (*end == '.') { + *end = '\0'; + end--; + } + /* Check if zero */ + start = str + is_neg; + printf("start: %s\n", start); + while (start != end && *str == '0') { + start++; + } + /* Discard negative sign if zero */ + if (start == end) { + *str = '0'; + *(str + 1) = '\0'; + } +} + +static int _get_precision_from_item(const cJSON * const item, int max_len) { + int precision = (cJSON_NumberFormatPrecisionGet(item->type) & 0xFF); + if (max_len <= precision) + { + precision = max_len - 1; + } + return precision; +} + /* Render the number nicely from the given item into a string. */ static cJSON_bool print_number(const cJSON * const item, printbuffer * const output_buffer) { unsigned char *output_pointer = NULL; double d = item->valuedouble; + int length = 0; size_t i = 0; unsigned char number_buffer[26] = {0}; /* temporary buffer to print the number into */ unsigned char decimal_point = get_decimal_point(); double test = 0.0; + int precision; if (output_buffer == NULL) { @@ -585,6 +646,17 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out { length = sprintf((char*)number_buffer, "%d", item->valueint); } + else if (item->type & cJSON_NumberIsFormatted) { + precision = _get_precision_from_item(item, sizeof(number_buffer)); + printf("precision: %d\n", precision); + printf("Style: %d\n", (item->type & cJSON_NumberFormatStyleFixedPoint)); + if (item->type & cJSON_NumberFormatStyleFixedPoint) { + length = sprintf((char*)number_buffer, "%.*f", (int)precision, d); + remove_trailing_zeros((char*)number_buffer); + } else { + length = sprintf((char*)number_buffer, "%.*g", (int)precision, d); + } + } else { /* Try 15 decimal places of precision to avoid nonsignificant nonzero digits */ diff --git a/cJSON.h b/cJSON.h index 37520bbc..e97f764e 100644 --- a/cJSON.h +++ b/cJSON.h @@ -19,7 +19,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +/* clang-format off */ #ifndef cJSON__h #define cJSON__h @@ -87,17 +87,28 @@ then using the CJSON_API_VISIBILITY flag to "export" the same symbols the way CJ /* cJSON Types: */ #define cJSON_Invalid (0) -#define cJSON_False (1 << 0) -#define cJSON_True (1 << 1) -#define cJSON_NULL (1 << 2) -#define cJSON_Number (1 << 3) -#define cJSON_String (1 << 4) -#define cJSON_Array (1 << 5) -#define cJSON_Object (1 << 6) -#define cJSON_Raw (1 << 7) /* raw json */ - -#define cJSON_IsReference 256 -#define cJSON_StringIsConst 512 +#define cJSON_False (1 << 0) +#define cJSON_True (1 << 1) +#define cJSON_NULL (1 << 2) +#define cJSON_Number (1 << 3) +#define cJSON_String (1 << 4) +#define cJSON_Array (1 << 5) +#define cJSON_Object (1 << 6) +#define cJSON_Raw (1 << 7) /* raw json */ + +#define cJSON_IsReference (1 << 8) +#define cJSON_StringIsConst (1 << 9) + +#define cJSON_NumberIsFormatted (1 << 10) +#define cJSON_NumberFormatStyleFixedPoint (1 << 11) +#define cJSON_NumberFormatStyleSet(g_format) ((g_format == true) ? 0 : cJSON_NumberFormatStyleFixedPoint) /* 0 for general, 1 for fixed point */ +#define cJSON_NumberFormatPrecisionShift (12) /* Position of the mask */ +#define cJSON_NumberFormatPrecisionMask (0x3F) /* 0x3F is the maximum precision */ +#define cJSON_NumberFormatPrecision (cJSON_NumberFormatPrecisionMask << cJSON_NumberFormatPrecisionShift) +#define cJSON_NumberFormatPrecisionSet(x) (((x) << cJSON_NumberFormatPrecisionShift) & cJSON_NumberFormatPrecision) +#define cJSON_NumberFormatPrecisionGet(x) (((x) >> cJSON_NumberFormatPrecisionShift) & cJSON_NumberFormatPrecisionMask) + +typedef int cJSON_bool; /* The cJSON structure: */ typedef struct cJSON @@ -129,8 +140,6 @@ typedef struct cJSON_Hooks void (CJSON_CDECL *free_fn)(void *ptr); } cJSON_Hooks; -typedef int cJSON_bool; - /* Limits how deeply nested arrays/objects can be before cJSON rejects to parse them. * This is to prevent stack overflows. */ #ifndef CJSON_NESTING_LIMIT @@ -285,6 +294,9 @@ CJSON_PUBLIC(double) cJSON_SetNumberHelper(cJSON *object, double number); /* Change the valuestring of a cJSON_String object, only takes effect when type of object is cJSON_String */ CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring); +/* Interface to set formatting options for numbers */ +CJSON_PUBLIC(void) cJSON_SetNumberFormat(cJSON *object, cJSON_bool general_format, int precision); + /* If the object is not a boolean type this does nothing and returns cJSON_Invalid else it returns the new type*/ #define cJSON_SetBoolValue(object, boolValue) ( \ (object != NULL && ((object)->type & (cJSON_False|cJSON_True))) ? \ diff --git a/tests/print_number.c b/tests/print_number.c index 3fbf9cb6..e6ba1a47 100644 --- a/tests/print_number.c +++ b/tests/print_number.c @@ -19,11 +19,50 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ - +/* clang-format off */ #include "unity/examples/unity_config.h" #include "unity/src/unity.h" #include "common.h" +static void assert_print_number_with_precision(const char *expected, double input, cJSON_bool g_format, int precision) +{ + unsigned char printed[1024]; + unsigned char new_buffer[26]; + unsigned int i = 0; + 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_SetNumberValue(item, input); + cJSON_SetNumberFormat(item, g_format, precision); + TEST_ASSERT_TRUE_MESSAGE(print_number(item, &buffer), "Failed to print number."); + + /* In MinGW or visual studio(before 2015),the exponten is represented using three digits,like:"1e-009","1e+017" + * remove extra "0" to output "1e-09" or "1e+17",which makes testcase PASS */ + for(i = 0;i 3 && new_buffer[i] =='0') + { + if((new_buffer[i-3] =='e' && new_buffer[i-2] == '-' && new_buffer[i] =='0') ||(new_buffer[i-2] =='e' && new_buffer[i-1] =='+')) + { + while(new_buffer[i] !='\0') + { + new_buffer[i] = new_buffer[i+1]; + i++; + } + } + } + } + TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, buffer.buffer, "Printed number is not as expected."); +} + static void assert_print_number(const char *expected, double input) { unsigned char printed[1024]; @@ -42,7 +81,7 @@ static void assert_print_number(const char *expected, double input) memset(new_buffer, 0, sizeof(new_buffer)); cJSON_SetNumberValue(item, input); TEST_ASSERT_TRUE_MESSAGE(print_number(item, &buffer), "Failed to print number."); - + /* In MinGW or visual studio(before 2015),the exponten is represented using three digits,like:"1e-009","1e+017" * remove extra "0" to output "1e-09" or "1e+17",which makes testcase PASS */ for(i = 0;i Date: Fri, 18 Oct 2024 02:53:57 +0200 Subject: [PATCH 2/8] Removed debug log --- cJSON.c | 4 ---- 1 file changed, 4 deletions(-) diff --git a/cJSON.c b/cJSON.c index 4d76825e..1b11dce0 100644 --- a/cJSON.c +++ b/cJSON.c @@ -19,7 +19,6 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ -/* clang-format off */ /* cJSON */ /* JSON parser in C. */ @@ -599,7 +598,6 @@ static void remove_trailing_zeros(char *str) { } /* Check if zero */ start = str + is_neg; - printf("start: %s\n", start); while (start != end && *str == '0') { start++; } @@ -648,8 +646,6 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out } else if (item->type & cJSON_NumberIsFormatted) { precision = _get_precision_from_item(item, sizeof(number_buffer)); - printf("precision: %d\n", precision); - printf("Style: %d\n", (item->type & cJSON_NumberFormatStyleFixedPoint)); if (item->type & cJSON_NumberFormatStyleFixedPoint) { length = sprintf((char*)number_buffer, "%.*f", (int)precision, d); remove_trailing_zeros((char*)number_buffer); From 699f2f4f8356d2700a94b75b6c748a8bbc689a23 Mon Sep 17 00:00:00 2001 From: Aaron Vanmaele Date: Wed, 12 Mar 2025 21:21:46 +0100 Subject: [PATCH 3/8] Number formatting: trailing zeros strlen The length needs to be adjusted if trailing zeros are removed or else a 0-termination is copied into the output buffer. --- cJSON.c | 1 + 1 file changed, 1 insertion(+) diff --git a/cJSON.c b/cJSON.c index 1b11dce0..97392753 100644 --- a/cJSON.c +++ b/cJSON.c @@ -649,6 +649,7 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out if (item->type & cJSON_NumberFormatStyleFixedPoint) { length = sprintf((char*)number_buffer, "%.*f", (int)precision, d); remove_trailing_zeros((char*)number_buffer); + length = strlen((char*)number_buffer); } else { length = sprintf((char*)number_buffer, "%.*g", (int)precision, d); } From 695f811caac3b1b4d67cfe621e7818077853b6db Mon Sep 17 00:00:00 2001 From: Aaron Vanmaele Date: Wed, 23 Apr 2025 01:35:39 +0200 Subject: [PATCH 4/8] Fix fixed-point rounding when only last digit is significant --- cJSON.c | 4 ++-- tests/print_number.c | 18 ++++++++++-------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/cJSON.c b/cJSON.c index 97392753..5fda41a2 100644 --- a/cJSON.c +++ b/cJSON.c @@ -598,7 +598,7 @@ static void remove_trailing_zeros(char *str) { } /* Check if zero */ start = str + is_neg; - while (start != end && *str == '0') { + while (start != end && *start == '0') { start++; } /* Discard negative sign if zero */ @@ -649,7 +649,7 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out if (item->type & cJSON_NumberFormatStyleFixedPoint) { length = sprintf((char*)number_buffer, "%.*f", (int)precision, d); remove_trailing_zeros((char*)number_buffer); - length = strlen((char*)number_buffer); + length = (int)strlen((char*)number_buffer); } else { length = sprintf((char*)number_buffer, "%.*g", (int)precision, d); } diff --git a/tests/print_number.c b/tests/print_number.c index e6ba1a47..65d570e6 100644 --- a/tests/print_number.c +++ b/tests/print_number.c @@ -144,14 +144,16 @@ static void print_number_fixed_point(void) assert_print_number_with_precision("100", 100, false, 1); assert_print_number_with_precision("100.5", 100.5, false, 3); assert_print_number_with_precision("100", 100.5, false, 0); /* Bankers rounding, to the nearest even */ - /* assert_print_number_with_precision("102", 101.5, false, 0); /1* Bankers rounding, to the nearest even *1/ */ - /* assert_print_number_with_precision("100.2", 100.15, false, 1); /1* Bankers rounding, to the nearest even *1/ */ - /* assert_print_number_with_precision("100.2", 100.25, false, 1); /1* Bankers rounding, to the nearest even *1/ */ - /* assert_print_number_with_precision("0", -0.0123, false, 1); */ - /* assert_print_number_with_precision("0", 0.0249, false, 1); */ - /* assert_print_number_with_precision("-0.02", -0.0153454, false, 2); */ - /* assert_print_number_with_precision("-0.05", -0.0453454, false, 2); */ - /* assert_print_number_with_precision("0", -10e-10, false, 5); */ + assert_print_number_with_precision("102", 101.5, false, 0); /* Bankers rounding, to the nearest even */ + assert_print_number_with_precision("100.2", 100.15, false, 1); /* Bankers rounding, to the nearest even */ + assert_print_number_with_precision("100.2", 100.25, false, 1); /* Bankers rounding, to the nearest even */ + assert_print_number_with_precision("0", -0.0123, false, 1); + assert_print_number_with_precision("0", 0.0249, false, 1); + assert_print_number_with_precision("-0.02", -0.0153454, false, 2); + assert_print_number_with_precision("-0.05", -0.0453454, false, 2); + assert_print_number_with_precision("0", -10e-10, false, 5); + assert_print_number_with_precision("-0.03", -0.029999999999999999, false, 2); + assert_print_number_with_precision("0.03", 0.029999999999999999, false, 2); } static void print_number_general_format(void) From ba9dee03407651bebdbec60635b2be23ef4173ab Mon Sep 17 00:00:00 2001 From: Aaron Vanmaele Date: Wed, 21 May 2025 20:58:36 +0200 Subject: [PATCH 5/8] Use snprintf when formatting number --- cJSON.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cJSON.c b/cJSON.c index 5fda41a2..91a2d666 100644 --- a/cJSON.c +++ b/cJSON.c @@ -638,20 +638,20 @@ static cJSON_bool print_number(const cJSON * const item, printbuffer * const out /* This checks for NaN and Infinity */ if (isnan(d) || isinf(d)) { - length = sprintf((char*)number_buffer, "null"); + length = snprintf((char*)number_buffer, sizeof(number_buffer), "null"); } else if(d == (double)item->valueint) { - length = sprintf((char*)number_buffer, "%d", item->valueint); + length = snprintf((char*)number_buffer, sizeof(number_buffer), "%d", item->valueint); } else if (item->type & cJSON_NumberIsFormatted) { precision = _get_precision_from_item(item, sizeof(number_buffer)); if (item->type & cJSON_NumberFormatStyleFixedPoint) { - length = sprintf((char*)number_buffer, "%.*f", (int)precision, d); + length = snprintf((char*)number_buffer, sizeof(number_buffer), "%.*f", (int)precision, d); remove_trailing_zeros((char*)number_buffer); length = (int)strlen((char*)number_buffer); } else { - length = sprintf((char*)number_buffer, "%.*g", (int)precision, d); + length = snprintf((char*)number_buffer, sizeof(number_buffer), "%.*g", (int)precision, d); } } else From 8abf8be9657713c95036256a30ec9306f675eb63 Mon Sep 17 00:00:00 2001 From: Aaron Vanmaele Date: Tue, 6 Jan 2026 20:17:39 +0100 Subject: [PATCH 6/8] Guarding cJSON_SetNumberFormat with NULL ptr check --- cJSON.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/cJSON.c b/cJSON.c index 91a2d666..51eba4b7 100644 --- a/cJSON.c +++ b/cJSON.c @@ -444,6 +444,10 @@ CJSON_PUBLIC(char*) cJSON_SetValuestring(cJSON *object, const char *valuestring) CJSON_PUBLIC(void) cJSON_SetNumberFormat(cJSON *object, cJSON_bool g_format, int precision) { + if (object == NULL) + { + return; + } if ((object->type != cJSON_Number) && precision < 0) { return; From 986dc0c2a5c5f1661114befd59dae8f3bc95b1b5 Mon Sep 17 00:00:00 2001 From: Aaron Vanmaele Date: Tue, 15 Sep 2026 21:55:53 +0200 Subject: [PATCH 7/8] Fix rounding to 1 figure 0.99 would become 0 instead of 1 --- cJSON.c | 4 ++-- tests/print_number.c | 13 +++++++++++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/cJSON.c b/cJSON.c index 51eba4b7..28e5daa5 100644 --- a/cJSON.c +++ b/cJSON.c @@ -605,8 +605,8 @@ static void remove_trailing_zeros(char *str) { while (start != end && *start == '0') { start++; } - /* Discard negative sign if zero */ - if (start == end) { + /* Discard negative sign if zero: everything from the sign to the end was a zero */ + if (start == end && *end == '0') { *str = '0'; *(str + 1) = '\0'; } diff --git a/tests/print_number.c b/tests/print_number.c index 65d570e6..0e00fa30 100644 --- a/tests/print_number.c +++ b/tests/print_number.c @@ -156,6 +156,18 @@ static void print_number_fixed_point(void) assert_print_number_with_precision("0.03", 0.029999999999999999, false, 2); } +static void print_number_fixed_point_single_digit_result(void) +{ + /* Rounding up to one digit: the trailing-zero strip left "1", and the zero check took a lone + * digit for a zero, printing "0". A single digit is only zero when it is '0'. */ + assert_print_number_with_precision("1", 0.999, false, 2); + assert_print_number_with_precision("-1", -0.999, false, 2); + assert_print_number_with_precision("1", 0.96, false, 1); + assert_print_number_with_precision("10", 9.999, false, 2); + assert_print_number_with_precision("0", 0.0049, false, 2); + assert_print_number_with_precision("0", -0.0049, false, 2); +} + static void print_number_general_format(void) { /* In general format, the precision specifies the number of significant digits. */ @@ -190,6 +202,7 @@ int CJSON_CDECL main(void) RUN_TEST(print_number_should_print_negative_reals); RUN_TEST(print_number_should_print_non_number); RUN_TEST(print_number_fixed_point); + RUN_TEST(print_number_fixed_point_single_digit_result); RUN_TEST(print_number_general_format); return UNITY_END(); From 376f93c211c8701e4678ff54126102d0e2858b45 Mon Sep 17 00:00:00 2001 From: Aaron Vanmaele Date: Tue, 15 Sep 2026 23:34:30 +0200 Subject: [PATCH 8/8] Fix rounded with precision 0 could result in -0 --- cJSON.c | 26 +++++++++++++------------- tests/print_number.c | 7 +++++++ 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/cJSON.c b/cJSON.c index 28e5daa5..7ddbf0d1 100644 --- a/cJSON.c +++ b/cJSON.c @@ -585,20 +585,20 @@ static void remove_trailing_zeros(char *str) { char *dot = strchr(str, '.'); /* Find the decimal point */ char *start; char *end = str + strlen(str) - 1; /* Start at the end of the string */ - /* Check if floating point, if not return */ - if (!dot) + /* Only a fractional part can carry trailing zeros; an integral one still needs the + * signed-zero check below, so this skips the stripping rather than returning */ + if (dot) { - return; - } - /* Remove trailing zeros */ - while (end > dot && *end == '0') { - *end = '\0'; /* Replace zero with null terminator */ - end--; - } - /* If the last character is now the decimal point, remove it */ - if (*end == '.') { - *end = '\0'; - end--; + /* Remove trailing zeros */ + while (end > dot && *end == '0') { + *end = '\0'; /* Replace zero with null terminator */ + end--; + } + /* If the last character is now the decimal point, remove it */ + if (*end == '.') { + *end = '\0'; + end--; + } } /* Check if zero */ start = str + is_neg; diff --git a/tests/print_number.c b/tests/print_number.c index 0e00fa30..5e2bd685 100644 --- a/tests/print_number.c +++ b/tests/print_number.c @@ -154,6 +154,13 @@ static void print_number_fixed_point(void) assert_print_number_with_precision("0", -10e-10, false, 5); assert_print_number_with_precision("-0.03", -0.029999999999999999, false, 2); assert_print_number_with_precision("0.03", 0.029999999999999999, false, 2); + /* A negative that rounds away to zero drops its sign at every precision, including 0, + * where "%.0f" yields "-0" and there is no decimal point to strip */ + assert_print_number_with_precision("0", -0.4, false, 0); + assert_print_number_with_precision("0", -0.0001, false, 0); + assert_print_number_with_precision("0", -0.5, false, 0); /* Bankers rounding, to the nearest even */ + assert_print_number_with_precision("-1", -0.6, false, 0); + assert_print_number_with_precision("-2", -1.5, false, 0); /* Bankers rounding, to the nearest even */ } static void print_number_fixed_point_single_digit_result(void)