From 5b7756726414f9992739c4983f5ee3587ce33ac9 Mon Sep 17 00:00:00 2001 From: Aaron Vanmaele Date: Tue, 1 Oct 2024 20:46:48 +0200 Subject: [PATCH] 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