Skip to content

Commit

Permalink
Feature to format floating point numbers
Browse files Browse the repository at this point in the history
  • Loading branch information
Vanmaele committed Oct 1, 2024
1 parent 12c4bf1 commit 5b77567
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 20 deletions.
74 changes: 73 additions & 1 deletion cJSON.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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)
{
Expand All @@ -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 */
Expand Down
40 changes: 26 additions & 14 deletions cJSON.h
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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))) ? \
Expand Down
79 changes: 74 additions & 5 deletions tests/print_number.c
Original file line number Diff line number Diff line change
Expand Up @@ -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 <sizeof(new_buffer);i++)
{
if(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];
Expand All @@ -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 <sizeof(new_buffer);i++)
Expand All @@ -56,9 +95,9 @@ static void assert_print_number(const char *expected, double input)
new_buffer[i] = new_buffer[i+1];
i++;
}
}
}
}
}
}
}
TEST_ASSERT_EQUAL_STRING_MESSAGE(expected, buffer.buffer, "Printed number is not as expected.");
}

Expand Down Expand Up @@ -100,6 +139,34 @@ static void print_number_should_print_negative_reals(void)
assert_print_number("-1.23e-126", -123e-128);
}

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); */
}

static void print_number_general_format(void)
{
/* In general format, the precision specifies the number of significant digits. */
assert_print_number_with_precision("1.23e+129", 123e+127, true, 3);
assert_print_number_with_precision("1.23e-126", 123e-128, true, 3);
assert_print_number_with_precision("1e+02", 100.5, true, 1);
assert_print_number_with_precision("100", 100.5, true, 3);
assert_print_number_with_precision("100.5", 100.5, true, 4);
assert_print_number_with_precision("100.5", 100.5, true, 5); /* JSON cuts off trailing zeros */
assert_print_number_with_precision("-1e-09", -10e-10, true, 5);

}

static void print_number_should_print_non_number(void)
{
TEST_IGNORE();
Expand All @@ -120,6 +187,8 @@ int CJSON_CDECL main(void)
RUN_TEST(print_number_should_print_positive_reals);
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_general_format);

return UNITY_END();
}

0 comments on commit 5b77567

Please sign in to comment.