# HG changeset patch # User Mike Becker # Date 1736458630 -3600 # Node ID 54df904472b0d5ac1e3afa0228be0d00d987d5e2 # Parent b381da3a9b19d86d25d394c8bd6ef6a187de1688 add fractional number formatting - relates to #526 diff -r b381da3a9b19 -r 54df904472b0 src/cx/json.h --- a/src/cx/json.h Thu Jan 09 21:19:52 2025 +0100 +++ b/src/cx/json.h Thu Jan 09 22:37:10 2025 +0100 @@ -449,6 +449,8 @@ bool sort_members; /** * The maximum number of fractional digits in a number value. + * The default value is 6 and values larger than 15 are reduced to 15. + * Note, that the actual number of digits may be lower, depending on the concrete number. */ uint8_t frac_max_digits; /** diff -r b381da3a9b19 -r 54df904472b0 src/json.c --- a/src/json.c Thu Jan 09 21:19:52 2025 +0100 +++ b/src/json.c Thu Jan 09 22:37:10 2025 +0100 @@ -988,7 +988,7 @@ static const CxJsonWriter cx_json_writer_default = { false, true, - 255, + 6, false, 4 }; @@ -1001,7 +1001,7 @@ return (CxJsonWriter) { true, true, - 255, + 6, use_spaces, 4 }; @@ -1050,7 +1050,7 @@ size_t actual = 0, expected = 0; // small buffer for number to string conversions - char numbuf[32]; + char numbuf[40]; // recursively write the values switch (value->type) { @@ -1160,12 +1160,71 @@ break; } case CX_JSON_NUMBER: { - // TODO: locale bullshit - // TODO: formatting settings - snprintf(numbuf, 32, "%g", value->value.number); - size_t len = strlen(numbuf); - actual += wfunc(numbuf, 1, len, target); - expected += len; + int precision = settings->frac_max_digits; + // because of the way how %g is defined, we need to + // double the precision and truncate ourselves + precision = 1 + (precision > 15 ? 30 : 2 * precision); + snprintf(numbuf, 40, "%.*g", precision, value->value.number); + char *dot, *exp; + unsigned char max_digits; + // find the decimal separator and hope that it's one of . or , + dot = strchr(numbuf, '.'); + if (dot == NULL) { + dot = strchr(numbuf, ','); + } + if (dot == NULL) { + // no decimal separator found + // output everything until a possible exponent + max_digits = 30; + dot = numbuf; + } else { + // found a decimal separator + // output everything until the separator + // and set max digits to what the settings say + size_t len = dot - numbuf; + actual += wfunc(numbuf, 1, len, target); + expected += len; + max_digits = settings->frac_max_digits; + if (max_digits > 15) { + max_digits = 15; + } + // locale independent separator + if (max_digits > 0) { + actual += wfunc(".", 1, 1, target); + expected++; + } + dot++; + } + // find the exponent + exp = strchr(dot, 'e'); + if (exp == NULL) { + // no exponent - output the rest + if (max_digits > 0) { + size_t len = strlen(dot); + if (len > max_digits) { + len = max_digits; + } + actual += wfunc(dot, 1, len, target); + expected += len; + } + } else { + // exponent found - truncate the frac digits + // and then output the rest + if (max_digits > 0) { + size_t len = exp - dot - 1; + if (len > max_digits) { + len = max_digits; + } + actual += wfunc(dot, 1, len, target); + expected += len; + } + actual += wfunc("e", 1, 1, target); + expected++; + exp++; + size_t len = strlen(exp); + actual += wfunc(exp, 1, len, target); + expected += len; + } break; } case CX_JSON_INTEGER: { diff -r b381da3a9b19 -r 54df904472b0 tests/test_json.c --- a/tests/test_json.c Thu Jan 09 21:19:52 2025 +0100 +++ b/tests/test_json.c Thu Jan 09 22:37:10 2025 +0100 @@ -887,6 +887,58 @@ cx_testing_allocator_destroy(&talloc); } +CX_TEST(test_json_write_frac_max_digits) { + CxJsonValue* num = cxJsonCreateNumber(NULL, 3.141592653589793); + CxJsonWriter writer = cxJsonWriterCompact(); + CxBuffer buf; + cxBufferInit(&buf, NULL, 32, NULL, 0); + CX_TEST_DO { + // test default settings (6 digits) + cxJsonWrite(&buf,num, cxBufferWriteFunc, &writer); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3.141592"))); + + // test too many digits + cxBufferReset(&buf); + writer.frac_max_digits = 200; + cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3.141592653589793"))); + + // test 0 digits + cxBufferReset(&buf); + writer.frac_max_digits = 0; + cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3"))); + + // test 2 digits + cxBufferReset(&buf); + writer.frac_max_digits = 2; + cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3.14"))); + + // test 3 digits + cxBufferReset(&buf); + writer.frac_max_digits = 3; + cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("3.141"))); + + // test 6 digits, but two are left of the decimal point + num->value.number = 47.110815; + cxBufferReset(&buf); + writer.frac_max_digits = 6; + cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("47.110815"))); + + // test 4 digits with exponent + num->value.number = 5.11223344e23; + cxBufferReset(&buf); + writer.frac_max_digits = 4; + cxJsonWrite(&buf, num, cxBufferWriteFunc, &writer); + CX_TEST_ASSERT(0 == cx_strcmp(cx_strn(buf.space, buf.size), CX_STR("5.1122e+23"))); + } + cxBufferDestroy(&buf); + cxJsonValueFree(num); +} + CxTestSuite *cx_test_suite_json(void) { CxTestSuite *suite = cx_test_suite_new("json"); @@ -909,6 +961,7 @@ cx_test_register(suite, test_json_write_pretty_default_spaces); cx_test_register(suite, test_json_write_pretty_default_tabs); cx_test_register(suite, test_json_write_pretty_preserve_order); + cx_test_register(suite, test_json_write_frac_max_digits); return suite; }