diff --git a/CHANGELOG.md b/CHANGELOG.md index 944666a..8098d87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,15 @@ All notable changes to this project will be documented in this file. ## Unreleased +#### Added +- Add `YYJSON_WRITE_FP_TO_FLOAT` flag to write real numbers using single-precison. +- Add `YYJSON_WRITE_FP_TO_FIXED(prec)` flag to write real numbers using fix-point notation. +- Add `set_fp_to_fixed(val)` and `set_fp_to_prec(val, prec)` functions to control the output format of a specific number. +- Add `set_str_noesc(val)` function to skip escaping for a specific string during writing. + +#### Changed +- Rewrite the floating-point number to string functions using faster algorithm. + #### Fixed - Fix some warnings when directly including yyjson.c: #177 - Fix missing indent for `YYJSON_TYPE_RAW` in prettify function: #178 diff --git a/CMakeLists.txt b/CMakeLists.txt index fd3fa68..afd7114 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -276,7 +276,7 @@ if(YYJSON_BUILD_TESTS) add_test(${SRC_NAME} ${SRC_NAME}) endif() - set_tests_properties(${SRC_NAME} PROPERTIES TIMEOUT 300) + set_tests_properties(${SRC_NAME} PROPERTIES TIMEOUT 600) message(STATUS "Add test: ${SRC_NAME}") endforeach() @@ -475,7 +475,7 @@ if (YYJSON_BUILD_FUZZER) add_executable(fuzzer "fuzz/fuzzer.c") target_link_libraries(fuzzer yyjson) - add_test(test_fuzzer ${CMAKE_CURRENT_BINARY_DIR}/fuzzer -dict=fuzzer.dict -max_total_time=300 ${CMAKE_CURRENT_BINARY_DIR}/corpus) + add_test(test_fuzzer ${CMAKE_CURRENT_BINARY_DIR}/fuzzer -dict=fuzzer.dict -max_total_time=600 ${CMAKE_CURRENT_BINARY_DIR}/corpus) set_tests_properties(test_fuzzer PROPERTIES TIMEOUT 600) else() message(WARNING "LibFuzzer requires LLVM Clang as compiler") diff --git a/doc/API.md b/doc/API.md index 3f5348e..4316198 100644 --- a/doc/API.md +++ b/doc/API.md @@ -555,6 +555,19 @@ This flag does not affect the performance of correctly encoded string. Adds a newline character `\n` at the end of the JSON. This can be helpful for text editors or NDJSON. +● **YYJSON_WRITE_FP_TO_FLOAT**
+Write floating-point numbers using single-precision (float). +This casts `double` to `float` before serialization. +This will produce shorter output, but may lose some precision. +This flag is ignored if `YYJSON_WRITE_FP_TO_FIXED(prec)` is also used. + +● **YYJSON_WRITE_FP_TO_FIXED(prec)**
+Write floating-point number using fixed-point notation. +This is similar to ECMAScript `Number.prototype.toFixed(prec)`, +but with trailing zeros removed. The `prec` ranges from 1 to 15. +This will produce shorter output but may lose some precision. + + --------------- # Accessing JSON Document @@ -689,6 +702,8 @@ bool yyjson_set_bool(yyjson_val *val, bool num); bool yyjson_set_uint(yyjson_val *val, uint64_t num); bool yyjson_set_sint(yyjson_val *val, int64_t num); bool yyjson_set_int(yyjson_val *val, int num); +bool yyjson_set_float(yyjson_val *val, float num); +bool yyjson_set_double(yyjson_val *val, double num); bool yyjson_set_real(yyjson_val *val, double num); // The string is not copied, should be held by caller. @@ -961,6 +976,8 @@ yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, bool val); yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, uint64_t num); yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, int64_t num); yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, int64_t num); +yyjson_mut_val *yyjson_mut_float(yyjson_mut_doc *doc, float num); +yyjson_mut_val *yyjson_mut_double(yyjson_mut_doc *doc, double num); yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, double num); // Creates a string value, the input string is NOT copied. @@ -1057,6 +1074,8 @@ bool yyjson_mut_arr_add_bool(yyjson_mut_doc *doc, yyjson_mut_val *arr, bool val) bool yyjson_mut_arr_add_uint(yyjson_mut_doc *doc, yyjson_mut_val *arr, uint64_t num); bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, yyjson_mut_val *arr, int64_t num); bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, yyjson_mut_val *arr, int64_t num); +bool yyjson_mut_arr_add_float(yyjson_mut_doc *doc, yyjson_mut_val *arr, float num); +bool yyjson_mut_arr_add_double(yyjson_mut_doc *doc, yyjson_mut_val *arr, double num); bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, yyjson_mut_val *arr, double num); bool yyjson_mut_arr_add_str(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str); bool yyjson_mut_arr_add_strn(yyjson_mut_doc *doc, yyjson_mut_val *arr, const char *str, size_t len); @@ -1136,6 +1155,8 @@ bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, yyjson_mut_val *obj, const cha bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, uint64_t val); bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, int64_t val); bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, int64_t val); +bool yyjson_mut_obj_add_float(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, float val); +bool yyjson_mut_obj_add_double(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, double val); bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, double val); bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, const char *val); bool yyjson_mut_obj_add_strn(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, const char *val, size_t len); @@ -1358,13 +1379,18 @@ it will write numbers according to these rules by default:
* The positive sign in the exponent part is removed. * The floating-point number writer will generate the shortest correctly rounded decimal representation. -There are 2 flags that can be used to adjust the number writing strategy: +There are several flags that can be used to adjust the number writing strategy: -- `YYJSON_WRITE_ALLOW_INF_AND_NAN` write inf/nan numbers as `Infinity` and `NaN` literals without error (non-standard). -- `YYJSON_WRITE_INF_AND_NAN_AS_NULL` write inf/nan numbers as `null` literal. +- `YYJSON_WRITE_ALLOW_INF_AND_NAN` writes inf/nan numbers as `Infinity` and `NaN` literals without error (non-standard). +- `YYJSON_WRITE_INF_AND_NAN_AS_NULL` writes inf/nan numbers as `null` literal. +- `YYJSON_WRITE_FP_TO_FLOAT` writes real numbers as `float` instead of `double`. +- `YYJSON_WRITE_FP_TO_FIXED(prec)` writes real numbers using fixed-point notation. -See the "Writer flag" section for more details. +See the `Writer flag` section for more details. +There are also some helper functions to control the output format of individual values: +- `yyjson_set_fp_to_float(yyjson_val *val, bool fpt)` and `yyjson_mut_set_fp_to_float(yyjson_mut_val *val, bool flt)` write this real number with `float` or `double` precision. +- `yyjson_set_fp_to_fixed(yyjson_val *val, int prec)` and `yyjson_mut_set_fp_to_fixed(yyjson_mut_val *val, int prec)` write this real number using fixed-point notation, the prec should be in the range of 1 to 15. # Text Processing @@ -1377,7 +1403,9 @@ This library only supports UTF-8 encoding without BOM, as specified in [RFC 8259 By default, yyjson performs strict UTF-8 encoding validation on input strings. If an invalid character is encountered, an error will be reported. -You can use `YYJSON_READ_ALLOW_INVALID_UNICODE` and `YYJSON_WRITE_ALLOW_INVALID_UNICODE` flags to allow invalid Unicode encoding. However, please note that if you enable these flags, the resulting value from yyjson may contain invalid characters, which can be used by other code and may introduce security risks. +You can use `YYJSON_READ_ALLOW_INVALID_UNICODE` and `YYJSON_WRITE_ALLOW_INVALID_UNICODE` flags to allow invalid Unicode encoding. However, please note that if you enable these flags, the resulting values from yyjson may contain invalid characters, which could be used by other code and introduce security risks. + +You can use `yyjson_set_str_noesc(yyjson_val *val, bool noesc)` or `yyjson_mut_set_str_noesc(yyjson_mut_val *val, bool noesc)` to mark a string as not needing to be escaped during JSON writing. This will make string writing faster and preserve the original string bytes. ## NUL Character This library supports the `NUL` character (also known as the `null terminator`, or Unicode `U+0000`, ASCII `\0`) inside strings. diff --git a/src/yyjson.c b/src/yyjson.c index a7f8aa4..c3acde0 100644 --- a/src/yyjson.c +++ b/src/yyjson.c @@ -21,7 +21,7 @@ *============================================================================*/ #include "yyjson.h" -#include +#include /* for `HUGE_VAL/INFINIY/NAN` macros, no libm required */ @@ -119,9 +119,13 @@ uint32_t yyjson_version(void) { /* IEEE 754 floating-point binary representation */ #if defined(__STDC_IEC_559__) || defined(__STDC_IEC_60559_BFP__) # define YYJSON_HAS_IEEE_754 1 -#elif (FLT_RADIX == 2) && (DBL_MANT_DIG == 53) && (DBL_DIG == 15) && \ - (DBL_MIN_EXP == -1021) && (DBL_MAX_EXP == 1024) && \ - (DBL_MIN_10_EXP == -307) && (DBL_MAX_10_EXP == 308) +#elif FLT_RADIX == 2 && \ + FLT_MANT_DIG == 24 && FLT_DIG == 6 && \ + FLT_MIN_EXP == -125 && FLT_MAX_EXP == 128 && \ + FLT_MIN_10_EXP == -37 && FLT_MAX_10_EXP == 38 && \ + DBL_MANT_DIG == 53 && DBL_DIG == 15 && \ + DBL_MIN_EXP == -1021 && DBL_MAX_EXP == 1024 && \ + DBL_MIN_10_EXP == -307 && DBL_MAX_10_EXP == 308 # define YYJSON_HAS_IEEE_754 1 #else # define YYJSON_HAS_IEEE_754 0 @@ -374,6 +378,8 @@ uint32_t yyjson_version(void) { /* Used to write u64 literal for C89 which doesn't support "ULL" suffix. */ #undef U64 #define U64(hi, lo) ((((u64)hi##UL) << 32U) + lo##UL) +#undef U32 +#define U32(hi) ((u32)(hi##UL)) /* Used to cast away (remove) const qualifier. */ #define constcast(type) (type)(void *)(size_t)(const void *) @@ -440,44 +446,55 @@ static_inline bool write_flag_eq(yyjson_write_flag flg, yyjson_write_flag chk) { #define F64_RAW_NAN U64(0x7FF80000, 0x00000000) #endif -/* double number bits */ +/* max significant digits count in decimal when reading double number */ +#define F64_MAX_DEC_DIG 768 + +/* maximum decimal power of double number (1.7976931348623157e308) */ +#define F64_MAX_DEC_EXP 308 + +/* minimum decimal power of double number (4.9406564584124654e-324) */ +#define F64_MIN_DEC_EXP (-324) + +/* maximum binary power of double number */ +#define F64_MAX_BIN_EXP 1024 + +/* minimum binary power of double number */ +#define F64_MIN_BIN_EXP (-1021) + +/* float/double number bits */ +#define F32_BITS 32 #define F64_BITS 64 -/* double number exponent part bits */ +/* float/double number exponent part bits */ +#define F32_EXP_BITS 8 #define F64_EXP_BITS 11 -/* double number significand part bits */ +/* float/double number significand part bits */ +#define F32_SIG_BITS 23 #define F64_SIG_BITS 52 -/* double number significand part bits (with 1 hidden bit) */ +/* float/double number significand part bits (with 1 hidden bit) */ +#define F32_SIG_FULL_BITS 24 #define F64_SIG_FULL_BITS 53 -/* double number significand bit mask */ +/* float/double number significand bit mask */ +#define F32_SIG_MASK U32(0x007FFFFF) #define F64_SIG_MASK U64(0x000FFFFF, 0xFFFFFFFF) -/* double number exponent bit mask */ +/* float/double number exponent bit mask */ +#define F32_EXP_MASK U32(0x7F800000) #define F64_EXP_MASK U64(0x7FF00000, 0x00000000) -/* double number exponent bias */ +/* float/double number exponent bias */ +#define F32_EXP_BIAS 127 #define F64_EXP_BIAS 1023 -/* double number significant digits count in decimal */ +/* float/double number significant digits count in decimal */ +#define F32_DEC_DIG 9 #define F64_DEC_DIG 17 -/* max significant digits count in decimal when reading double number */ -#define F64_MAX_DEC_DIG 768 - -/* maximum decimal power of double number (1.7976931348623157e308) */ -#define F64_MAX_DEC_EXP 308 - -/* minimum decimal power of double number (4.9406564584124654e-324) */ -#define F64_MIN_DEC_EXP (-324) - -/* maximum binary power of double number */ -#define F64_MAX_BIN_EXP 1024 - -/* minimum binary power of double number */ -#define F64_MIN_BIN_EXP (-1021) +/* buffer length required for float/double number writer */ +#define FP_BUF_LEN 40 @@ -690,7 +707,15 @@ static_inline u32 byte_load_4(const void *src) { static_inline f64 f64_from_raw(u64 u) { /* use memcpy to avoid violating the strict aliasing rule */ f64 f; - memcpy(&f, &u, 8); + memcpy(&f, &u, sizeof(u)); + return f; +} + +/** Convert raw binary to float. */ +static_inline f32 f32_from_raw(u32 u) { + /* use memcpy to avoid violating the strict aliasing rule */ + f32 f; + memcpy(&f, &u, sizeof(u)); return f; } @@ -698,7 +723,15 @@ static_inline f64 f64_from_raw(u64 u) { static_inline u64 f64_to_raw(f64 f) { /* use memcpy to avoid violating the strict aliasing rule */ u64 u; - memcpy(&u, &f, 8); + memcpy(&u, &f, sizeof(u)); + return u; +} + +/** Convert double to raw binary. */ +static_inline u32 f32_to_raw(f32 f) { + /* use memcpy to avoid violating the strict aliasing rule */ + u32 u; + memcpy(&u, &f, sizeof(u)); return u; } @@ -724,20 +757,14 @@ static_inline u64 f64_raw_get_nan(bool sign) { #endif } -/** - Convert normalized u64 (highest bit is 1) to f64. - - Some compiler (such as Microsoft Visual C++ 6.0) do not support converting - number from u64 to f64. This function will first convert u64 to i64 and then - to f64, with `to nearest` rounding mode. - */ -static_inline f64 normalized_u64_to_f64(u64 val) { -#if YYJSON_U64_TO_F64_NO_IMPL - i64 sig = (i64)((val >> 1) | (val & 1)); - return ((f64)sig) * (f64)2.0; -#else - return (f64)val; +/** Casting double to float, allow overflow. */ +#if yyjson_has_attribute(no_sanitize) +__attribute__((no_sanitize("undefined"))) +#elif yyjson_gcc_available(4, 9, 0) +__attribute__((__no_sanitize_undefined__)) #endif +static_inline f32 f64_to_f32(f64 val) { + return (f32)val; } @@ -3824,7 +3851,8 @@ static_inline bool read_null(u8 **ptr, yyjson_val *val) { } /** Read 'Inf' or 'Infinity' literal (ignoring case). */ -static_inline bool read_inf(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { +static_inline bool read_inf(bool sign, u8 **ptr, u8 **pre, + yyjson_read_flag flg, yyjson_val *val) { u8 *hdr = *ptr - sign; u8 *cur = *ptr; u8 **end = ptr; @@ -3841,7 +3869,7 @@ static_inline bool read_inf(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { cur += 3; } *end = cur; - if (pre) { + if (has_read_flag(NUMBER_AS_RAW)) { /* add null-terminator for previous raw string */ if (*pre) **pre = '\0'; *pre = cur; @@ -3857,7 +3885,8 @@ static_inline bool read_inf(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { } /** Read 'NaN' literal (ignoring case). */ -static_inline bool read_nan(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { +static_inline bool read_nan(bool sign, u8 **ptr, u8 **pre, + yyjson_read_flag flg, yyjson_val *val) { u8 *hdr = *ptr - sign; u8 *cur = *ptr; u8 **end = ptr; @@ -3866,7 +3895,7 @@ static_inline bool read_nan(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { (cur[2] == 'N' || cur[2] == 'n')) { cur += 3; *end = cur; - if (pre) { + if (has_read_flag(NUMBER_AS_RAW)) { /* add null-terminator for previous raw string */ if (*pre) **pre = '\0'; *pre = cur; @@ -3883,9 +3912,9 @@ static_inline bool read_nan(bool sign, u8 **ptr, u8 **pre, yyjson_val *val) { /** Read 'Inf', 'Infinity' or 'NaN' literal (ignoring case). */ static_inline bool read_inf_or_nan(bool sign, u8 **ptr, u8 **pre, - yyjson_val *val) { - if (read_inf(sign, ptr, pre, val)) return true; - if (read_nan(sign, ptr, pre, val)) return true; + yyjson_read_flag flg, yyjson_val *val) { + if (read_inf(sign, ptr, pre, flg, val)) return true; + if (read_nan(sign, ptr, pre, flg, val)) return true; return false; } @@ -3921,7 +3950,7 @@ static_noinline bool read_number_raw(u8 **ptr, /* read first digit, check leading zero */ if (unlikely(!digi_is_digit(*cur))) { if (has_read_flag(ALLOW_INF_AND_NAN)) { - if (read_inf_or_nan(*hdr == '-', &cur, pre, val)) return_raw(); + if (read_inf_or_nan(*hdr == '-', &cur, pre, flg, val)) return_raw(); } return_err(cur, "no digit after minus sign"); } @@ -4481,7 +4510,7 @@ static_inline bool read_number(u8 **ptr, if (unlikely(!digi_is_nonzero(*cur))) { /* 0 or non-digit char */ if (unlikely(*cur != '0')) { /* non-digit char */ if (has_read_flag(ALLOW_INF_AND_NAN)) { - if (read_inf_or_nan(sign, &cur, pre, val)) { + if (read_inf_or_nan(sign, &cur, pre, flg, val)) { *end = cur; return true; } @@ -4540,7 +4569,7 @@ static_inline bool read_number(u8 **ptr, /* this number is an integer consisting of 19 digits */ if (sign && (sig > ((u64)1 << 63))) { /* overflow */ if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); - return_f64(normalized_u64_to_f64(sig)); + return_f64(unsafe_yyjson_u64_to_f64(sig)); } return_i64(sig); } @@ -4594,7 +4623,7 @@ static_inline bool read_number(u8 **ptr, /* convert to double if overflow */ if (sign) { if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); - return_f64(normalized_u64_to_f64(sig)); + return_f64(unsafe_yyjson_u64_to_f64(sig)); } return_i64(sig); } @@ -5083,7 +5112,7 @@ static_inline bool read_number(u8 **ptr, /* read first digit, check leading zero */ if (unlikely(!digi_is_digit(*cur))) { if (has_read_flag(ALLOW_INF_AND_NAN)) { - if (read_inf_or_nan(sign, &cur, pre, val)) { + if (read_inf_or_nan(sign, &cur, pre, flg, val)) { *end = cur; return true; } @@ -5117,7 +5146,7 @@ static_inline bool read_number(u8 **ptr, cur++; if (sign) { if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); - return_f64(normalized_u64_to_f64(sig)); + return_f64(unsafe_yyjson_u64_to_f64(sig)); } return_i64(sig); } @@ -5129,7 +5158,7 @@ static_inline bool read_number(u8 **ptr, /* this number is an integer consisting of 1 to 19 digits */ if (sign && (sig > ((u64)1 << 63))) { if (has_read_flag(BIGNUM_AS_RAW)) return_raw(); - return_f64(normalized_u64_to_f64(sig)); + return_f64(unsafe_yyjson_u64_to_f64(sig)); } return_i64(sig); } @@ -5789,12 +5818,12 @@ static_noinline yyjson_doc *read_root_single(u8 *hdr, if (*cur == 'n') { if (likely(read_null(&cur, val))) goto doc_end; if (has_read_flag(ALLOW_INF_AND_NAN)) { - if (read_nan(false, &cur, pre, val)) goto doc_end; + if (read_nan(false, &cur, pre, flg, val)) goto doc_end; } goto fail_literal_null; } if (has_read_flag(ALLOW_INF_AND_NAN)) { - if (read_inf_or_nan(false, &cur, pre, val)) goto doc_end; + if (read_inf_or_nan(false, &cur, pre, flg, val)) goto doc_end; } goto fail_character; @@ -5989,7 +6018,7 @@ static_inline yyjson_doc *read_root_minify(u8 *hdr, ctn_len++; if (likely(read_null(&cur, val))) goto arr_val_end; if (has_read_flag(ALLOW_INF_AND_NAN)) { - if (read_nan(false, &cur, pre, val)) goto arr_val_end; + if (read_nan(false, &cur, pre, flg, val)) goto arr_val_end; } goto fail_literal_null; } @@ -6008,7 +6037,7 @@ static_inline yyjson_doc *read_root_minify(u8 *hdr, (*cur == 'i' || *cur == 'I' || *cur == 'N')) { val_incr(); ctn_len++; - if (read_inf_or_nan(false, &cur, pre, val)) goto arr_val_end; + if (read_inf_or_nan(false, &cur, pre, flg, val)) goto arr_val_end; goto fail_character_val; } if (has_read_flag(ALLOW_COMMENTS)) { @@ -6142,7 +6171,7 @@ static_inline yyjson_doc *read_root_minify(u8 *hdr, ctn_len++; if (likely(read_null(&cur, val))) goto obj_val_end; if (has_read_flag(ALLOW_INF_AND_NAN)) { - if (read_nan(false, &cur, pre, val)) goto obj_val_end; + if (read_nan(false, &cur, pre, flg, val)) goto obj_val_end; } goto fail_literal_null; } @@ -6154,7 +6183,7 @@ static_inline yyjson_doc *read_root_minify(u8 *hdr, (*cur == 'i' || *cur == 'I' || *cur == 'N')) { val++; ctn_len++; - if (read_inf_or_nan(false, &cur, pre, val)) goto obj_val_end; + if (read_inf_or_nan(false, &cur, pre, flg, val)) goto obj_val_end; goto fail_character_val; } if (has_read_flag(ALLOW_COMMENTS)) { @@ -6418,7 +6447,7 @@ static_inline yyjson_doc *read_root_pretty(u8 *hdr, ctn_len++; if (likely(read_null(&cur, val))) goto arr_val_end; if (has_read_flag(ALLOW_INF_AND_NAN)) { - if (read_nan(false, &cur, pre, val)) goto arr_val_end; + if (read_nan(false, &cur, pre, flg, val)) goto arr_val_end; } goto fail_literal_null; } @@ -6437,7 +6466,7 @@ static_inline yyjson_doc *read_root_pretty(u8 *hdr, (*cur == 'i' || *cur == 'I' || *cur == 'N')) { val_incr(); ctn_len++; - if (read_inf_or_nan(false, &cur, pre, val)) goto arr_val_end; + if (read_inf_or_nan(false, &cur, pre, flg, val)) goto arr_val_end; goto fail_character_val; } if (has_read_flag(ALLOW_COMMENTS)) { @@ -6592,7 +6621,7 @@ static_inline yyjson_doc *read_root_pretty(u8 *hdr, ctn_len++; if (likely(read_null(&cur, val))) goto obj_val_end; if (has_read_flag(ALLOW_INF_AND_NAN)) { - if (read_nan(false, &cur, pre, val)) goto obj_val_end; + if (read_nan(false, &cur, pre, flg, val)) goto obj_val_end; } goto fail_literal_null; } @@ -6604,7 +6633,7 @@ static_inline yyjson_doc *read_root_pretty(u8 *hdr, (*cur == 'i' || *cur == 'I' || *cur == 'N')) { val++; ctn_len++; - if (read_inf_or_nan(false, &cur, pre, val)) goto obj_val_end; + if (read_inf_or_nan(false, &cur, pre, flg, val)) goto obj_val_end; goto fail_character_val; } if (has_read_flag(ALLOW_COMMENTS)) { @@ -7096,7 +7125,7 @@ static_inline u8 *write_u32_len_4(u32 val, u8 *buf) { return buf + 4; } -static_inline u8 *write_u32_len_1_8(u32 val, u8 *buf) { +static_inline u8 *write_u32_len_1_to_8(u32 val, u8 *buf) { u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; if (val < 100) { /* 1-2 digits: aa */ @@ -7143,7 +7172,7 @@ static_inline u8 *write_u32_len_1_8(u32 val, u8 *buf) { } } -static_inline u8 *write_u64_len_5_8(u32 val, u8 *buf) { +static_inline u8 *write_u32_len_5_to_8(u32 val, u8 *buf) { u32 aa, bb, cc, dd, aabb, bbcc, ccdd, lz; if (val < 1000000) { /* 5-6 digits: aabbcc */ @@ -7180,13 +7209,13 @@ static_inline u8 *write_u64(u64 val, u8 *buf) { u32 mid, low; if (val < 100000000) { /* 1-8 digits */ - buf = write_u32_len_1_8((u32)val, buf); + buf = write_u32_len_1_to_8((u32)val, buf); return buf; } else if (val < (u64)100000000 * 100000000) { /* 9-16 digits */ hgh = val / 100000000; /* (val / 100000000) */ low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ - buf = write_u32_len_1_8((u32)hgh, buf); + buf = write_u32_len_1_to_8((u32)hgh, buf); buf = write_u32_len_8(low, buf); return buf; @@ -7195,7 +7224,7 @@ static_inline u8 *write_u64(u64 val, u8 *buf) { low = (u32)(val - tmp * 100000000); /* (val % 100000000) */ hgh = (u32)(tmp / 10000); /* (tmp / 10000) */ mid = (u32)(tmp - hgh * 10000); /* (tmp % 10000) */ - buf = write_u64_len_5_8((u32)hgh, buf); + buf = write_u32_len_5_to_8((u32)hgh, buf); buf = write_u32_len_4(mid, buf); buf = write_u32_len_8(low, buf); return buf; @@ -7225,23 +7254,30 @@ static const u8 dec_trailing_zero_table[] = { 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; -/** Write an unsigned integer with a length of 1 to 16. */ +static_inline u8 *write_u32_len_1_to_9(u32 val, u8 *buf) { + if (val >= 100000000) { + u32 hi = val / 10000000; + val = val - hi * 10000000; + *buf++ = (u8)hi + '0'; + } + return write_u32_len_1_to_8((u32)val, buf); +} + static_inline u8 *write_u64_len_1_to_16(u64 val, u8 *buf) { u64 hgh; u32 low; if (val < 100000000) { /* 1-8 digits */ - buf = write_u32_len_1_8((u32)val, buf); + buf = write_u32_len_1_to_8((u32)val, buf); return buf; } else { /* 9-16 digits */ hgh = val / 100000000; /* (val / 100000000) */ low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ - buf = write_u32_len_1_8((u32)hgh, buf); + buf = write_u32_len_1_to_8((u32)hgh, buf); buf = write_u32_len_8(low, buf); return buf; } } -/** Write an unsigned integer with a length of 1 to 17. */ static_inline u8 *write_u64_len_1_to_17(u64 val, u8 *buf) { u64 hgh; u32 mid, low, one; @@ -7258,11 +7294,54 @@ static_inline u8 *write_u64_len_1_to_17(u64 val, u8 *buf) { } else if (val >= (u64)100000000){ /* len: 9 to 15 */ hgh = val / 100000000; /* (val / 100000000) */ low = (u32)(val - hgh * 100000000); /* (val % 100000000) */ - buf = write_u32_len_1_8((u32)hgh, buf); + buf = write_u32_len_1_to_8((u32)hgh, buf); buf = write_u32_len_8(low, buf); return buf; - } else { /* len: 1 to 8 */ - buf = write_u32_len_1_8((u32)val, buf); + } else { /* len: 1 to 8 */ + buf = write_u32_len_1_to_8((u32)val, buf); + return buf; + } +} + +/** + Write an unsigned integer with a length of 7 to 9 with trailing zero trimmed. + These digits are named as "abbccddee" here. + For example, input 123456000, output "123456". + */ +static_inline u8 *write_u32_len_7_to_9_trim(u32 val, u8 *buf) { + bool lz; + u32 tz, tz1, tz2; + + u32 abbcc = val / 10000; /* (abbccddee / 10000) */ + u32 ddee = val - abbcc * 10000; /* (abbccddee % 10000) */ + u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ + u32 a = (abb * 41) >> 12; /* (abb / 100) */ + u32 bb = abb - a * 100; /* (abb % 100) */ + u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ + + /* write abbcc */ + buf[0] = (u8)(a + '0'); + buf += a > 0; + lz = bb < 10 && a == 0; + byte_copy_2(buf + 0, digit_table + bb * 2 + lz); + buf -= lz; + byte_copy_2(buf + 2, digit_table + cc * 2); + + if (ddee) { + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ + byte_copy_2(buf + 4, digit_table + dd * 2); + byte_copy_2(buf + 6, digit_table + ee * 2); + tz1 = dec_trailing_zero_table[dd]; + tz2 = dec_trailing_zero_table[ee]; + tz = ee ? tz2 : (tz1 + 2); + buf += 8 - tz; + return buf; + } else { + tz1 = dec_trailing_zero_table[bb]; + tz2 = dec_trailing_zero_table[cc]; + tz = cc ? tz2 : (tz1 + tz2); + buf += 4 - tz; return buf; } } @@ -7272,38 +7351,36 @@ static_inline u8 *write_u64_len_1_to_17(u64 val, u8 *buf) { These digits are named as "abbccddeeffgghhii" here. For example, input 1234567890123000, output "1234567890123". */ -static_inline u8 *write_u64_len_16_to_17_trim(u64 sig, u8 *buf) { +static_inline u8 *write_u64_len_16_to_17_trim(u64 val, u8 *buf) { u32 tz, tz1, tz2; - u32 abbccddee = (u32)(sig / 100000000); - u32 ffgghhii = (u32)(sig - (u64)abbccddee * 100000000); + u32 abbccddee = (u32)(val / 100000000); + u32 ffgghhii = (u32)(val - (u64)abbccddee * 100000000); + u32 abbcc = abbccddee / 10000; + u32 ddee = abbccddee - abbcc * 10000; + u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ + u32 a = (abb * 41) >> 12; /* (abb / 100) */ + u32 bb = abb - a * 100; /* (abb % 100) */ + u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ + buf[0] = (u8)(a + '0'); + buf += a > 0; + byte_copy_2(buf + 0, digit_table + bb * 2); + byte_copy_2(buf + 2, digit_table + cc * 2); if (ffgghhii) { - u32 abbcc = abbccddee / 10000; - u32 ddee = abbccddee - abbcc * 10000; - u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ - u32 a = (abb * 41) >> 12; /* (abb / 100) */ - u32 bb = abb - a * 100; /* (abb % 100) */ - u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ - u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ - u32 ee = ddee - dd * 100; /* (ddee % 100) */ - + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ u32 ffgg = (u32)(((u64)ffgghhii * 109951163) >> 40); /* (val / 10000) */ - u32 hhii = ffgghhii - ffgg * 10000; /* (val % 10000) */ - u32 ff = (ffgg * 5243) >> 19; /* (aabb / 100) */ - u32 gg = ffgg - ff * 100; /* (aabb % 100) */ - - buf[0] = (u8)(a + '0'); - buf += a > 0; - byte_copy_2(buf + 0, digit_table + bb * 2); - byte_copy_2(buf + 2, digit_table + cc * 2); + u32 hhii = ffgghhii - ffgg * 10000; /* (val % 10000) */ + u32 ff = (ffgg * 5243) >> 19; /* (aabb / 100) */ + u32 gg = ffgg - ff * 100; /* (aabb % 100) */ byte_copy_2(buf + 4, digit_table + dd * 2); byte_copy_2(buf + 6, digit_table + ee * 2); byte_copy_2(buf + 8, digit_table + ff * 2); byte_copy_2(buf + 10, digit_table + gg * 2); if (hhii) { - u32 hh = (hhii * 5243) >> 19; /* (ccdd / 100) */ - u32 ii = hhii - hh * 100; /* (ccdd % 100) */ + u32 hh = (hhii * 5243) >> 19; /* (ccdd / 100) */ + u32 ii = hhii - hh * 100; /* (ccdd % 100) */ byte_copy_2(buf + 12, digit_table + hh * 2); byte_copy_2(buf + 14, digit_table + ii * 2); tz1 = dec_trailing_zero_table[hh]; @@ -7317,21 +7394,9 @@ static_inline u8 *write_u64_len_16_to_17_trim(u64 sig, u8 *buf) { return buf + 12 - tz; } } else { - u32 abbcc = abbccddee / 10000; - u32 ddee = abbccddee - abbcc * 10000; - u32 abb = (u32)(((u64)abbcc * 167773) >> 24); /* (abbcc / 100) */ - u32 a = (abb * 41) >> 12; /* (abb / 100) */ - u32 bb = abb - a * 100; /* (abb % 100) */ - u32 cc = abbcc - abb * 100; /* (abbcc % 100) */ - - buf[0] = (u8)(a + '0'); - buf += a > 0; - byte_copy_2(buf + 0, digit_table + bb * 2); - byte_copy_2(buf + 2, digit_table + cc * 2); - if (ddee) { - u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ - u32 ee = ddee - dd * 100; /* (ddee % 100) */ + u32 dd = (ddee * 5243) >> 19; /* (ddee / 100) */ + u32 ee = ddee - dd * 100; /* (ddee % 100) */ byte_copy_2(buf + 4, digit_table + dd * 2); byte_copy_2(buf + 6, digit_table + ee * 2); tz1 = dec_trailing_zero_table[dd]; @@ -7347,44 +7412,188 @@ static_inline u8 *write_u64_len_16_to_17_trim(u64 sig, u8 *buf) { } } -/** Write a signed integer in the range -324 to 308. */ +/** Write exponent part in range `e-45` to `e38`. */ +static_inline u8 *write_f32_exp(i32 exp, u8 *buf) { + bool lz; + byte_copy_2(buf, "e-"); + buf += 2 - (exp >= 0); + exp = exp < 0 ? -exp : exp; + lz = exp < 10; + byte_copy_2(buf + 0, digit_table + (u32)exp * 2 + lz); + return buf + 2 - lz; +} + +/** Write exponent part in range `e-324` to `e308`. */ static_inline u8 *write_f64_exp(i32 exp, u8 *buf) { - buf[0] = '-'; - buf += exp < 0; + byte_copy_2(buf, "e-"); + buf += 2 - (exp >= 0); exp = exp < 0 ? -exp : exp; if (exp < 100) { - u32 lz = exp < 10; + bool lz = exp < 10; byte_copy_2(buf + 0, digit_table + (u32)exp * 2 + lz); return buf + 2 - lz; } else { - u32 hi = ((u32)exp * 656) >> 16; /* exp / 100 */ - u32 lo = (u32)exp - hi * 100; /* exp % 100 */ + u32 hi = ((u32)exp * 656) >> 16; /* exp / 100 */ + u32 lo = (u32)exp - hi * 100; /* exp % 100 */ buf[0] = (u8)((u8)hi + (u8)'0'); byte_copy_2(buf + 1, digit_table + lo * 2); return buf + 3; } } +/** Magic number for fast `divide by power of 10`. */ +typedef struct { + u64 p10, mul; + u32 shr1, shr2; +} div_pow10_magic; + +/** Generated with llvm, see https://github.com/llvm/llvm-project/ + blob/main/llvm/lib/Support/DivisionByConstantInfo.cpp */ +static const div_pow10_magic div_pow10_table[] = { + { U64(0x00000000, 0x00000001), U64(0x00000000, 0x00000000), 0, 0 }, + { U64(0x00000000, 0x0000000A), U64(0xCCCCCCCC, 0xCCCCCCCD), 0, 3 }, + { U64(0x00000000, 0x00000064), U64(0x28F5C28F, 0x5C28F5C3), 2, 2 }, + { U64(0x00000000, 0x000003E8), U64(0x20C49BA5, 0xE353F7CF), 3, 4 }, + { U64(0x00000000, 0x00002710), U64(0x346DC5D6, 0x3886594B), 0, 11 }, + { U64(0x00000000, 0x000186A0), U64(0x0A7C5AC4, 0x71B47843), 5, 7 }, + { U64(0x00000000, 0x000F4240), U64(0x431BDE82, 0xD7B634DB), 0, 18 }, + { U64(0x00000000, 0x00989680), U64(0xD6BF94D5, 0xE57A42BD), 0, 23 }, + { U64(0x00000000, 0x05F5E100), U64(0xABCC7711, 0x8461CEFD), 0, 26 }, + { U64(0x00000000, 0x3B9ACA00), U64(0x0044B82F, 0xA09B5A53), 9, 11 }, + { U64(0x00000002, 0x540BE400), U64(0xDBE6FECE, 0xBDEDD5BF), 0, 33 }, + { U64(0x00000017, 0x4876E800), U64(0xAFEBFF0B, 0xCB24AAFF), 0, 36 }, + { U64(0x000000E8, 0xD4A51000), U64(0x232F3302, 0x5BD42233), 0, 37 }, + { U64(0x00000918, 0x4E72A000), U64(0x384B84D0, 0x92ED0385), 0, 41 }, + { U64(0x00005AF3, 0x107A4000), U64(0x0B424DC3, 0x5095CD81), 0, 42 }, + { U64(0x00038D7E, 0xA4C68000), U64(0x00024075, 0xF3DCEAC3), 15, 20 }, + { U64(0x002386F2, 0x6FC10000), U64(0x39A5652F, 0xB1137857), 0, 51 }, + { U64(0x01634578, 0x5D8A0000), U64(0x00005C3B, 0xD5191B53), 17, 22 }, + { U64(0x0DE0B6B3, 0xA7640000), U64(0x000049C9, 0x7747490F), 18, 24 }, + { U64(0x8AC72304, 0x89E80000), U64(0x760F253E, 0xDB4AB0d3), 0, 62 }, +}; + +/** Divide a number by power of 10. */ +static_inline void div_pow10(u64 num, u32 exp, u64 *div, u64 *mod, u64 *p10) { + u64 hi, lo; + div_pow10_magic m = div_pow10_table[exp]; + u128_mul(num >> m.shr1, m.mul, &hi, &lo); + *div = hi >> m.shr2; + *mod = num - (*div * m.p10); + *p10 = m.p10; +} + +/** Multiplies 64-bit integer and returns highest 64-bit rounded value. */ +static_inline u32 u64_round_to_odd(u64 u, u32 cp) { + u64 hi, lo; + u32 y_hi, y_lo; + u128_mul(cp, u, &hi, &lo); + y_hi = (u32)hi; + y_lo = (u32)(lo >> 32); + return y_hi | (y_lo > 1); +} + /** Multiplies 128-bit integer and returns highest 64-bit rounded value. */ -static_inline u64 round_to_odd(u64 hi, u64 lo, u64 cp) { +static_inline u64 u128_round_to_odd(u64 hi, u64 lo, u64 cp) { u64 x_hi, x_lo, y_hi, y_lo; u128_mul(cp, lo, &x_hi, &x_lo); u128_mul_add(cp, hi, x_hi, &y_hi, &y_lo); return y_hi | (y_lo > 1); } -/** - Convert double number from binary to decimal. - The output significand is shortest decimal but may have trailing zeros. - - @param sig_raw The raw value of significand in IEEE 754 format. - @param exp_raw The raw value of exponent in IEEE 754 format. - @param sig_bin The decoded value of significand in binary. - @param exp_bin The decoded value of exponent in binary. - @param sig_dec The output value of significand in decimal. - @param exp_dec The output value of exponent in decimal. - @warning The input double number should not be 0, inf, nan. - */ +/** Convert f32 from binary to decimal (shortest but may have trailing zeros). + The input should not be 0, inf or nan. */ +static_inline void f32_bin_to_dec(u32 sig_raw, u32 exp_raw, + u32 sig_bin, i32 exp_bin, + u32 *sig_dec, i32 *exp_dec) { + + bool is_even, irregular, round_up, trim; + bool u0_inside, u1_inside, w0_inside, w1_inside; + u64 p10_hi, p10_lo, hi, lo; + u32 s, sp, cb, cbl, cbr, vb, vbl, vbr, upper, lower, mid; + i32 k, h; + + /* Fast path, see f64_bin_to_dec(). */ + while (likely(sig_raw)) { + u32 mod, dec, add_1, add_10, s_hi, s_lo; + u32 c, half_ulp, t0, t1; + + /* k = floor(exp_bin * log10(2)); */ + /* h = exp_bin + floor(log2(10) * -k); (h = 0/1/2/3) */ + k = (i32)(exp_bin * 315653) >> 20; + h = exp_bin + ((-k * 217707) >> 16); + pow10_table_get_sig(-k, &p10_hi, &p10_lo); + + /* sig_bin << (1/2/3/4) */ + cb = sig_bin << (h + 1); + u128_mul(cb, p10_hi, &hi, &lo); + s_hi = (u32)(hi); + s_lo = (u32)(lo >> 32); + mod = s_hi % 10; + dec = s_hi - mod; + + /* right shift 4 to fit in u32 */ + c = (mod << (32 - 4)) | (s_lo >> 4); + half_ulp = (u32)(p10_hi >> (32 + 4 - h)); + + /* check w1, u0, w0 range */ + w1_inside = (s_lo >= ((u32)1 << 31)); + if (unlikely(s_lo == ((u32)1 << 31))) break; + u0_inside = (half_ulp >= c); + if (unlikely(half_ulp == c)) break; + t0 = (u32)10 << (32 - 4); + t1 = c + half_ulp; + w0_inside = (t1 >= t0); + if (unlikely(t0 - t1 <= (u32)1)) break; + + trim = (u0_inside | w0_inside); + add_10 = (w0_inside ? 10 : 0); + add_1 = mod + w1_inside; + *sig_dec = dec + (trim ? add_10 : add_1); + *exp_dec = k; + return; + } + + /* Schubfach algorithm, see f64_bin_to_dec(). */ + irregular = (sig_raw == 0 && exp_raw > 1); + is_even = !(sig_bin & 1); + cbl = 4 * sig_bin - 2 + irregular; + cb = 4 * sig_bin; + cbr = 4 * sig_bin + 2; + + /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */ + /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */ + k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; + h = exp_bin + ((-k * 217707) >> 16) + 1; + pow10_table_get_sig(-k, &p10_hi, &p10_lo); + p10_hi += 1; + + vbl = u64_round_to_odd(p10_hi, cbl << h); + vb = u64_round_to_odd(p10_hi, cb << h); + vbr = u64_round_to_odd(p10_hi, cbr << h); + lower = vbl + !is_even; + upper = vbr - !is_even; + + s = vb / 4; + if (s >= 10) { + sp = s / 10; + u0_inside = (lower <= 40 * sp); + w0_inside = (upper >= 40 * sp + 40); + if (u0_inside != w0_inside) { + *sig_dec = sp * 10 + (w0_inside ? 10 : 0); + *exp_dec = k; + return; + } + } + u1_inside = (lower <= 4 * s); + w1_inside = (upper >= 4 * s + 4); + mid = 4 * s + 2; + round_up = (vb > mid) || (vb == mid && (s & 1) != 0); + *sig_dec = s + ((u1_inside != w1_inside) ? w1_inside : round_up); + *exp_dec = k; +} + +/** Convert f64 from binary to decimal (shortest but may have trailing zeros). + The input should not be 0, inf or nan. */ static_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw, u64 sig_bin, i32 exp_bin, u64 *sig_dec, i32 *exp_dec) { @@ -7415,9 +7624,8 @@ static_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw, u64 c, half_ulp, t0, t1; /* k = floor(exp_bin * log10(2)); */ - k = (i32)(exp_bin * 315653) >> 20; - /* h = exp_bin + floor(log2(10) * -k); (h = 0/1/2/3) */ + k = (i32)(exp_bin * 315653) >> 20; h = exp_bin + ((-k * 217707) >> 16); pow10_table_get_sig(-k, &p10_hi, &p10_lo); @@ -7429,25 +7637,23 @@ static_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw, dec = s_hi - mod; /* right shift 4 to fit in u64 */ - c = (mod << 60) | (s_lo >> 4); + c = (mod << (64 - 4)) | (s_lo >> 4); half_ulp = p10_hi >> (4 - h); + /* check w1, u0, w0 range */ w1_inside = (s_lo >= ((u64)1 << 63)); if (unlikely(s_lo == ((u64)1 << 63))) break; - u0_inside = (half_ulp >= c); if (unlikely(half_ulp == c)) break; - - t0 = (u64)10 << (64 - 4); + t0 = ((u64)10 << (64 - 4)); t1 = c + half_ulp; - w0_inside = t1 >= t0; + w0_inside = (t1 >= t0); if (unlikely(t0 - t1 <= (u64)1)) break; trim = (u0_inside | w0_inside); add_10 = (w0_inside ? 10 : 0); add_1 = mod + w1_inside; - s = dec + (trim ? add_10 : add_1); - *sig_dec = s; + *sig_dec = dec + (trim ? add_10 : add_1); *exp_dec = k; return; } @@ -7466,18 +7672,15 @@ static_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw, cbr = 4 * sig_bin + 2; /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */ - k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; - /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */ + k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; h = exp_bin + ((-k * 217707) >> 16) + 1; - pow10_table_get_sig(-k, &p10_hi, &p10_lo); p10_lo += 1; - vbl = round_to_odd(p10_hi, p10_lo, cbl << h); - vb = round_to_odd(p10_hi, p10_lo, cb << h); - vbr = round_to_odd(p10_hi, p10_lo, cbr << h); - + vbl = u128_round_to_odd(p10_hi, p10_lo, cbl << h); + vb = u128_round_to_odd(p10_hi, p10_lo, cb << h); + vbr = u128_round_to_odd(p10_hi, p10_lo, cbr << h); lower = vbl + !is_even; upper = vbr - !is_even; @@ -7492,31 +7695,207 @@ static_inline void f64_bin_to_dec(u64 sig_raw, u32 exp_raw, return; } } - u1_inside = (lower <= 4 * s); w1_inside = (upper >= 4 * s + 4); - mid = 4 * s + 2; round_up = (vb > mid) || (vb == mid && (s & 1) != 0); - *sig_dec = s + ((u1_inside != w1_inside) ? w1_inside : round_up); *exp_dec = k; } +/** Convert f64 from binary to decimal (fast but not the shortest). + The input should not be 0, inf, nan. */ +static_inline void f64_bin_to_dec_fast(u64 sig_raw, u32 exp_raw, + u64 sig_bin, i32 exp_bin, + u64 *sig_dec, i32 *exp_dec, + bool *round_up) { + u64 cb, p10_hi, p10_lo, s_hi, s_lo; + i32 k, h; + bool irregular, u; + + irregular = (sig_raw == 0 && exp_raw > 1); + + /* k = floor(exp_bin * log10(2) + (irregular ? log10(3.0 / 4.0) : 0)); */ + /* h = exp_bin + floor(log2(10) * -k) + 1; (h = 1/2/3/4) */ + k = (i32)(exp_bin * 315653 - (irregular ? 131237 : 0)) >> 20; + h = exp_bin + ((-k * 217707) >> 16); + pow10_table_get_sig(-k, &p10_hi, &p10_lo); + + /* sig_bin << (1/2/3/4) */ + cb = sig_bin << (h + 1); + u128_mul(cb, p10_lo, &s_hi, &s_lo); + u128_mul_add(cb, p10_hi, s_hi, &s_hi, &s_lo); + + /* round up */ + u = s_lo >= (irregular ? U64(0x55555555, 0x55555555) : ((u64)1 << 63)); + + *sig_dec = s_hi + u; + *exp_dec = k; + *round_up = u; + return; +} + +/** Write inf/nan if allowed. */ +static_inline u8 *write_inf_or_nan(u8 *buf, yyjson_write_flag flg, + u64 sig_raw, bool sign) { + if (has_write_flag(INF_AND_NAN_AS_NULL)) { + byte_copy_4(buf, "null"); + return buf + 4; + } + if (has_write_flag(ALLOW_INF_AND_NAN)) { + if (sig_raw == 0) { + buf[0] = '-'; + buf += sign; + byte_copy_8(buf, "Infinity"); + return buf + 8; + } else { + byte_copy_4(buf, "NaN"); + return buf + 3; + } + } + return NULL; +} + +/** + Write a float number (requires 40 bytes buffer). + We follow the ECMAScript specification for printing floating-point numbers, + similar to `Number.prototype.toString()`, but with the following changes: + 1. Keep the negative sign of `-0.0` to preserve input information. + 2. Keep decimal point to indicate the number is floating point. + 3. Remove positive sign in the exponent part. + */ +static_noinline u8 *write_f32_raw(u8 *buf, u64 raw_f64, + yyjson_write_flag flg) { + u32 sig_bin, sig_dec, sig_raw; + i32 exp_bin, exp_dec, sig_len, dot_ofs; + u32 exp_raw, raw; + u8 *end; + bool sign; + + /* cast double to float */ + raw = f32_to_raw(f64_to_f32(f64_from_raw(raw_f64))); + + /* decode raw bytes from IEEE-754 double format. */ + sign = (bool)(raw >> (F32_BITS - 1)); + sig_raw = raw & F32_SIG_MASK; + exp_raw = (raw & F32_EXP_MASK) >> F32_SIG_BITS; + + /* return inf or nan */ + if (unlikely(exp_raw == ((u32)1 << F32_EXP_BITS) - 1)) { + return write_inf_or_nan(buf, flg, sig_raw, sign); + } + + /* add sign for all finite number */ + buf[0] = '-'; + buf += sign; + + /* return zero */ + if ((raw << 1) == 0) { + byte_copy_4(buf, "0.0"); + return buf + 3; + } + + if (likely(exp_raw != 0)) { + /* normal number */ + sig_bin = sig_raw | ((u32)1 << F32_SIG_BITS); + exp_bin = (i32)exp_raw - F32_EXP_BIAS - F32_SIG_BITS; + + /* fast path for small integer number without fraction */ + if ((-F32_SIG_BITS <= exp_bin && exp_bin <= 0) && + (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) { + sig_dec = sig_bin >> -exp_bin; /* range: [1, 0xFFFFFF] */ + buf = write_u32_len_1_to_8(sig_dec, buf); + byte_copy_2(buf, ".0"); + return buf + 2; + } + + /* binary to decimal */ + f32_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* the sig length is 7 or 9 */ + sig_len = 7 + (sig_dec >= (u32)10000000) + (sig_dec >= (u32)100000000); + + /* the decimal point offset relative to the first digit */ + dot_ofs = sig_len + exp_dec; + + if (-6 < dot_ofs && dot_ofs <= 21) { + i32 num_sep_pos, dot_set_pos, pre_ofs; + u8 *num_hdr, *num_end, *num_sep, *dot_end; + bool no_pre_zero; + + /* fill zeros */ + memset(buf, '0', 32); + + /* not prefixed with zero, e.g. 1.234, 1234.0 */ + no_pre_zero = (dot_ofs > 0); + + /* write the number as digits */ + pre_ofs = no_pre_zero ? 0 : (2 - dot_ofs); + num_hdr = buf + pre_ofs; + num_end = write_u32_len_7_to_9_trim(sig_dec, num_hdr); + + /* seperate these digits to leave a space for dot */ + num_sep_pos = no_pre_zero ? dot_ofs : 0; + num_sep = num_hdr + num_sep_pos; + byte_move_8(num_sep + no_pre_zero, num_sep); + num_end += no_pre_zero; + + /* write the dot */ + dot_set_pos = yyjson_max(dot_ofs, 1); + buf[dot_set_pos] = '.'; + + /* return the ending */ + dot_end = buf + dot_ofs + 2; + return yyjson_max(dot_end, num_end); + + } else { + /* write with scientific notation, e.g. 1.234e56 */ + end = write_u32_len_7_to_9_trim(sig_dec, buf + 1); + end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ + exp_dec += sig_len - 1; + buf[0] = buf[1]; + buf[1] = '.'; + return write_f32_exp(exp_dec, end); + } + + } else { + /* subnormal number */ + sig_bin = sig_raw; + exp_bin = 1 - F32_EXP_BIAS - F32_SIG_BITS; + + /* binary to decimal */ + f32_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); + + /* write significand part */ + end = write_u32_len_1_to_9(sig_dec, buf + 1); + buf[0] = buf[1]; + buf[1] = '.'; + exp_dec += (i32)(end - buf) - 2; + + /* trim trailing zeros */ + end -= *(end - 1) == '0'; /* branchless for last zero */ + end -= *(end - 1) == '0'; /* branchless for second last zero */ + while (*(end - 1) == '0') end--; /* for unlikely more zeros */ + end -= *(end - 1) == '.'; /* remove dot, e.g. 2.e-321 -> 2e-321 */ + + /* write exponent part */ + return write_f32_exp(exp_dec, end); + } +} + /** Write a double number (requires 40 bytes buffer). - - We follows the ECMAScript specification to print floating point numbers, - but with the following changes: - 1. Keep the negative sign of 0.0 to preserve input information. + We follow the ECMAScript specification for printing floating-point numbers, + similar to `Number.prototype.toString()`, but with the following changes: + 1. Keep the negative sign of `-0.0` to preserve input information. 2. Keep decimal point to indicate the number is floating point. - 3. Remove positive sign of exponent part. + 3. Remove positive sign in the exponent part. */ static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { u64 sig_bin, sig_dec, sig_raw; i32 exp_bin, exp_dec, sig_len, dot_ofs; - u32 exp_raw, hi, lo; - u8 *hdr, *end; + u32 exp_raw; + u8 *end; bool sign; /* decode raw bytes from IEEE-754 double format. */ @@ -7526,28 +7905,12 @@ static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { /* return inf or nan */ if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) { - if (has_write_flag(INF_AND_NAN_AS_NULL)) { - byte_copy_4(buf, "null"); - return buf + 4; - } - if (has_write_flag(ALLOW_INF_AND_NAN)) { - if (sig_raw == 0) { - buf[0] = '-'; - buf += sign; - byte_copy_8(buf, "Infinity"); - return buf + 8; - } else { - byte_copy_4(buf, "NaN"); - return buf + 3; - } - } - return NULL; + return write_inf_or_nan(buf, flg, sig_raw, sign); } /* add sign for all finite number */ buf[0] = '-'; buf += sign; - hdr = buf; /* return zero */ if ((raw << 1) == 0) { @@ -7561,14 +7924,12 @@ static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS; /* fast path for small integer number without fraction */ - if (-F64_SIG_BITS <= exp_bin && exp_bin <= 0) { - if (u64_tz_bits(sig_bin) >= (u32)-exp_bin) { - /* number is integer in range 1 to 0x1FFFFFFFFFFFFF */ - sig_dec = sig_bin >> -exp_bin; - buf = write_u64_len_1_to_16(sig_dec, buf); - byte_copy_2(buf, ".0"); - return buf + 2; - } + if ((-F64_SIG_BITS <= exp_bin && exp_bin <= 0) && + (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) { + sig_dec = sig_bin >> -exp_bin; /* range: [1, 0x1FFFFFFFFFFFFF] */ + buf = write_u64_len_1_to_16(sig_dec, buf); + byte_copy_2(buf, ".0"); + return buf + 2; } /* binary to decimal */ @@ -7586,14 +7947,14 @@ static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { bool no_pre_zero; /* fill zeros */ - memset(hdr, '0', 32); + memset(buf, '0', 32); /* not prefixed with zero, e.g. 1.234, 1234.0 */ no_pre_zero = (dot_ofs > 0); /* write the number as digits */ pre_ofs = no_pre_zero ? 0 : (2 - dot_ofs); - num_hdr = hdr + pre_ofs; + num_hdr = buf + pre_ofs; num_end = write_u64_len_16_to_17_trim(sig_dec, num_hdr); /* seperate these digits to leave a space for dot */ @@ -7603,23 +7964,21 @@ static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { num_end += no_pre_zero; /* write the dot */ - dot_set_pos = no_pre_zero ? dot_ofs : 1; - hdr[dot_set_pos] = '.'; + dot_set_pos = yyjson_max(dot_ofs, 1); + buf[dot_set_pos] = '.'; /* return the ending */ - dot_end = hdr + dot_ofs + 2; - return dot_end < num_end ? num_end : dot_end; + dot_end = buf + dot_ofs + 2; + return yyjson_max(dot_end, num_end); } else { /* write with scientific notation, e.g. 1.234e56 */ end = write_u64_len_16_to_17_trim(sig_dec, buf + 1); end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ exp_dec += sig_len - 1; - hdr[0] = hdr[1]; - hdr[1] = '.'; - end[0] = 'e'; - buf = write_f64_exp(exp_dec, end + 1); - return buf; + buf[0] = buf[1]; + buf[1] = '.'; + return write_f64_exp(exp_dec, end); } } else { @@ -7631,58 +7990,189 @@ static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, &sig_dec, &exp_dec); /* write significand part */ - buf = write_u64_len_1_to_17(sig_dec, buf + 1); - hdr[0] = hdr[1]; - hdr[1] = '.'; - exp_dec += (i32)(buf - hdr) - 2; + end = write_u64_len_1_to_17(sig_dec, buf + 1); + buf[0] = buf[1]; + buf[1] = '.'; + exp_dec += (i32)(end - buf) - 2; /* trim trailing zeros */ - buf -= *(buf - 1) == '0'; /* branchless for last zero */ - buf -= *(buf - 1) == '0'; /* branchless for second last zero */ - while (*(buf - 1) == '0') buf--; /* for unlikely more trailing zeros */ - buf -= *(buf - 1) == '.'; /* remove dot, e.g. 2.e-321 -> 2e-321 */ + end -= *(end - 1) == '0'; /* branchless for last zero */ + end -= *(end - 1) == '0'; /* branchless for second last zero */ + while (*(end - 1) == '0') end--; /* for unlikely more zeros */ + end -= *(end - 1) == '.'; /* remove dot, e.g. 2.e-321 -> 2e-321 */ /* write exponent part */ - byte_copy_2(buf, "e-"); - exp_dec = -exp_dec; - hi = ((u32)exp_dec * 656) >> 16; /* exp / 100 */ - lo = (u32)exp_dec - hi * 100; /* exp % 100 */ - buf[2] = (u8)((u8)hi + (u8)'0'); - byte_copy_2(buf + 3, digit_table + lo * 2); - return buf + 5; + return write_f64_exp(exp_dec, end); } } -#else /* FP_WRITER */ +/** + Write a double number using fixed-point notation (requires 40 bytes buffer). -/** Write a double number (requires 40 bytes buffer). */ -static_inline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { - /* - For IEEE 754, `DBL_DECIMAL_DIG` is 17 for round-trip. - For non-IEEE formats, 17 is used to avoid buffer overflow, - round-trip is not guaranteed. - */ -#if defined(DBL_DECIMAL_DIG) && DBL_DECIMAL_DIG != 17 - int dig = DBL_DECIMAL_DIG > 17 ? 17 : DBL_DECIMAL_DIG; -#else - int dig = 17; -#endif + We follow the ECMAScript specification for printing floating-point numbers, + similar to `Number.prototype.toFixed(prec)`, but with the following changes: + 1. Keep the negative sign of `-0.0` to preserve input information. + 2. Keep decimal point to indicate the number is floating point. + 3. Remove positive sign in the exponent part. + 4. Remove trailing zeros and reduce unnecessary precision. + */ +static_noinline u8 *write_f64_raw_fixed(u8 *buf, u64 raw, yyjson_write_flag flg, + u32 prec) { + u64 sig_bin, sig_dec, sig_raw; + i32 exp_bin, exp_dec, sig_len, dot_ofs; + u32 exp_raw; + u8 *end; + bool sign; - /* - The snprintf() function is locale-dependent. For currently known locales, - (en, zh, ja, ko, am, he, hi) use '.' as the decimal point, while other - locales use ',' as the decimal point. we need to replace ',' with '.' - to avoid the locale setting. - */ - f64 val = f64_from_raw(raw); + /* decode raw bytes from IEEE-754 double format. */ + sign = (bool)(raw >> (F64_BITS - 1)); + sig_raw = raw & F64_SIG_MASK; + exp_raw = (u32)((raw & F64_EXP_MASK) >> F64_SIG_BITS); + + /* return inf or nan */ + if (unlikely(exp_raw == ((u32)1 << F64_EXP_BITS) - 1)) { + return write_inf_or_nan(buf, flg, sig_raw, sign); + } + + /* add sign for all finite number */ + buf[0] = '-'; + buf += sign; + + /* return zero */ + if ((raw << 1) == 0) { + byte_copy_4(buf, "0.0"); + return buf + 3; + } + + if (likely(exp_raw != 0)) { + /* normal number */ + sig_bin = sig_raw | ((u64)1 << F64_SIG_BITS); + exp_bin = (i32)exp_raw - F64_EXP_BIAS - F64_SIG_BITS; + + /* fast path for small integer number without fraction */ + if ((-F64_SIG_BITS <= exp_bin && exp_bin <= 0) && + (u64_tz_bits(sig_bin) >= (u32)-exp_bin)) { + sig_dec = sig_bin >> -exp_bin; /* range: [1, 0x1FFFFFFFFFFFFF] */ + buf = write_u64_len_1_to_16(sig_dec, buf); + byte_copy_2(buf, ".0"); + return buf + 2; + } + + /* only `fabs(num) < 1e21` are processed here. */ + if ((raw << 1) < (U64(0x444B1AE4, 0xD6E2EF50) << 1)) { + i32 num_sep_pos, dot_set_pos, pre_ofs; + u8 *num_hdr, *num_end, *num_sep; + bool round_up, no_pre_zero; + + /* binary to decimal */ + f64_bin_to_dec_fast(sig_raw, exp_raw, sig_bin, exp_bin, + &sig_dec, &exp_dec, &round_up); + + /* the sig length is 16 or 17 */ + sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); + + /* limit the length of digits after the decimal point */ + if (exp_dec < -1) { + i32 sig_len_cut = -exp_dec - (i32)prec; + if (sig_len_cut > sig_len) { + byte_copy_4(buf, "0.0"); + return buf + 3; + } + if (sig_len_cut > 0) { + u64 div, mod, p10; + + /* remove round up */ + sig_dec -= round_up; + sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); + + /* cut off some digits */ + div_pow10(sig_dec, (u32)sig_len_cut, &div, &mod, &p10); + + /* add round up */ + sig_dec = div + (mod >= p10 / 2); + + /* update exp and sig length */ + exp_dec += sig_len_cut; + sig_len -= sig_len_cut; + sig_len += (sig_dec >= u64_pow10_table[sig_len]); + } + if (sig_len <= 0) { + byte_copy_4(buf, "0.0"); + return buf + 3; + } + } + + /* fill zeros */ + memset(buf, '0', 32); + + /* the decimal point offset relative to the first digit */ + dot_ofs = sig_len + exp_dec; + + /* not prefixed with zero, e.g. 1.234, 1234.0 */ + no_pre_zero = (dot_ofs > 0); + + /* write the number as digits */ + pre_ofs = no_pre_zero ? 0 : (1 - dot_ofs); + num_hdr = buf + pre_ofs; + num_end = write_u64_len_1_to_17(sig_dec, num_hdr); + + /* seperate these digits to leave a space for dot */ + num_sep_pos = no_pre_zero ? dot_ofs : -dot_ofs; + num_sep = buf + num_sep_pos; + byte_move_16(num_sep + 1, num_sep); + num_end += (exp_dec < 0); + + /* write the dot */ + dot_set_pos = yyjson_max(dot_ofs, 1); + buf[dot_set_pos] = '.'; + + /* remove trailing zeros */ + buf += dot_set_pos + 2; + buf = yyjson_max(buf, num_end); + buf -= *(buf - 1) == '0'; /* branchless for last zero */ + buf -= *(buf - 1) == '0'; /* branchless for second last zero */ + while (*(buf - 1) == '0') buf--; /* for unlikely more zeros */ + buf += *(buf - 1) == '.'; /* keep a zero after dot */ + return buf; + + } else { + /* binary to decimal */ + f64_bin_to_dec(sig_raw, exp_raw, sig_bin, exp_bin, + &sig_dec, &exp_dec); + + /* the sig length is 16 or 17 */ + sig_len = 16 + (sig_dec >= (u64)100000000 * 100000000); + + /* write with scientific notation, e.g. 1.234e56 */ + end = write_u64_len_16_to_17_trim(sig_dec, buf + 1); + end -= (end == buf + 2); /* remove '.0', e.g. 2.0e34 -> 2e34 */ + exp_dec += sig_len - 1; + buf[0] = buf[1]; + buf[1] = '.'; + return write_f64_exp(exp_dec, end); + } + } else { + /* subnormal number */ + byte_copy_4(buf, "0.0"); + return buf + 3; + } +} + +#else /* FP_WRITER */ + #if YYJSON_MSC_VER >= 1400 - int len = sprintf_s((char *)buf, 32, "%.*g", dig, val); +#define snprintf_num(buf, len, fmt, dig, val) \ + sprintf_s((char *)buf, len, fmt, dig, val) #elif defined(snprintf) || (YYJSON_STDC_VER >= 199901L) - int len = snprintf((char *)buf, 32, "%.*g", dig, val); +#define snprintf_num(buf, len, fmt, dig, val) \ + snprintf((char *)buf, len, fmt, dig, val) #else - int len = sprintf((char *)buf, "%.*g", dig, val); +#define snprintf_num(buf, len, fmt, dig, val) \ + sprintf((char *)buf, fmt, dig, val) #endif - + +static_noinline u8 *write_fp_reformat(u8 *buf, int len, + yyjson_write_flag flg, bool fixed) { u8 *cur = buf; if (unlikely(len < 1)) return NULL; cur += (*cur == '-'); @@ -7691,8 +8181,7 @@ static_inline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { if (has_write_flag(INF_AND_NAN_AS_NULL)) { byte_copy_4(buf, "null"); return buf + 4; - } - else if (has_write_flag(ALLOW_INF_AND_NAN)) { + } else if (has_write_flag(ALLOW_INF_AND_NAN)) { if (*cur == 'i') { byte_copy_8(cur, "Infinity"); return cur + 8; @@ -7704,18 +8193,86 @@ static_inline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { return NULL; } else { /* finite number */ - int i = 0; - bool fp = false; - for (; i < len; i++) { - if (buf[i] == ',') buf[i] = '.'; - if (digi_is_fp((u8)buf[i])) fp = true; + u8 *end = buf + len, *dot = NULL, *exp = NULL; + + /* + The snprintf() function is locale-dependent. For currently known + locales, (en, zh, ja, ko, am, he, hi) use '.' as the decimal point, + while other locales use ',' as the decimal point. we need to replace + ',' with '.' to avoid the locale setting. + */ + for (; cur < end; cur++) { + switch (*cur) { + case ',': *cur = '.'; /* fallthrough */ + case '.': dot = cur; break; + case 'e': exp = cur; break; + default: break; + } } - if (!fp) { - byte_copy_2(buf + len, ".0"); - len += 2; + if (fixed) { + /* remove trailing zeros */ + while (*(end - 1) == '0') end--; + end += *(end - 1) == '.'; + } else { + if (!dot && !exp) { + /* add decimal point, e.g. 123 -> 123.0 */ + byte_copy_2(end, ".0"); + end += 2; + } else if (exp) { + cur = exp + 1; + /* remove positive sign in the exponent part */ + if (*cur == '+') { + memmove(cur, cur + 1, (usize)(end - cur - 1)); + end--; + } + cur += (*cur == '-'); + /* remove leading zeros in the exponent part */ + if (*cur == '0') { + u8 *hdr = cur++; + while (*cur == '0') cur++; + memmove(hdr, cur, (usize)(end - cur)); + end -= (usize)(cur - hdr); + } + } } + return end; + } +} + +/** Write a double number (requires 40 bytes buffer). */ +static_noinline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { +#if defined(DBL_DECIMAL_DIG) && DBL_DECIMAL_DIG < F64_DEC_DIG + int dig = DBL_DECIMAL_DIG; +#else + int dig = F64_DEC_DIG; +#endif + f64 val = f64_from_raw(raw); + int len = snprintf_num(buf, FP_BUF_LEN, "%.*g", dig, val); + return write_fp_reformat(buf, len, flg, false); +} + +/** Write a double number (requires 40 bytes buffer). */ +static_noinline u8 *write_f32_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { +#if defined(FLT_DECIMAL_DIG) && FLT_DECIMAL_DIG < F32_DEC_DIG + int dig = FLT_DECIMAL_DIG; +#else + int dig = F32_DEC_DIG; +#endif + f64 val = (f64)f64_to_f32(f64_from_raw(raw)); + int len = snprintf_num(buf, FP_BUF_LEN, "%.*g", dig, val); + return write_fp_reformat(buf, len, flg, false); +} + +/** Write a double number (requires 40 bytes buffer). */ +static_noinline u8 *write_f64_raw_fixed(u8 *buf, u64 raw, + yyjson_write_flag flg, u32 prec) { + f64 val = (f64)f64_from_raw(raw); + if (-1e21 < val && val < 1e21) { + int len = snprintf_num(buf, FP_BUF_LEN, "%.*f", (int)prec, val); + return write_fp_reformat(buf, len, flg, true); + } else { + return write_f64_raw(buf, raw, flg); } - return buf + len; } #endif /* FP_WRITER */ @@ -7723,15 +8280,35 @@ static_inline u8 *write_f64_raw(u8 *buf, u64 raw, yyjson_write_flag flg) { /** Write a JSON number (requires 40 bytes buffer). */ static_inline u8 *write_number(u8 *cur, yyjson_val *val, yyjson_write_flag flg) { - if (val->tag & YYJSON_SUBTYPE_REAL) { - u64 raw = val->uni.u64; - return write_f64_raw(cur, raw, flg); - } else { + if (!(val->tag & YYJSON_SUBTYPE_REAL)) { u64 pos = val->uni.u64; u64 neg = ~pos + 1; - usize sgn = ((val->tag & YYJSON_SUBTYPE_SINT) > 0) & ((i64)pos < 0); + usize sign = ((val->tag & YYJSON_SUBTYPE_SINT) > 0) & ((i64)pos < 0); *cur = '-'; - return write_u64(sgn ? neg : pos, cur + sgn); + return write_u64(sign ? neg : pos, cur + sign); + } else { + u64 raw = val->uni.u64; + u32 val_fmt = (u32)(val->tag >> 32); + u32 all_fmt = flg; + u32 fmt = val_fmt | all_fmt; + if (likely(!(fmt >> (32 - YYJSON_WRITE_FP_FLAG_BITS)))) { + /* double to shortest */ + return write_f64_raw(cur, raw, flg); + } else if (fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS)) { + /* double to fixed */ + u32 val_prec = val_fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS); + u32 all_prec = all_fmt >> (32 - YYJSON_WRITE_FP_PREC_BITS); + u32 prec = val_prec ? val_prec : all_prec; + return write_f64_raw_fixed(cur, raw, flg, prec); + } else { + if (fmt & YYJSON_WRITE_FP_TO_FLOAT) { + /* float to shortest */ + return write_f32_raw(cur, raw, flg); + } else { + /* double to shortest */ + return write_f64_raw(cur, raw, flg); + } + } } } @@ -8480,7 +9057,7 @@ static_inline u8 *yyjson_write_single(yyjson_val *val, break; case YYJSON_TYPE_NUM: - incr_len(40 + end_len); + incr_len(FP_BUF_LEN + end_len); cur = write_number(cur, val, flg); if (unlikely(!cur)) goto fail_num; break; @@ -8623,7 +9200,7 @@ static_inline u8 *yyjson_write_minify(const yyjson_val *root, goto val_end; } if (val_type == YYJSON_TYPE_NUM) { - incr_len(40); + incr_len(FP_BUF_LEN); cur = write_number(cur, val, flg); if (unlikely(!cur)) goto fail_num; *cur++ = ','; @@ -8816,7 +9393,7 @@ static_inline u8 *yyjson_write_pretty(const yyjson_val *root, } if (val_type == YYJSON_TYPE_NUM) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); - incr_len(40 + (no_indent ? 0 : level * 4)); + incr_len(FP_BUF_LEN + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_number(cur, val, flg); if (unlikely(!cur)) goto fail_num; @@ -9182,7 +9759,7 @@ static_inline u8 *yyjson_mut_write_minify(const yyjson_mut_val *root, goto val_end; } if (val_type == YYJSON_TYPE_NUM) { - incr_len(40); + incr_len(FP_BUF_LEN); cur = write_number(cur, (yyjson_val *)val, flg); if (unlikely(!cur)) goto fail_num; *cur++ = ','; @@ -9381,7 +9958,7 @@ static_inline u8 *yyjson_mut_write_pretty(const yyjson_mut_val *root, } if (val_type == YYJSON_TYPE_NUM) { no_indent = (bool)((u8)ctn_obj & (u8)ctn_len); - incr_len(40 + (no_indent ? 0 : level * 4)); + incr_len(FP_BUF_LEN + (no_indent ? 0 : level * 4)); cur = write_indent(cur, no_indent ? 0 : level, spaces); cur = write_number(cur, (yyjson_val *)val, flg); if (unlikely(!cur)) goto fail_num; diff --git a/src/yyjson.h b/src/yyjson.h index 3941ddd..033590d 100644 --- a/src/yyjson.h +++ b/src/yyjson.h @@ -1131,6 +1131,28 @@ static const yyjson_write_flag YYJSON_WRITE_NEWLINE_AT_END = 1 << 7; +/** The highest 8 bits of `yyjson_write_flag` and real number value's `tag` + are reserved for controlling the output format of floating-point numbers. */ +#define YYJSON_WRITE_FP_FLAG_BITS 8 + +/** The highest 4 bits of flag are reserved for precision value. */ +#define YYJSON_WRITE_FP_PREC_BITS 4 + +/** Write floating-point number using fixed-point notation. + - This is similar to ECMAScript `Number.prototype.toFixed(prec)`, + but with trailing zeros removed. The `prec` ranges from 1 to 15. + - This will produce shorter output but may lose some precision. */ +#define YYJSON_WRITE_FP_TO_FIXED(prec) ((yyjson_write_flag)( \ + (uint32_t)((uint32_t)(prec)) << (32 - 4) )) + +/** Write floating-point numbers using single-precision (float). + - This casts `double` to `float` before serialization. + - This will produce shorter output, but may lose some precision. + - This flag is ignored if `YYJSON_WRITE_FP_TO_FIXED(prec)` is also used. */ +#define YYJSON_WRITE_FP_TO_FLOAT ((yyjson_write_flag)(1 << (32 - 5))) + + + /** Result code for JSON writer */ typedef uint32_t yyjson_write_code; @@ -1780,11 +1802,33 @@ yyjson_api_inline bool yyjson_set_sint(yyjson_val *val, int64_t num); @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num); +/** Set the value to float. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_float(yyjson_val *val, float num); + +/** Set the value to double. + Returns false if input is NULL or `val` is object or array. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_double(yyjson_val *val, double num); + /** Set the value to real. Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num); +/** Set the floating-point number's output format to fixed-point notation. + Returns false if input is NULL or `val` is not real type. + @see YYJSON_WRITE_FP_TO_FIXED flag. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_fp_to_fixed(yyjson_val *val, int prec); + +/** Set the floating-point number's output format to single-precision. + Returns false if input is NULL or `val` is not real type. + @see YYJSON_WRITE_FP_TO_FLOAT flag. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_fp_to_float(yyjson_val *val, bool flt); + /** Set the value to string (null-terminated). Returns false if input is NULL or `val` is object or array. @warning This will modify the `immutable` value, use with caution. */ @@ -1796,6 +1840,14 @@ yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str); yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, const char *str, size_t len); +/** Marks this string as not needing to be escaped during JSON writing. + This can be used to avoid the overhead of escaping if the string contains + only characters that do not require escaping. + Returns false if input is NULL or `val` is not string. + @see YYJSON_SUBTYPE_NOESC subtype. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_set_str_noesc(yyjson_val *val, bool noesc); + /*============================================================================== @@ -2353,11 +2405,35 @@ yyjson_api_inline bool yyjson_mut_set_sint(yyjson_mut_val *val, int64_t num); @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num); +/** Set the value to float. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_float(yyjson_mut_val *val, float num); + +/** Set the value to double. + Returns false if input is NULL. + @warning This function should not be used on an existing object or array. */ +yyjson_api_inline bool yyjson_mut_set_double(yyjson_mut_val *val, double num); + /** Set the value to real. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num); +/** Set the floating-point number's output format to fixed-point notation. + Returns false if input is NULL or `val` is not real type. + @see YYJSON_WRITE_FP_TO_FIXED flag. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_mut_set_fp_to_fixed(yyjson_mut_val *val, + int prec); + +/** Set the floating-point number's output format to single-precision. + Returns false if input is NULL or `val` is not real type. + @see YYJSON_WRITE_FP_TO_FLOAT flag. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_mut_set_fp_to_float(yyjson_mut_val *val, + bool flt); + /** Set the value to string (null-terminated). Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ @@ -2369,6 +2445,15 @@ yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, const char *str); yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, const char *str, size_t len); +/** Marks this string as not needing to be escaped during JSON writing. + This can be used to avoid the overhead of escaping if the string contains + only characters that do not require escaping. + Returns false if input is NULL or `val` is not string. + @see YYJSON_SUBTYPE_NOESC subtype. + @warning This will modify the `immutable` value, use with caution. */ +yyjson_api_inline bool yyjson_mut_set_str_noesc(yyjson_mut_val *val, + bool noesc); + /** Set the value to array. Returns false if input is NULL. @warning This function should not be used on an existing object or array. */ @@ -2440,7 +2525,15 @@ yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, int64_t num); -/** Creates and returns an real number value, returns NULL on error. */ +/** Creates and returns a float number value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_float(yyjson_mut_doc *doc, + float num); + +/** Creates and returns a double number value, returns NULL on error. */ +yyjson_api_inline yyjson_mut_val *yyjson_mut_double(yyjson_mut_doc *doc, + double num); + +/** Creates and returns a real number value, returns NULL on error. */ yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, double num); @@ -3155,7 +3248,7 @@ yyjson_api_inline bool yyjson_mut_arr_add_sint(yyjson_mut_doc *doc, int64_t num); /** - Adds a integer value at the end of the array. + Adds an integer value at the end of the array. @param doc The `doc` is only used for memory allocation. @param arr The array to which the value is to be inserted. Returns false if it is NULL or not an array. @@ -3166,6 +3259,30 @@ yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, yyjson_mut_val *arr, int64_t num); +/** + Adds a float value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_float(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + float num); + +/** + Adds a double value at the end of the array. + @param doc The `doc` is only used for memory allocation. + @param arr The array to which the value is to be inserted. + Returns false if it is NULL or not an array. + @param num The number to be added. + @return Whether successful. + */ +yyjson_api_inline bool yyjson_mut_arr_add_double(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num); + /** Adds a double value at the end of the array. @param doc The `doc` is only used for memory allocation. @@ -3681,10 +3798,30 @@ yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *key, int64_t val); +/** Adds a float value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_float(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, float val); + /** Adds a double value at the end of the object. The `key` should be a null-terminated UTF-8 string. This function allows duplicated key in one object. + @warning The key string is not copied, you should keep the string + unmodified for the lifetime of this JSON document. */ +yyjson_api_inline bool yyjson_mut_obj_add_double(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *key, double val); + +/** Adds a real value at the end of the object. + The `key` should be a null-terminated UTF-8 string. + This function allows duplicated key in one object. + @warning The key string is not copied, you should keep the string unmodified for the lifetime of this JSON document. */ yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, @@ -4606,6 +4743,19 @@ yyjson_api_inline bool unsafe_yyjson_is_str_noesc(const char *str, size_t len) { return false; } +yyjson_api_inline double unsafe_yyjson_u64_to_f64(uint64_t num) { +#if YYJSON_U64_TO_F64_NO_IMPL + uint64_t msb = ((uint64_t)1) << 63; + if ((num & msb) == 0) { + return (double)(int64_t)num; + } else { + return ((double)(int64_t)((num >> 1) | (num & 1))) * (double)2.0; + } +#else + return (double)num; +#endif +} + yyjson_api_inline yyjson_type unsafe_yyjson_get_type(void *val) { uint8_t tag = (uint8_t)((yyjson_val *)val)->tag; return (yyjson_type)(tag & YYJSON_TYPE_MASK); @@ -4723,17 +4873,7 @@ yyjson_api_inline double unsafe_yyjson_get_num(void *val) { } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT)) { return (double)((yyjson_val *)val)->uni.i64; } else if (tag == (YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT)) { -#if YYJSON_U64_TO_F64_NO_IMPL - uint64_t msb = ((uint64_t)1) << 63; - uint64_t num = ((yyjson_val *)val)->uni.u64; - if ((num & msb) == 0) { - return (double)(int64_t)num; - } else { - return ((double)(int64_t)((num >> 1) | (num & 1))) * (double)2.0; - } -#else - return (double)((yyjson_val *)val)->uni.u64; -#endif + return unsafe_yyjson_u64_to_f64(((yyjson_val *)val)->uni.u64); } return 0.0; } @@ -4781,6 +4921,14 @@ yyjson_api_inline void unsafe_yyjson_set_len(void *val, size_t len) { ((yyjson_val *)val)->tag = tag; } +yyjson_api_inline void unsafe_yyjson_set_tag(void *val, yyjson_type type, + yyjson_subtype subtype, + size_t len) { + uint64_t tag = (uint64_t)len << YYJSON_TAG_BIT; + tag |= (type | subtype); + ((yyjson_val *)val)->tag = tag; +} + yyjson_api_inline void unsafe_yyjson_inc_len(void *val) { uint64_t tag = ((yyjson_val *)val)->tag; tag += (uint64_t)(1 << YYJSON_TAG_BIT); @@ -4789,64 +4937,81 @@ yyjson_api_inline void unsafe_yyjson_inc_len(void *val) { yyjson_api_inline void unsafe_yyjson_set_raw(void *val, const char *raw, size_t len) { - unsafe_yyjson_set_type(val, YYJSON_TYPE_RAW, YYJSON_SUBTYPE_NONE); - unsafe_yyjson_set_len(val, len); + unsafe_yyjson_set_tag(val, YYJSON_TYPE_RAW, YYJSON_SUBTYPE_NONE, len); ((yyjson_val *)val)->uni.str = raw; } yyjson_api_inline void unsafe_yyjson_set_null(void *val) { - unsafe_yyjson_set_type(val, YYJSON_TYPE_NULL, YYJSON_SUBTYPE_NONE); - unsafe_yyjson_set_len(val, 0); + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NULL, YYJSON_SUBTYPE_NONE, 0); } yyjson_api_inline void unsafe_yyjson_set_bool(void *val, bool num) { yyjson_subtype subtype = num ? YYJSON_SUBTYPE_TRUE : YYJSON_SUBTYPE_FALSE; - unsafe_yyjson_set_type(val, YYJSON_TYPE_BOOL, subtype); - unsafe_yyjson_set_len(val, 0); + unsafe_yyjson_set_tag(val, YYJSON_TYPE_BOOL, subtype, 0); } yyjson_api_inline void unsafe_yyjson_set_uint(void *val, uint64_t num) { - unsafe_yyjson_set_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_UINT); - unsafe_yyjson_set_len(val, 0); + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_UINT, 0); ((yyjson_val *)val)->uni.u64 = num; } yyjson_api_inline void unsafe_yyjson_set_sint(void *val, int64_t num) { - unsafe_yyjson_set_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_SINT); - unsafe_yyjson_set_len(val, 0); + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_SINT, 0); ((yyjson_val *)val)->uni.i64 = num; } +yyjson_api_inline void unsafe_yyjson_set_fp_to_fixed(void *val, int prec) { + ((yyjson_val *)val)->tag &= ~((uint64_t)YYJSON_WRITE_FP_TO_FIXED(15) << 32); + ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_WRITE_FP_TO_FIXED(prec) << 32; +} + +yyjson_api_inline void unsafe_yyjson_set_fp_to_float(void *val, bool flt) { + uint64_t flag = (uint64_t)YYJSON_WRITE_FP_TO_FLOAT << 32; + if (flt) ((yyjson_val *)val)->tag |= flag; + else ((yyjson_val *)val)->tag &= ~flag; +} + +yyjson_api_inline void unsafe_yyjson_set_float(void *val, float num) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0); + ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_WRITE_FP_TO_FLOAT << 32; + ((yyjson_val *)val)->uni.f64 = num; +} + +yyjson_api_inline void unsafe_yyjson_set_double(void *val, double num) { + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0); + ((yyjson_val *)val)->uni.f64 = num; +} + yyjson_api_inline void unsafe_yyjson_set_real(void *val, double num) { - unsafe_yyjson_set_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL); - unsafe_yyjson_set_len(val, 0); + unsafe_yyjson_set_tag(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL, 0); ((yyjson_val *)val)->uni.f64 = num; } -yyjson_api_inline void unsafe_yyjson_set_str(void *val, const char *str) { - size_t len = strlen(str); - bool noesc = unsafe_yyjson_is_str_noesc(str, len); - yyjson_subtype sub = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; - unsafe_yyjson_set_type(val, YYJSON_TYPE_STR, sub); - unsafe_yyjson_set_len(val, len); - ((yyjson_val *)val)->uni.str = str; +yyjson_api_inline void unsafe_yyjson_set_str_noesc(void *val, bool noesc) { + ((yyjson_val *)val)->tag &= ~(uint64_t)YYJSON_SUBTYPE_MASK; + if (noesc) ((yyjson_val *)val)->tag |= (uint64_t)YYJSON_SUBTYPE_NOESC; } yyjson_api_inline void unsafe_yyjson_set_strn(void *val, const char *str, size_t len) { - unsafe_yyjson_set_type(val, YYJSON_TYPE_STR, YYJSON_SUBTYPE_NONE); - unsafe_yyjson_set_len(val, len); + unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, YYJSON_SUBTYPE_NONE, len); + ((yyjson_val *)val)->uni.str = str; +} + +yyjson_api_inline void unsafe_yyjson_set_str(void *val, const char *str) { + size_t len = strlen(str); + bool noesc = unsafe_yyjson_is_str_noesc(str, len); + yyjson_subtype subtype = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; + unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, subtype, len); ((yyjson_val *)val)->uni.str = str; } yyjson_api_inline void unsafe_yyjson_set_arr(void *val, size_t size) { - unsafe_yyjson_set_type(val, YYJSON_TYPE_ARR, YYJSON_SUBTYPE_NONE); - unsafe_yyjson_set_len(val, size); + unsafe_yyjson_set_tag(val, YYJSON_TYPE_ARR, YYJSON_SUBTYPE_NONE, size); } yyjson_api_inline void unsafe_yyjson_set_obj(void *val, size_t size) { - unsafe_yyjson_set_type(val, YYJSON_TYPE_OBJ, YYJSON_SUBTYPE_NONE); - unsafe_yyjson_set_len(val, size); + unsafe_yyjson_set_tag(val, YYJSON_TYPE_OBJ, YYJSON_SUBTYPE_NONE, size); } @@ -5070,12 +5235,36 @@ yyjson_api_inline bool yyjson_set_int(yyjson_val *val, int num) { return true; } +yyjson_api_inline bool yyjson_set_float(yyjson_val *val, float num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_float(val, num); + return true; +} + +yyjson_api_inline bool yyjson_set_double(yyjson_val *val, double num) { + if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; + unsafe_yyjson_set_double(val, num); + return true; +} + yyjson_api_inline bool yyjson_set_real(yyjson_val *val, double num) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; unsafe_yyjson_set_real(val, num); return true; } +yyjson_api_inline bool yyjson_set_fp_to_fixed(yyjson_val *val, int prec) { + if (yyjson_unlikely(!yyjson_is_real(val))) return false; + unsafe_yyjson_set_fp_to_fixed(val, prec); + return true; +} + +yyjson_api_inline bool yyjson_set_fp_to_float(yyjson_val *val, bool flt) { + if (yyjson_unlikely(!yyjson_is_real(val))) return false; + unsafe_yyjson_set_fp_to_float(val, flt); + return true; +} + yyjson_api_inline bool yyjson_set_str(yyjson_val *val, const char *str) { if (yyjson_unlikely(!val || unsafe_yyjson_is_ctn(val))) return false; if (yyjson_unlikely(!str)) return false; @@ -5091,6 +5280,12 @@ yyjson_api_inline bool yyjson_set_strn(yyjson_val *val, return true; } +yyjson_api_inline bool yyjson_set_str_noesc(yyjson_val *val, bool noesc) { + if (yyjson_unlikely(!yyjson_is_str(val))) return false; + unsafe_yyjson_set_str_noesc(val, noesc); + return true; +} + /*============================================================================== @@ -5593,12 +5788,38 @@ yyjson_api_inline bool yyjson_mut_set_int(yyjson_mut_val *val, int num) { return true; } +yyjson_api_inline bool yyjson_mut_set_float(yyjson_mut_val *val, float num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_float(val, num); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_double(yyjson_mut_val *val, double num) { + if (yyjson_unlikely(!val)) return false; + unsafe_yyjson_set_double(val, num); + return true; +} + yyjson_api_inline bool yyjson_mut_set_real(yyjson_mut_val *val, double num) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_real(val, num); return true; } +yyjson_api_inline bool yyjson_mut_set_fp_to_fixed(yyjson_mut_val *val, + int prec) { + if (yyjson_unlikely(!yyjson_mut_is_real(val))) return false; + unsafe_yyjson_set_fp_to_fixed(val, prec); + return true; +} + +yyjson_api_inline bool yyjson_mut_set_fp_to_float(yyjson_mut_val *val, + bool flt) { + if (yyjson_unlikely(!yyjson_mut_is_real(val))) return false; + unsafe_yyjson_set_fp_to_float(val, flt); + return true; +} + yyjson_api_inline bool yyjson_mut_set_str(yyjson_mut_val *val, const char *str) { if (yyjson_unlikely(!val || !str)) return false; @@ -5613,6 +5834,13 @@ yyjson_api_inline bool yyjson_mut_set_strn(yyjson_mut_val *val, return true; } +yyjson_api_inline bool yyjson_mut_set_str_noesc(yyjson_mut_val *val, + bool noesc) { + if (yyjson_unlikely(!yyjson_mut_is_str(val))) return false; + unsafe_yyjson_set_str_noesc(val, noesc); + return true; +} + yyjson_api_inline bool yyjson_mut_set_arr(yyjson_mut_val *val) { if (yyjson_unlikely(!val)) return false; unsafe_yyjson_set_arr(val, 0); @@ -5631,201 +5859,141 @@ yyjson_api_inline bool yyjson_mut_set_obj(yyjson_mut_val *val) { * Mutable JSON Value Creation API (Implementation) *============================================================================*/ +#define yyjson_mut_val_one(func) \ + if (yyjson_likely(doc)) { \ + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); \ + if (yyjson_likely(val)) { \ + func \ + return val; \ + } \ + } \ + return NULL + +#define yyjson_mut_val_one_str(func) \ + if (yyjson_likely(doc && str)) { \ + yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); \ + if (yyjson_likely(val)) { \ + func \ + return val; \ + } \ + } \ + return NULL + yyjson_api_inline yyjson_mut_val *yyjson_mut_raw(yyjson_mut_doc *doc, const char *str) { - if (yyjson_likely(str)) return yyjson_mut_rawn(doc, str, strlen(str)); - return NULL; + yyjson_mut_val_one_str({ unsafe_yyjson_set_raw(val, str, strlen(str)); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_rawn(yyjson_mut_doc *doc, const char *str, size_t len) { - if (yyjson_likely(doc && str)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; - val->uni.str = str; - return val; - } - } - return NULL; + yyjson_mut_val_one_str({ unsafe_yyjson_set_raw(val, str, len); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_rawcpy(yyjson_mut_doc *doc, const char *str) { - if (yyjson_likely(str)) return yyjson_mut_rawncpy(doc, str, strlen(str)); - return NULL; + yyjson_mut_val_one_str({ + size_t len = strlen(str); + char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_raw(val, new_str, len); + }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_rawncpy(yyjson_mut_doc *doc, const char *str, size_t len) { - if (yyjson_likely(doc && str)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + yyjson_mut_val_one_str({ char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); - if (yyjson_likely(val && new_str)) { - val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_RAW; - val->uni.str = new_str; - return val; - } - } - return NULL; + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_raw(val, new_str, len); + }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_null(yyjson_mut_doc *doc) { - if (yyjson_likely(doc)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - val->tag = YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE; - return val; - } - } - return NULL; + yyjson_mut_val_one({ unsafe_yyjson_set_null(val); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_true(yyjson_mut_doc *doc) { - if (yyjson_likely(doc)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; - return val; - } - } - return NULL; + yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, true); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_false(yyjson_mut_doc *doc) { - if (yyjson_likely(doc)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; - return val; - } - } - return NULL; + yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, false); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_bool(yyjson_mut_doc *doc, bool _val) { - if (yyjson_likely(doc)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - _val = !!_val; - val->tag = YYJSON_TYPE_BOOL | (uint8_t)((uint8_t)_val << 3); - return val; - } - } - return NULL; + yyjson_mut_val_one({ unsafe_yyjson_set_bool(val, _val); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_uint(yyjson_mut_doc *doc, uint64_t num) { - if (yyjson_likely(doc)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; - val->uni.u64 = num; - return val; - } - } - return NULL; + yyjson_mut_val_one({ unsafe_yyjson_set_uint(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_sint(yyjson_mut_doc *doc, int64_t num) { - if (yyjson_likely(doc)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; - val->uni.i64 = num; - return val; - } - } - return NULL; + yyjson_mut_val_one({ unsafe_yyjson_set_sint(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_int(yyjson_mut_doc *doc, int64_t num) { - return yyjson_mut_sint(doc, num); + yyjson_mut_val_one({ unsafe_yyjson_set_sint(val, num); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_float(yyjson_mut_doc *doc, + float num) { + yyjson_mut_val_one({ unsafe_yyjson_set_float(val, num); }); +} + +yyjson_api_inline yyjson_mut_val *yyjson_mut_double(yyjson_mut_doc *doc, + double num) { + yyjson_mut_val_one({ unsafe_yyjson_set_double(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_real(yyjson_mut_doc *doc, double num) { - if (yyjson_likely(doc)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; - val->uni.f64 = num; - return val; - } - } - return NULL; + yyjson_mut_val_one({ unsafe_yyjson_set_real(val, num); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_str(yyjson_mut_doc *doc, const char *str) { - if (yyjson_likely(doc && str)) { - size_t len = strlen(str); - bool noesc = unsafe_yyjson_is_str_noesc(str, len); - yyjson_subtype sub = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | - (uint64_t)(YYJSON_TYPE_STR | sub); - val->uni.str = str; - return val; - } - } - return NULL; + yyjson_mut_val_one_str({ unsafe_yyjson_set_str(val, str); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_strn(yyjson_mut_doc *doc, const char *str, size_t len) { - if (yyjson_likely(doc && str)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); - if (yyjson_likely(val)) { - val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; - val->uni.str = str; - return val; - } - } - return NULL; + yyjson_mut_val_one_str({ unsafe_yyjson_set_strn(val, str, len); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_strcpy(yyjson_mut_doc *doc, const char *str) { - if (yyjson_likely(doc && str)) { + yyjson_mut_val_one_str({ size_t len = strlen(str); bool noesc = unsafe_yyjson_is_str_noesc(str, len); yyjson_subtype sub = noesc ? YYJSON_SUBTYPE_NOESC : YYJSON_SUBTYPE_NONE; - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); - if (yyjson_likely(val && new_str)) { - val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | - (uint64_t)(YYJSON_TYPE_STR | sub); - val->uni.str = new_str; - return val; - } - } - return NULL; + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_tag(val, YYJSON_TYPE_STR, sub, len); + val->uni.str = new_str; + }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_strncpy(yyjson_mut_doc *doc, const char *str, size_t len) { - if (yyjson_likely(doc && str)) { - yyjson_mut_val *val = unsafe_yyjson_mut_val(doc, 1); + yyjson_mut_val_one_str({ char *new_str = unsafe_yyjson_mut_strncpy(doc, str, len); - if (yyjson_likely(val && new_str)) { - val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; - val->uni.str = new_str; - return val; - } - } - return NULL; + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_strn(val, new_str, len); + }); } +#undef yyjson_mut_val_one +#undef yyjson_mut_val_one_str + /*============================================================================== @@ -5963,8 +6131,7 @@ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr(yyjson_mut_doc *doc) { yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_bool( yyjson_mut_doc *doc, const bool *vals, size_t count) { yyjson_mut_arr_with_func({ - bool _val = !!vals[i]; - val->tag = YYJSON_TYPE_BOOL | (uint8_t)((uint8_t)_val << 3); + unsafe_yyjson_set_bool(val, vals[i]); }); } @@ -5980,96 +6147,86 @@ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint( yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_real( yyjson_mut_doc *doc, const double *vals, size_t count) { - return yyjson_mut_arr_with_double(doc, vals, count); + yyjson_mut_arr_with_func({ + unsafe_yyjson_set_real(val, vals[i]); + }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint8( yyjson_mut_doc *doc, const int8_t *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; - val->uni.i64 = (int64_t)vals[i]; + unsafe_yyjson_set_sint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint16( yyjson_mut_doc *doc, const int16_t *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; - val->uni.i64 = vals[i]; + unsafe_yyjson_set_sint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint32( yyjson_mut_doc *doc, const int32_t *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; - val->uni.i64 = vals[i]; + unsafe_yyjson_set_sint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_sint64( yyjson_mut_doc *doc, const int64_t *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; - val->uni.i64 = vals[i]; + unsafe_yyjson_set_sint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint8( yyjson_mut_doc *doc, const uint8_t *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; - val->uni.u64 = vals[i]; + unsafe_yyjson_set_uint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint16( yyjson_mut_doc *doc, const uint16_t *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; - val->uni.u64 = vals[i]; + unsafe_yyjson_set_uint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint32( yyjson_mut_doc *doc, const uint32_t *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; - val->uni.u64 = vals[i]; + unsafe_yyjson_set_uint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_uint64( yyjson_mut_doc *doc, const uint64_t *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; - val->uni.u64 = vals[i]; + unsafe_yyjson_set_uint(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_float( yyjson_mut_doc *doc, const float *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; - val->uni.f64 = (double)vals[i]; + unsafe_yyjson_set_float(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_double( yyjson_mut_doc *doc, const double *vals, size_t count) { yyjson_mut_arr_with_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; - val->uni.f64 = vals[i]; + unsafe_yyjson_set_double(val, vals[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_str( yyjson_mut_doc *doc, const char **vals, size_t count) { yyjson_mut_arr_with_func({ - uint64_t len = (uint64_t)strlen(vals[i]); - val->tag = (len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; - val->uni.str = vals[i]; - if (yyjson_unlikely(!val->uni.str)) return NULL; + if (yyjson_unlikely(!vals[i])) return NULL; + unsafe_yyjson_set_str(val, vals[i]); }); } @@ -6077,37 +6234,37 @@ yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strn( yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { if (yyjson_unlikely(count > 0 && !lens)) return NULL; yyjson_mut_arr_with_func({ - val->tag = ((uint64_t)lens[i] << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; - val->uni.str = vals[i]; - if (yyjson_unlikely(!val->uni.str)) return NULL; + if (yyjson_unlikely(!vals[i])) return NULL; + unsafe_yyjson_set_strn(val, vals[i], lens[i]); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strcpy( yyjson_mut_doc *doc, const char **vals, size_t count) { size_t len; - const char *str; + const char *str, *new_str; yyjson_mut_arr_with_func({ str = vals[i]; - if (!str) return NULL; + if (yyjson_unlikely(!str)) return NULL; len = strlen(str); - val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; - val->uni.str = unsafe_yyjson_mut_strncpy(doc, str, len); - if (yyjson_unlikely(!val->uni.str)) return NULL; + new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_strn(val, new_str, len); }); } yyjson_api_inline yyjson_mut_val *yyjson_mut_arr_with_strncpy( yyjson_mut_doc *doc, const char **vals, const size_t *lens, size_t count) { size_t len; - const char *str; + const char *str, *new_str; if (yyjson_unlikely(count > 0 && !lens)) return NULL; yyjson_mut_arr_with_func({ str = vals[i]; + if (yyjson_unlikely(!str)) return NULL; len = lens[i]; - val->tag = ((uint64_t)len << YYJSON_TAG_BIT) | YYJSON_TYPE_STR; - val->uni.str = unsafe_yyjson_mut_strncpy(doc, str, len); - if (yyjson_unlikely(!val->uni.str)) return NULL; + new_str = unsafe_yyjson_mut_strncpy(doc, str, len); + if (yyjson_unlikely(!new_str)) return NULL; + unsafe_yyjson_set_strn(val, new_str, len); }); } @@ -6401,6 +6558,26 @@ yyjson_api_inline bool yyjson_mut_arr_add_int(yyjson_mut_doc *doc, return false; } +yyjson_api_inline bool yyjson_mut_arr_add_float(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + float num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_float(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + +yyjson_api_inline bool yyjson_mut_arr_add_double(yyjson_mut_doc *doc, + yyjson_mut_val *arr, + double num) { + if (yyjson_likely(doc && yyjson_mut_is_arr(arr))) { + yyjson_mut_val *val = yyjson_mut_double(doc, num); + return yyjson_mut_arr_append(arr, val); + } + return false; +} + yyjson_api_inline bool yyjson_mut_arr_add_real(yyjson_mut_doc *doc, yyjson_mut_val *arr, double num) { @@ -6890,75 +7067,68 @@ yyjson_api_inline bool yyjson_mut_obj_rotate(yyjson_mut_val *obj, yyjson_api_inline bool yyjson_mut_obj_add_null(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key) { - yyjson_mut_obj_add_func({ - val->tag = YYJSON_TYPE_NULL | YYJSON_SUBTYPE_NONE; - }); + yyjson_mut_obj_add_func({ unsafe_yyjson_set_null(val); }); } yyjson_api_inline bool yyjson_mut_obj_add_true(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key) { - yyjson_mut_obj_add_func({ - val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_TRUE; - }); + yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, true); }); } yyjson_api_inline bool yyjson_mut_obj_add_false(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key) { - yyjson_mut_obj_add_func({ - val->tag = YYJSON_TYPE_BOOL | YYJSON_SUBTYPE_FALSE; - }); + yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, false); }); } yyjson_api_inline bool yyjson_mut_obj_add_bool(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, bool _val) { - yyjson_mut_obj_add_func({ - _val = !!_val; - val->tag = YYJSON_TYPE_BOOL | (uint8_t)((uint8_t)(_val) << 3); - }); + yyjson_mut_obj_add_func({ unsafe_yyjson_set_bool(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_uint(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, uint64_t _val) { - yyjson_mut_obj_add_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_UINT; - val->uni.u64 = _val; - }); + yyjson_mut_obj_add_func({ unsafe_yyjson_set_uint(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_sint(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, int64_t _val) { - yyjson_mut_obj_add_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; - val->uni.i64 = _val; - }); + yyjson_mut_obj_add_func({ unsafe_yyjson_set_sint(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_int(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, int64_t _val) { - yyjson_mut_obj_add_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_SINT; - val->uni.i64 = _val; - }); + yyjson_mut_obj_add_func({ unsafe_yyjson_set_sint(val, _val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_float(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + float _val) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_float(val, _val); }); +} + +yyjson_api_inline bool yyjson_mut_obj_add_double(yyjson_mut_doc *doc, + yyjson_mut_val *obj, + const char *_key, + double _val) { + yyjson_mut_obj_add_func({ unsafe_yyjson_set_double(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_real(yyjson_mut_doc *doc, yyjson_mut_val *obj, const char *_key, double _val) { - yyjson_mut_obj_add_func({ - val->tag = YYJSON_TYPE_NUM | YYJSON_SUBTYPE_REAL; - val->uni.f64 = _val; - }); + yyjson_mut_obj_add_func({ unsafe_yyjson_set_real(val, _val); }); } yyjson_api_inline bool yyjson_mut_obj_add_str(yyjson_mut_doc *doc, diff --git a/test/data/num/nan_inf_literal_fail.txt b/test/data/num/literal_fail.txt similarity index 100% rename from test/data/num/nan_inf_literal_fail.txt rename to test/data/num/literal_fail.txt diff --git a/test/data/num/nan_inf_literal_pass.txt b/test/data/num/literal_pass.txt similarity index 59% rename from test/data/num/nan_inf_literal_pass.txt rename to test/data/num/literal_pass.txt index 79e0053..f723aee 100644 --- a/test/data/num/nan_inf_literal_pass.txt +++ b/test/data/num/literal_pass.txt @@ -1,4 +1,5 @@ -# NaN and Infinity +# ============================================================================== +# NaN and Infinity literals NAN NaN diff --git a/test/data/num/num_overflow.txt b/test/data/num/num_overflow.txt new file mode 100644 index 0000000..062b928 --- /dev/null +++ b/test/data/num/num_overflow.txt @@ -0,0 +1,14 @@ +# ============================================================================== + +# Integer to double overflow +1797693134862315708145274237317043567980705675258449965989174768031572607800285387605895586327668781715404589535143824642343213268894641827684675467035375169860499105765512820762454900903893289440758685084551339423045832369032229481658085593321233482747978262041447231687381771809192998812504040261841248583680 +1000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +179769313486231580814527423731704356798070567525844996598917476803157260780028538760589558632766878171540458953514382464234321326889464182768467546703537516986049910576551282076245490090389328944075868508455133942304583236903222948165808559332123348274797826204144723168738177180919299881250404026184124858368 +200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +9999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 + +# Real number overflow +1.8e308 +1.8e999 +1.8e999999999 +1.8e9999999999999999999999999999999 diff --git a/test/data/num/real_pass_1.txt b/test/data/num/real_pass_1.txt index 05b10e6..d3c12aa 100644 --- a/test/data/num/real_pass_1.txt +++ b/test/data/num/real_pass_1.txt @@ -1,27 +1,3 @@ -# ============================================================================== -# INT64 to double - -# -0x8000000000000400 --9223372036854776832 -# -0x8000000000000400 - 1 --9223372036854776831 -# -0x8000000000000400 + 1 --9223372036854776833 - -# -0xFFFFFFFFFFFFFC00 --18446744073709550592 -# -0xFFFFFFFFFFFFFC00 - 1 --18446744073709550591 -# -0xFFFFFFFFFFFFFC00 + 1 --18446744073709550593 - -# -0xFFFFFFFFFFFFF400 --18446744073709548544 -# -0xFFFFFFFFFFFFF3FF - 1 --18446744073709548543 -# -0xFFFFFFFFFFFFF401 + 1 --18446744073709548545 - # ============================================================================== # Zero (0e?) @@ -289,6 +265,13 @@ -0.00000000000000000000000e+999999999999999999999999 -0.00000000000000000000000E+999999999999999999999999 +# ============================================================================== +# Zero (smaller than subnormal) +2e-324 +1.0e-324 +4.9406564584124654e-325 +123e-999999999999999999999999 + # ============================================================================== # Limits diff --git a/test/data/num/sint_bignum.txt b/test/data/num/sint_bignum.txt index 4ea2dd8..1c2603e 100644 --- a/test/data/num/sint_bignum.txt +++ b/test/data/num/sint_bignum.txt @@ -1,12 +1,48 @@ -# INT64_MIN -- +# ============================================================================== +# INT64_MIN -0, -1, -2, -3, -4, ... -9223372036854775809 -9223372036854775810 -9223372036854775811 -9223372036854775812 -9223372036854775813 +-9223372036854775814 +-9223372036854775815 +-9223372036854775816 +-9223372036854775817 +-9223372036854775818 +-9223372036854775819 +-9223372036854775820 +-9223372036854775821 -# others +# ============================================================================== +# Others +-92233720368547758079 +-92233720368547758080 +-92233720368547758081 -10000000000000000000 -99999999999999999999 -1000000000000000000000000000000000000000000000000000000000000000000000000000000 -9999999999999999999999999999999999999999999999999999999999999999999999999999999 + +# ============================================================================== +# Special +# -0x8000000000000400 +-9223372036854776832 +# -0x8000000000000400 - 1 +-9223372036854776831 +# -0x8000000000000400 + 1 +-9223372036854776833 + +# -0xFFFFFFFFFFFFFC00 +-18446744073709550592 +# -0xFFFFFFFFFFFFFC00 - 1 +-18446744073709550591 +# -0xFFFFFFFFFFFFFC00 + 1 +-18446744073709550593 + +# -0xFFFFFFFFFFFFF400 +-18446744073709548544 +# -0xFFFFFFFFFFFFF3FF - 1 +-18446744073709548543 +# -0xFFFFFFFFFFFFF401 + 1 +-18446744073709548545 diff --git a/test/data/num/sint_pass.txt b/test/data/num/sint_pass.txt index 26b165b..dcf267f 100644 --- a/test/data/num/sint_pass.txt +++ b/test/data/num/sint_pass.txt @@ -1,7 +1,9 @@ +# ============================================================================== # Zero -0 -# INT64_MIN -- +# ============================================================================== +# INT64_MIN +0, +1, +2, +3, +4, ... -9223372036854775808 -9223372036854775807 -9223372036854775806 @@ -10,8 +12,10 @@ -9223372036854775803 -9223372036854775802 -9223372036854775801 --9223372036854775800 +-9223372036854775799 +-9223372036854775798 +# ============================================================================== # -m^n -1 -2 diff --git a/test/data/num/uint_bignum.txt b/test/data/num/uint_bignum.txt index 23c6c12..614d528 100644 --- a/test/data/num/uint_bignum.txt +++ b/test/data/num/uint_bignum.txt @@ -1,12 +1,25 @@ -# UINT64_MAX ++ +# ============================================================================== +# UINT64_MAX +1, +2, +3, +4, +5, ... 18446744073709551616 18446744073709551617 18446744073709551618 18446744073709551619 18446744073709551620 +18446744073709551621 +18446744073709551622 +18446744073709551623 +18446744073709551624 +18446744073709551625 +18446744073709551626 -# others +# ============================================================================== +# Others +184467440737095516149 +184467440737095516150 +184467440737095516151 +19000000000000000000 20000000000000000000 99999999999999999999 +1900000000000000000000000000000000000000000000000000000000000000000000000000000 2000000000000000000000000000000000000000000000000000000000000000000000000000000 9999999999999999999999999999999999999999999999999999999999999999999999999999999 diff --git a/test/data/num/uint_pass.txt b/test/data/num/uint_pass.txt index 4ef7363..4250067 100644 --- a/test/data/num/uint_pass.txt +++ b/test/data/num/uint_pass.txt @@ -1,9 +1,10 @@ +# ============================================================================== # Zero 0 -# INT64_MAX -- ++ +# ============================================================================== +# INT64_MAX -0, -1, -2, -3, -4, ... 9223372036854775807 - 9223372036854775806 9223372036854775805 9223372036854775804 @@ -11,7 +12,12 @@ 9223372036854775802 9223372036854775801 9223372036854775800 +9223372036854775799 +9223372036854775798 +9223372036854775797 +# INT64_MAX +1, +2, +3, +4, ... +9223372036854775808 9223372036854775809 9223372036854775810 9223372036854775811 @@ -19,8 +25,13 @@ 9223372036854775813 9223372036854775814 9223372036854775815 +9223372036854775816 +9223372036854775817 +9223372036854775818 +9223372036854775819 -# UINT64_MAX -- +# ============================================================================== +# UINT64_MAX -0, -1, -2, -3, -4, ... 18446744073709551615 18446744073709551614 18446744073709551613 @@ -28,7 +39,12 @@ 18446744073709551611 18446744073709551610 18446744073709551609 +18446744073709551608 +18446744073709551607 +18446744073709551606 +18446744073709551605 +# ============================================================================== # m^n 1 2 diff --git a/test/test_json_mut_val.c b/test/test_json_mut_val.c index a9f90b8..91cd09c 100644 --- a/test/test_json_mut_val.c +++ b/test/test_json_mut_val.c @@ -174,6 +174,28 @@ static void test_json_mut_val_api(void) { yy_assert(yyjson_mut_get_real(val) == (f64)0); yy_assert(yyjson_mut_get_num(val) == (f64)-123); + yy_assert(yyjson_mut_float(NULL, (f32)123.0) == NULL); + val = yyjson_mut_float(doc, (f32)123.0); + yy_assert(validate_mut_val_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL)); + yy_assert((val->tag >> 32) == YYJSON_WRITE_FP_TO_FLOAT); + yy_assert(strcmp(yyjson_mut_get_type_desc(val), "real") == 0); + yy_assert(yyjson_mut_get_uint(val) == (u64)0); + yy_assert(yyjson_mut_get_sint(val) == (i64)0); + yy_assert(yyjson_mut_get_int(val) == (i64)0); + yy_assert((f32)yyjson_mut_get_real(val) == (f32)123.0); + yy_assert((f32)yyjson_mut_get_num(val) == (f32)123.0); + + yy_assert(yyjson_mut_double(NULL, 123.0) == NULL); + val = yyjson_mut_double(doc, 123.0); + yy_assert(validate_mut_val_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL)); + yy_assert((val->tag >> 32) == 0); + yy_assert(strcmp(yyjson_mut_get_type_desc(val), "real") == 0); + yy_assert(yyjson_mut_get_uint(val) == (u64)0); + yy_assert(yyjson_mut_get_sint(val) == (i64)0); + yy_assert(yyjson_mut_get_int(val) == (i64)0); + yy_assert(yyjson_mut_get_real(val) == (f64)123.0); + yy_assert(yyjson_mut_get_num(val) == (f64)123.0); + yy_assert(yyjson_mut_real(NULL, 123.0) == NULL); val = yyjson_mut_real(doc, 123.0); yy_assert(validate_mut_val_type(val, YYJSON_TYPE_NUM, YYJSON_SUBTYPE_REAL)); @@ -811,6 +833,18 @@ static void test_json_mut_arr_api(void) { val = yyjson_mut_arr_get_last(arr); yy_assert(yyjson_mut_get_int(val) == -12); + yy_assert(!yyjson_mut_arr_add_float(NULL, arr, (float)-20.0)); + yy_assert(!yyjson_mut_arr_add_float(doc, NULL, (float)-20.0)); + yy_assert(yyjson_mut_arr_add_float(doc, arr, (float)-20.0)); + val = yyjson_mut_arr_get_last(arr); + yy_assert((float)yyjson_mut_get_real(val) == (float)-20.0); + + yy_assert(!yyjson_mut_arr_add_double(NULL, arr, -20.0)); + yy_assert(!yyjson_mut_arr_add_double(doc, NULL, -20.0)); + yy_assert(yyjson_mut_arr_add_double(doc, arr, -20.0)); + val = yyjson_mut_arr_get_last(arr); + yy_assert(yyjson_mut_get_real(val) == -20.0); + yy_assert(!yyjson_mut_arr_add_real(NULL, arr, -20.0)); yy_assert(!yyjson_mut_arr_add_real(doc, NULL, -20.0)); yy_assert(yyjson_mut_arr_add_real(doc, arr, -20.0)); @@ -1632,6 +1666,14 @@ static void test_json_mut_obj_api(void) { val = yyjson_mut_obj_get(obj, "g"); yy_assert(yyjson_mut_get_int(val) == -456); + yy_assert(yyjson_mut_obj_add_float(doc, obj, "h", (float)789.0)); + val = yyjson_mut_obj_get(obj, "h"); + yy_assert((float)yyjson_mut_get_real(val) == (float)789.0); + + yy_assert(yyjson_mut_obj_add_double(doc, obj, "h", 789.0)); + val = yyjson_mut_obj_get(obj, "h"); + yy_assert(yyjson_mut_get_real(val) == 789.0); + yy_assert(yyjson_mut_obj_add_real(doc, obj, "h", 789.0)); val = yyjson_mut_obj_get(obj, "h"); yy_assert(yyjson_mut_get_real(val) == 789.0); diff --git a/test/test_json_reader.c b/test/test_json_reader.c index a6d69b4..aba9f56 100644 --- a/test/test_json_reader.c +++ b/test/test_json_reader.c @@ -223,8 +223,10 @@ static void test_json_parsing(void) { for (int i = 0; i < count; i++) { char *name = names[i]; + if (*name == '.') continue; char path[YY_MAX_PATH]; yy_path_combine(path, dir, name, NULL); + if (yy_str_has_prefix(name, "y_")) { test_read_file(path, FLAG_NONE, EXPECT_PASS); } else if (yy_str_has_prefix(name, "n_")) { @@ -244,48 +246,17 @@ static void test_json_transform(void) { char **names = yy_dir_read(dir, &count); yy_assertf(names != NULL && count != 0, "read dir fail:%s\n", dir); - const char *pass_names[] = { - "number_1.0.json", - "number_1.000000000000000005.json", - "number_1e-999.json", - "number_1e6.json", - "number_1000000000000000.json", - "number_10000000000000000999.json", - "number_-9223372036854775808.json", - "number_-9223372036854775809.json", - "number_9223372036854775807.json", - "number_9223372036854775808.json", - "object_key_nfc_nfd.json", - "object_key_nfd_nfc.json", - "object_same_key_different_values.json", - "object_same_key_same_value.json", - "object_same_key_unclear_values.json", - "string_with_escaped_NULL.json" - }; - - const char *fail_names[] = { - "string_1_escaped_invalid_codepoint.json", - "string_1_invalid_codepoint.json", - "string_2_escaped_invalid_codepoints.json", - "string_2_invalid_codepoints.json", - "string_3_escaped_invalid_codepoints.json", - "string_3_invalid_codepoints.json" - }; - for (int i = 0; i < count; i++) { char *name = names[i]; + if (*name == '.') continue; char path[YY_MAX_PATH]; + yy_path_combine(path, dir, name, NULL); - expect_type expect = EXPECT_NONE; - for (usize p = 0; p < sizeof(pass_names) / sizeof(pass_names[0]); p++) { - if (strcmp(name, pass_names[p]) == 0) expect = EXPECT_PASS; - } - for (usize p = 0; p < sizeof(fail_names) / sizeof(fail_names[0]); p++) { - if (strcmp(name, fail_names[p]) == 0) expect = EXPECT_FAIL; + if (yy_str_contains(name, "invalid")) { + test_read_file(path, FLAG_NONE, EXPECT_FAIL); + } else { + test_read_file(path, FLAG_NONE, EXPECT_PASS); } - - yy_path_combine(path, dir, name, NULL); - test_read_file(path, FLAG_NONE, expect); } yy_dir_free(names); @@ -301,8 +272,10 @@ static void test_json_encoding(void) { for (int i = 0; i < count; i++) { char *name = names[i]; + if (*name == '.') continue; char path[YY_MAX_PATH]; yy_path_combine(path, dir, name, NULL); + if (strcmp(name, "utf8.json") == 0) { test_read_file(path, FLAG_NONE, EXPECT_PASS); } else { diff --git a/test/test_json_writer.c b/test/test_json_writer.c index acaf284..a9f636b 100644 --- a/test/test_json_writer.c +++ b/test/test_json_writer.c @@ -857,6 +857,16 @@ yy_test_case(test_json_writer) { yy_assert(strcmp(ret, "[\"abc\"]") == 0); free(ret); + yyjson_set_str(val, "abc\n"); + yyjson_set_str_noesc(val, true); + ret = yyjson_write(doc, 0, NULL); + yy_assert(strcmp(ret, "[\"abc\n\"]") == 0); + free(ret); + yyjson_set_str_noesc(val, false); + ret = yyjson_write(doc, 0, NULL); + yy_assert(strcmp(ret, "[\"abc\\n\"]") == 0); + free(ret); + yyjson_set_strn(val, "abcd", 3); ret = yyjson_write(doc, 0, NULL); yy_assert(strcmp(ret, "[\"abc\"]") == 0); diff --git a/test/test_number.c b/test/test_number.c index 3a682c6..dbc4500 100644 --- a/test/test_number.c +++ b/test/test_number.c @@ -8,813 +8,840 @@ #include "goo_double_conv.h" #include -#if defined(__clang__) -# pragma clang diagnostic push -# pragma clang diagnostic ignored "-Wformat" -#elif defined(__GNUC__) -# pragma GCC diagnostic push -# pragma GCC diagnostic ignored "-Wformat" -#endif +#if !YYJSON_DISABLE_READER && !YYJSON_DISABLE_WRITER +// IEEE 754 binary floating-point format is used in the current environment. +#define FP_IEEE_754 GOO_HAS_IEEE_754 +// Whether yyjson use libc's strtod/sprintf instead of its built-in functions. +#define FP_USE_LIBC (!FP_IEEE_754 || YYJSON_DISABLE_FAST_FP_CONV) -#if !YYJSON_DISABLE_READER && !YYJSON_DISABLE_WRITER +// The decimal precision required to serialize/deserialize a floating-point value. +#ifndef FLT_DECIMAL_DIG +#define FLT_DECIMAL_DIG 9 +#endif +#ifndef DBL_DECIMAL_DIG +#define DBL_DECIMAL_DIG 17 +#endif -/*============================================================================== - * Number converter - *============================================================================*/ -typedef enum { - NUM_TYPE_FAIL, - NUM_TYPE_SINT, - NUM_TYPE_UINT, - NUM_TYPE_REAL, - NUM_TYPE_INF_NAN_LITERAL, -} num_type; +// ============================================================================= +// Number conversion helper +// ============================================================================= -/// Convert double to raw. -static yy_inline u64 f64_to_u64_raw(f64 f) { - u64 u; - memcpy((void *)&u, (void *)&f, sizeof(u64)); - return u; +/// Convert raw to float. +static yy_inline f32 f32_from_raw(u32 u) { + f32 f; + memcpy((void *)&f, (void *)&u, sizeof(u)); + return f; } /// Convert raw to double. -static yy_inline f64 f64_from_u64_raw(u64 u) { +static yy_inline f64 f64_from_raw(u64 u) { f64 f; - memcpy((void *)&f, (void *)&u, sizeof(u64)); + memcpy((void *)&f, (void *)&u, sizeof(u)); return f; } -/// Get a random finite double number. -static yy_inline f64 rand_f64(void) { - while (true) { - u64 u = yy_rand_u64(); - f64 f = f64_from_u64_raw(u); - if (isfinite(f)) return f; - }; +/// Current locale decimal point. +static char locale_decimal_point = '.'; + +/// Update locale decimal point. +static void update_locale_decimal_point(void) { + struct lconv *conv = localeconv(); + char c = conv->decimal_point[0]; + yy_assertf(c && conv->decimal_point[1] == '\0', + "locale decimal point is invalid: %s\n", conv->decimal_point); + locale_decimal_point = c; } -/// Whether this character is digit. -static yy_inline bool char_is_digit(char c) { - return '0' <= c && c <= '9'; +/// Replace decimal point to current locale. +static void decimal_point_to_locale(char *buf) { + char *ptr = strchr(buf, '.'); + if (ptr) *ptr = locale_decimal_point; } -/// Get the number of significant digit from a floating-point number string. -static yy_inline int str_get_sig_len(const char *str) { - const char *cur = str, *dot = NULL, *hdr = NULL, *end = NULL; - for (; *cur && *cur != 'e' && *cur != 'E' ; cur++) { - if (*cur == '.') dot = cur; - else if (char_is_digit(*cur)) { - if (!hdr) hdr = cur; - end = cur; - } - } - if (!hdr) return 0; - return (int)((end - hdr + 1) - (hdr < dot && dot < end)); +/// Reset decimal point to default minimal "C" locale. +static void decimal_point_to_std(char *buf) { + char *ptr = strchr(buf, locale_decimal_point); + if (ptr) *ptr = '.'; } -/// Check JSON number format and return type (FAIL/SINT/UINT/REAL). -static yy_inline num_type check_json_num(const char *str) { - bool sign = (*str == '-'); - str += sign; - if (!char_is_digit(*str)) { - if (!yy_str_cmp(str, "nan", true) || - !yy_str_cmp(str, "inf", true) || - !yy_str_cmp(str, "infinity", true)) return NUM_TYPE_INF_NAN_LITERAL; - return NUM_TYPE_FAIL; - } - if (*str == '0' && char_is_digit(str[1])) return NUM_TYPE_FAIL; - while (char_is_digit(*str)) str++; - if (*str == '\0') return sign ? NUM_TYPE_SINT : NUM_TYPE_UINT; - if (*str == '.') { - str++; - if (!char_is_digit(*str)) return NUM_TYPE_FAIL; - while (char_is_digit(*str)) str++; + + +// ============================================================================= +// Number conversion (libc) +// ============================================================================= + +/// Read float from string (libc). +static usize libc_f32_read(const char *str, f32 *val) { + bool has_locale = (locale_decimal_point != '.'); + if (has_locale) { + char *dup = yy_str_copy(str); + decimal_point_to_locale(dup); + str = dup; } - if (*str == 'e' || *str == 'E') { - str++; - if (*str == '-' || *str == '+') str++; - if (!char_is_digit(*str)) return NUM_TYPE_FAIL; - while (char_is_digit(*str)) str++; + char *end = NULL; + *val = strtof(str, &end); + usize len = end ? (end - str) : 0; + if (has_locale) free((void *)str); + return len; +} + +/// Read double from string (libc). +static usize libc_f64_read(const char *str, f64 *val) { + bool has_locale = (locale_decimal_point != '.'); + if (has_locale) { + char *dup = yy_str_copy(str); + decimal_point_to_locale(dup); + str = dup; } - if (*str == '\0') return NUM_TYPE_REAL; - return NUM_TYPE_FAIL; + char *end = NULL; + *val = strtod(str, &end); + usize len = end ? (end - str) : 0; + if (has_locale) free((void *)str); + return len; } +/// Write float to string shortest (libc). +static usize libc_f32_write(f32 val, char *buf, usize len) { + if (isinf(val)) return snprintf(buf, len, (val > 0) ? "Infinity" : "-Infinity"); + if (isnan(val)) return snprintf(buf, len, "NaN"); + int out_len = snprintf(buf, len, "%.*g", FLT_DECIMAL_DIG, val); + if (locale_decimal_point != '.') decimal_point_to_std(buf); + return (out_len >= (int)len) ? 0 : out_len; +} +/// Write double to string shortest (libc). +static usize libc_f64_write(f64 val, char *buf, usize len) { + if (isinf(val)) return snprintf(buf, len, (val > 0) ? "Infinity" : "-Infinity"); + if (isnan(val)) return snprintf(buf, len, "NaN"); + int out_len = snprintf(buf, len, "%.*g", DBL_DECIMAL_DIG, val); + if (locale_decimal_point != '.') decimal_point_to_std(buf); + return (out_len >= (int)len) ? 0 : out_len; +} -#if !YYJSON_DISABLE_FAST_FP_CONV && GOO_HAS_IEEE_754 +/// Write double to string with fixed-point notation (libc). +static usize libc_f64_write_fixed(f64 val, int prec, char *buf, usize len) { + if (isinf(val)) return snprintf(buf, len, (val > 0) ? "Infinity" : "-Infinity"); + if (isnan(val)) return snprintf(buf, len, "NaN"); + int out_len = snprintf(buf, len, "%.*f", prec, val); + if (locale_decimal_point != '.') decimal_point_to_std(buf); + return (out_len >= (int)len) ? 0 : out_len; +} -/// read double from string -static usize f64_read(const char *str, f64 *val) { + + +// ============================================================================= +// Number conversion (google/double-conversion) +// ============================================================================= + +/// Read float from string (google/double-conversion). +static usize goo_f32_read(const char *str, f32 *val) { int str_len = (int)strlen(str); - *val = goo_strtod(str, &str_len); + *val = goo_strtof(str, str_len, &str_len); return (usize)str_len; } -/// write double to string -static usize f64_write(char *buf, usize len, f64 val) { - return (usize)goo_dtoa(val, buf, (int)len); +/// Read double from string (google/double-conversion). +static usize goo_f64_read(const char *str, f64 *val) { + int str_len = (int)strlen(str); + *val = goo_strtod(str, str_len, &str_len); + return (usize)str_len; } -#else +/// Write float to string shortest (google/double-conversion). +static usize goo_f32_write(f32 val, char *buf, usize len) { + return (usize)goo_ftoa(val, GOO_FMT_SHORTEST, 0, buf, (int)len); +} -/// Get locale decimal point. -static char locale_decimal_point(void) { - struct lconv *conv = localeconv(); - char c = conv->decimal_point[0]; - yy_assertf(c && conv->decimal_point[1] == '\0', - "locale decimal point is invalid: %s\n", conv->decimal_point); - return c; +/// Write double to string shortest (google/double-conversion). +static usize goo_f64_write(f64 val, char *buf, usize len) { + return (usize)goo_dtoa(val, GOO_FMT_SHORTEST, 0, buf, (int)len); +} + +/// Write double to string with fixed-point notation (google/double-conversion). +static usize goo_f64_write_fixed(f64 val, int prec, char *buf, usize len) { + return (usize)goo_dtoa(val, GOO_FMT_FIXED, prec, buf, (int)len); } -/// read double from string + + +// ============================================================================= +// Number conversion (common) +// ============================================================================= + +/// Read float from string. +static usize f32_read(const char *str, f32 *val) { +#if FP_IEEE_754 + return goo_f32_read(str, val); +#else + return libc_f32_read(str, val); +#endif +} + +/// Read double from string. static usize f64_read(const char *str, f64 *val) { - if (locale_decimal_point() != '.') { - char *dup = yy_str_copy(str); - for (char *cur = dup; *cur; cur++) { - if (*cur == '.') *cur = locale_decimal_point(); - } - char *end = NULL; - *val = strtod(dup, &end); - usize len = end ? (end - dup) : 0; - free((void *)dup); - return len; - } else { - char *end = NULL; - *val = strtod(str, &end); - usize len = end ? (end - str) : 0; - return len; - } +#if !FP_USE_LIBC + return goo_f64_read(str, val); +#else + return libc_f64_read(str, val); +#endif } -/// write double to string -static usize f64_write(char *buf, usize len, f64 val) { - int out_len = snprintf(buf, len, "%.17g", val); - if (out_len < 1 || out_len >= (int)len) return 0; - if (locale_decimal_point() != '.') { - char c = locale_decimal_point(); - for (int i = 0; i < out_len; i++) { - if (buf[i] == c) buf[i] = '.'; - } - } - if (out_len >= 3 && !strncmp(buf, "inf", 3)) return snprintf(buf, len, "Infinity"); - if (out_len >= 4 && !strncmp(buf, "+inf", 4)) return snprintf(buf, len, "Infinity"); - if (out_len >= 4 && !strncmp(buf, "-inf", 4)) return snprintf(buf, len, "-Infinity"); - if (out_len >= 3 && !strncmp(buf, "nan", 3)) return snprintf(buf, len, "NaN"); - if (out_len >= 4 && !strncmp(buf, "-nan", 4)) return snprintf(buf, len, "NaN"); - if (out_len >= 4 && !strncmp(buf, "+nan", 4)) return snprintf(buf, len, "NaN"); - if (!strchr(buf, '.') && !strchr(buf, 'e') && !strchr(buf, 'e')) { - if ((usize)out_len + 3 >= len) return 0; - memcpy(buf + out_len, ".0", 3); - out_len += 2; - } - return out_len; +/// Write float to string shortest. +static usize f32_write(f32 val, char *buf, usize len) { +#if !FP_USE_LIBC + return goo_f32_write(val, buf, len); +#else + return libc_f32_write(val, buf, len); +#endif } +/// Write double to string shortest. +static usize f64_write(f64 val, char *buf, usize len) { +#if !FP_USE_LIBC + return goo_f64_write(val, buf, len); +#else + return libc_f64_write(val, buf, len); #endif +} + +/// Write double to string with fixed-point notation. +static usize f64_write_fixed(f64 val, int prec, char *buf, usize len) { +#if !FP_USE_LIBC + return goo_f64_write_fixed(val, prec, buf, len); +#else + return libc_f64_write_fixed(val, prec, buf, len); +#endif +} -/// check if there's overflow when reading as integer number -static bool check_int_overflow(const char *str, num_type type) { +// ============================================================================= +// Number string format checker +// ============================================================================= + +/// number type +typedef enum { + NUM_TYPE_FAIL, // not a JSON number + NUM_TYPE_SINT, // signed integer + NUM_TYPE_UINT, // unsigned integer + NUM_TYPE_REAL, // real number + NUM_TYPE_LITERAL // nan or inf literal (non standard) +} num_type; + +/// number information +typedef struct { + const char *str; + usize len; + num_type type; + bool int_overflow; + bool real_overflow; + i64 i; + u64 u; + f64 f; +} num_info; + +/// Whether this character is digit. +static yy_inline bool char_is_digit(char c) { + return '0' <= c && c <= '9'; +} + +/// Whether this character is sign. +static yy_inline bool char_is_sign(char c) { + return c == '-' || c == '+'; +} + +/// Check for overflow when reading an integer number (uint64/int64). +static yy_inline bool check_int_overflow(const char *str, num_type type) { if (type != NUM_TYPE_SINT && type != NUM_TYPE_UINT) return false; bool sign = (*str == '-'); str += sign; + usize str_len = strlen(str); + if (str_len < 19) return false; + const char *max = sign ? "9223372036854775808" : "18446744073709551615"; usize max_len = strlen(max); - usize str_len = strlen(str); if (str_len > max_len) return true; if (str_len == max_len && strcmp(str, max) > 0) return true; return false; } -/// check if there's overflow when reading as real number -static bool check_real_overflow(const char *str, num_type type) { +/// Check for overflow when reading a real number (double). +static yy_inline bool check_real_overflow(const char *str, num_type type) { if (type != NUM_TYPE_SINT && type != NUM_TYPE_UINT && type != NUM_TYPE_REAL) return false; + f64 val = 0; - f64_read(str, &val); - return isinf(val); + if (!f64_read(str, &val)) return false; + return !!isinf(val); } - - -/*============================================================================== - * Test single number (uint) - *============================================================================*/ - -static void test_uint_read(const char *line, usize len, u64 num) { -#if !YYJSON_DISABLE_READER - yyjson_doc *doc = yyjson_read(line, len, 0); - yyjson_val *val = yyjson_doc_get_root(doc); - yy_assertf(yyjson_is_uint(val), - "num should be read as uint: %s\n", line); - u64 get = yyjson_get_uint(val); - yy_assertf(num == get, - "uint num read not match:\nstr: %s\nreturn: %llu\nexpect: %llu\n", - line, get, num); - yyjson_doc_free(doc); +/// Check JSON number string and return its type. +/// This checks only the string format, not for numeric overflow. +static yy_inline num_type get_num_type(const char *str) { + if (!str) return NUM_TYPE_FAIL; - // read number as raw - doc = yyjson_read(line, len, YYJSON_READ_NUMBER_AS_RAW); - val = yyjson_doc_get_root(doc); - yy_assertf(yyjson_is_raw(val), - "num should be read as raw: %s\n", line); - yy_assertf(strcmp(line, yyjson_get_raw(val)) == 0, - "uint num read as raw not match:\nstr: %s\nreturn: %s\n", - line, yyjson_get_raw(val)); - yyjson_doc_free(doc); + // optional sign + bool sign = (*str == '-'); + str += sign; - // read big number as raw - doc = yyjson_read(line, len, YYJSON_READ_BIGNUM_AS_RAW); - val = yyjson_doc_get_root(doc); - yy_assertf(yyjson_is_uint(val), - "num should be read as uint: %s\n", line); - get = yyjson_get_uint(val); - yy_assertf(num == get, - "uint num read not match:\nstr: %s\nreturn: %llu\nexpect: %llu\n", - line, get, num); - yyjson_doc_free(doc); -#endif -} - -static void test_uint_write(const char *line, u64 num) { -#if !YYJSON_DISABLE_WRITER - yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val *val = yyjson_mut_uint(doc, num); - yyjson_mut_doc_set_root(doc, val); + // must begin with a digit + if (!char_is_digit(*str)) { + if (!yy_str_cmp(str, "nan", true) || + !yy_str_cmp(str, "inf", true) || + !yy_str_cmp(str, "infinity", true)) return NUM_TYPE_LITERAL; + return NUM_TYPE_FAIL; + } - usize out_len; - char *out_str; - out_str = yyjson_mut_write(doc, 0, &out_len); - yy_assertf(strcmp(out_str, line) == 0, - "uint num write not match:\nstr: %s\nreturn: %s\nexpect: %s\n", - line, out_str, line); + // leading zeros are not allowed + if (*str == '0' && char_is_digit(str[1])) return NUM_TYPE_FAIL; - free(out_str); - yyjson_mut_doc_free(doc); -#endif -} - -static void test_uint(const char *line, usize len) { - yy_assertf(check_json_num(line) == NUM_TYPE_UINT, - "input is not uint: %s\n", line); - u64 num = strtoull(line, NULL, 10); - test_uint_read(line, len, num); - - yyjson_val val; - const char *ptr = yyjson_read_number(line, &val, 0, NULL, NULL); - yy_assertf(ptr == &line[len], "uint num fail: %s\n", line); - yy_assertf(yyjson_is_uint(&val), - "num should be read as uint: %s\n", line); - u64 get = yyjson_get_uint(&val); - yy_assertf(num == get, - "uint num read not match:\nstr: %s\nreturn: %llu\nexpect: %llu\n", - line, get, num); - - ptr = yyjson_read_number(line, &val, YYJSON_READ_NUMBER_AS_RAW, NULL, NULL); - yy_assertf(ptr == &line[len], "uint num fail: %s\n", line); - yy_assertf(yyjson_is_raw(&val), - "num should be read as raw: %s\n", line); - yy_assertf(strcmp(line, yyjson_get_raw(&val)) == 0, - "uint num read as raw not match:\nstr: %s\nreturn: %s\n", - line, yyjson_get_raw(&val)); - - ptr = yyjson_read_number(line, &val, YYJSON_READ_BIGNUM_AS_RAW, NULL, NULL); - yy_assertf(ptr == &line[len], "uint num fail: %s\n", line); - yy_assertf(yyjson_is_uint(&val), - "num should be read as uint: %s\n", line); - get = yyjson_get_uint(&val); - yy_assertf(num == get, - "uint num read not match:\nstr: %s\nreturn: %llu\nexpect: %llu\n", - line, get, num); - - char buf[32] = { 0 }; - snprintf(buf, 32, "%llu%c", num, '\0'); - test_uint_write(buf, num); -} - - - -/*============================================================================== - * Test single number (sint) - *============================================================================*/ - -static void test_sint_read(const char *line, usize len, i64 num) { -#if !YYJSON_DISABLE_READER - yyjson_doc *doc = yyjson_read(line, len, 0); - yyjson_val *val = yyjson_doc_get_root(doc); - yy_assertf(yyjson_is_sint(val), - "num should be read as sint: %s\n", line); - i64 get = yyjson_get_sint(val); - yy_assertf(num == get, - "uint num read not match:\nstr: %s\nreturn: %lld\nexpect: %lld\n", - line, get, num); - yyjson_doc_free(doc); + // one or more digits + while (char_is_digit(*str)) str++; - // read number as raw - doc = yyjson_read(line, len, YYJSON_READ_NUMBER_AS_RAW); - val = yyjson_doc_get_root(doc); - yy_assertf(yyjson_is_raw(val), - "num should be read as raw: %s\n", line); - yy_assertf(strcmp(line, yyjson_get_raw(val)) == 0, - "sint num read as raw not match:\nstr: %s\nreturn: %s\n", - line, yyjson_get_raw(val)); - yyjson_doc_free(doc); + // ending with integer type + if (*str == '\0') return sign ? NUM_TYPE_SINT : NUM_TYPE_UINT; - // read big number as raw - doc = yyjson_read(line, len, YYJSON_READ_BIGNUM_AS_RAW); - val = yyjson_doc_get_root(doc); - yy_assertf(yyjson_is_sint(val), - "num should be read as sint: %s\n", line); - get = yyjson_get_sint(val); - yy_assertf(num == get, - "uint num read not match:\nstr: %s\nreturn: %lld\nexpect: %lld\n", - line, get, num); - yyjson_doc_free(doc); -#endif + // optional fraction part + if (*str == '.') { + str++; + // one or more digits + if (!char_is_digit(*str)) return NUM_TYPE_FAIL; + while (char_is_digit(*str)) str++; + } + + // optional exponent part + if (*str == 'e' || *str == 'E') { + str++; + // optional sign + if (*str == '-' || *str == '+') str++; + // one or more digits + if (!char_is_digit(*str)) return NUM_TYPE_FAIL; + while (char_is_digit(*str)) str++; + } + + // ending with real type + if (*str == '\0') return NUM_TYPE_REAL; + return NUM_TYPE_FAIL; } -static void test_sint_write(const char *line, i64 num) { -#if !YYJSON_DISABLE_WRITER - yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val *val = yyjson_mut_sint(doc, num); - yyjson_mut_doc_set_root(doc, val); +/// Get number information from a string. +static yy_inline num_info get_num_info(const char *str) { + num_info info = { 0 }; + info.str = str; + info.len = str ? strlen(str) : 0; + info.type = get_num_type(str); - char *str = yyjson_mut_write(doc, 0, NULL); - yy_assertf(strcmp(str, line) == 0, - "uint num write not match:\nreturn: %s\nexpect: %s\n", - str, line); - free(str); - yyjson_mut_doc_free(doc); -#endif + if (info.type == NUM_TYPE_FAIL) { + return info; + } + + if (info.type == NUM_TYPE_LITERAL) { + bool sign = *str == '-'; + str += sign; + info.f = (*str == 'n' || *str == 'N') ? NAN : (sign ? -INFINITY : INFINITY); + return info; + } + + if (info.type == NUM_TYPE_UINT || info.type == NUM_TYPE_SINT) { + info.int_overflow = check_int_overflow(str, info.type); + if (!info.int_overflow) { + if (info.type == NUM_TYPE_UINT) { + yy_assert(sizeof(unsigned long long) >= sizeof(u64)); + info.u = (u64)strtoull(str, NULL, 10); + info.f = (f64)info.u; + } else { + yy_assert(sizeof(signed long long) >= sizeof(i64)); + info.i = (i64)strtoll(str, NULL, 10); + info.f = (f64)info.i; + } + return info; + } + } + + // real number and integer overflow number + f64 val = 0; + yy_assert(f64_read(str, &val) > 0); + info.f = val; + info.real_overflow = !!isinf(val); + return info; } -static void test_sint(const char *line, usize len) { - yy_assertf(check_json_num(line) == NUM_TYPE_SINT, - "input is not sint: %s\n", line); - - i64 num = strtoll(line, NULL, 10); - test_sint_read(line, len, num); - - yyjson_val val; - const char *ptr = yyjson_read_number(line, &val, 0, NULL, NULL); - yy_assertf(ptr == &line[len], "sint num fail: %s\n", line); - yy_assertf(yyjson_is_sint(&val), - "num should be read as sint: %s\n", line); - i64 get = yyjson_get_sint(&val); - yy_assertf(num == get, - "sint num read not match:\nstr: %s\nreturn: %lld\nexpect: %lld\n", - line, get, num); - - ptr = yyjson_read_number(line, &val, YYJSON_READ_NUMBER_AS_RAW, NULL, NULL); - yy_assertf(ptr == &line[len], "uint num fail: %s\n", line); - yy_assertf(yyjson_is_raw(&val), - "num should be read as raw: %s\n", line); - yy_assertf(strcmp(line, yyjson_get_raw(&val)) == 0, - "sint num read as raw not match:\nstr: %s\nreturn: %s\n", - line, yyjson_get_raw(&val)); - - ptr = yyjson_read_number(line, &val, YYJSON_READ_BIGNUM_AS_RAW, NULL, NULL); - yy_assertf(ptr == &line[len], "sint num fail: %s\n", line); - yy_assertf(yyjson_is_sint(&val), - "num should be read as sint: %s\n", line); - get = yyjson_get_sint(&val); - yy_assertf(num == get, - "sint num read not match:\nstr: %s\nreturn: %lld\nexpect: %lld\n", - line, get, num); - - char buf[32] = { 0 }; - snprintf(buf, 32, "%lld%c", num, '\0'); - test_sint_write(buf, num); - num = (i64)((u64)~num + 1); // num = -num, avoid ubsan - snprintf(buf, 32, "%lld%c", num, '\0'); - test_sint_write(buf, num); +/// Check if the number string is in its most compact (shortest) form. +static yy_inline bool check_num_compact(const char *str, num_type type) { + if (type == NUM_TYPE_SINT || type == NUM_TYPE_UINT) { + return true; + } + + if (type == NUM_TYPE_LITERAL) { + bool sign = *str == '-'; + str += sign; + if (*str == 'n' || *str == 'N') return !sign; // sign is unnecessary for NaN + return true; + } + + if (type == NUM_TYPE_REAL) { + // get decimal point and exponent part + const char *dot = NULL, *exp = NULL, *end = NULL; + const char *cur = str; + while (*cur) { + if (*cur == '.') dot = cur; + else if (*cur == 'e' || *cur == 'E') exp = cur; + cur++; + } + end = cur; + + // check fraction part + if (dot) { + if (exp) { + if (*(exp - 1) == '0' || + *(exp - 1) == '.') return false; // 1.0e23, 1.e23 -> 1e23 + } else { + if (*(end - 1) == '0' && + *(end - 2) != '.' && + !char_is_digit(*(end - 2))) return false; // 1.10 -> 1.1 + } + } + + // check exponent part + if (exp) { + if (exp[1] == '+') return false; // 1e+23 -> 1e23 + if (exp[1] == '0') return false; // 1e023 -> 1e23 + } + return true; + } + + return false; } -/*============================================================================== - * Test single number (real) - *============================================================================*/ +// ============================================================================= +// Number read and write +// ============================================================================= -static void test_real_read(const char *line, usize len, f64 num) { -#if !YYJSON_DISABLE_READER - f64 ret; - yyjson_doc *doc; - yyjson_val *val; - if (isinf(num)) { - // read number as JSON value - doc = yyjson_read(line, len, 0); - val = yyjson_doc_get_root(doc); - yy_assertf(!doc, "num %s should fail, but returns %.17g\n", - line, yyjson_get_real(val)); - - // read number as raw string - doc = yyjson_read(line, len, YYJSON_READ_NUMBER_AS_RAW); - val = yyjson_doc_get_root(doc); - yy_assertf(yyjson_is_raw(val), - "num should be read as raw: %s\n", line); - yy_assertf(strcmp(line, yyjson_get_raw(val)) == 0, - "num read as raw not match:\nstr: %s\nreturn: %s\n", - line, yyjson_get_raw(val)); - yyjson_doc_free(doc); - - // read big number as raw string - doc = yyjson_read(line, len, YYJSON_READ_BIGNUM_AS_RAW); - val = yyjson_doc_get_root(doc); - yy_assertf(yyjson_is_raw(val), - "num should be read as raw: %s\n", line); - yy_assertf(strcmp(line, yyjson_get_raw(val)) == 0, - "num read as raw not match:\nstr: %s\nreturn: %s\n", - line, yyjson_get_raw(val)); - yyjson_doc_free(doc); +/// Validate a real number's output. +static void validate_real_output(const char *str, + void *val_ptr, yyjson_write_flag flg) { +#define expect(expr) yy_assertf(expr, "num: %.17g, flg: %u, out: [%s]", num, flg, str) + + yyjson_val *val = val_ptr; + yy_assert(yyjson_is_real(val)); + + /// global flag + bool allow_inf_nan = (flg & YYJSON_WRITE_ALLOW_INF_AND_NAN) != 0; + bool inf_nan_to_null = (flg & YYJSON_WRITE_INF_AND_NAN_AS_NULL) != 0; + bool to_float = (flg & YYJSON_WRITE_FP_TO_FLOAT) != 0; + u32 to_fixed = flg >> (32 - YYJSON_WRITE_FP_PREC_BITS); + + /// value flag, should override global flag + bool val_to_float = ((u32)(val->tag >> 32) & YYJSON_WRITE_FP_TO_FLOAT) != 0; + u32 val_to_fixed = (u32)(val->tag >> 32) >> (32 - YYJSON_WRITE_FP_PREC_BITS); + if (val_to_fixed) to_fixed = val_to_fixed; + if (val_to_float) to_float = val_to_float; + + /// `to fixed` should override `to float` + if (to_fixed) to_float = false; + + char buf[64]; + f64 num = val->uni.f64; + if (to_float) num = (f32)num; + + if (isfinite(num)) { + expect(get_num_type(str) == NUM_TYPE_REAL); + expect(check_num_compact(str, NUM_TYPE_REAL)); -#if !YYJSON_DISABLE_NON_STANDARD - // read number as JSON value - doc = yyjson_read(line, len, YYJSON_READ_ALLOW_INF_AND_NAN); - val = yyjson_doc_get_root(doc); - ret = yyjson_get_real(val); - yy_assertf(yyjson_is_real(val) && (ret == num), - "num %s should be read as inf, but returns %.17g\n", - line, ret); - yyjson_doc_free(doc); + if (to_fixed && (-1e21 < num && num < 1e21)) { + // To fixed-point. + // This will remove trailing zeros and reduce unnecessary precision, + // so the output string may not be the same as libc/google's. + f64_write_fixed(num, to_fixed, buf, sizeof(buf)); + f64 out_num; + expect(f64_read(str, &out_num) > 0); + expect(f64_read(buf, &num) > 0); + expect(out_num == num); + + } else { +#if FP_USE_LIBC + // To shortest. + // The libc's output string may not be the shortest. + f64 out_num; + expect(f64_read(str, &out_num) > 0); + if (to_float) { + expect((f32)out_num == (f32)num); + } else { + expect(out_num == num); + } +#else + // To shortest. + // The output string should be exactly the same as google's. + if (to_float) { + f32_write((f32)num, buf, sizeof(buf)); + } else { + f64_write(num, buf, sizeof(buf)); + } + expect(!strcmp(str, buf)); #endif - + } } else { - u64 num_raw = f64_to_u64_raw(num); - u64 ret_raw; - i64 ulp; - - // 0 ulp error - doc = yyjson_read(line, len, 0); - val = yyjson_doc_get_root(doc); - ret = yyjson_get_real(val); - ret_raw = f64_to_u64_raw(ret); - ulp = (i64)num_raw - (i64)ret_raw; - if (ulp < 0) ulp = -ulp; - yy_assertf(yyjson_is_real(val) && ulp == 0, - "string %s should be read as %.17g, but returns %.17g\n", - line, num, ret); - yyjson_doc_free(doc); - - // read big number as raw - doc = yyjson_read(line, len, YYJSON_READ_BIGNUM_AS_RAW); - val = yyjson_doc_get_root(doc); - if (check_json_num(line) == NUM_TYPE_REAL) { - yy_assert(yyjson_is_real(val)); - } else { - yy_assert(yyjson_is_raw(val)); + if (inf_nan_to_null) { + expect(!strcmp(str, "null")); + } +#if !YYJSON_DISABLE_NON_STANDARD + else if (allow_inf_nan) { + if (isnan(num)) { + expect(!strcmp(str, "NaN")); + } else if (num > 0) { + expect(!strcmp(str, "Infinity")); + } else { + expect(!strcmp(str, "-Infinity")); + } } - yyjson_doc_free(doc); - } #endif + else { + expect(!str); + } + } +#undef expect } -static void test_real_write(const char *line, usize len, f64 num) { -#if !YYJSON_DISABLE_WRITER + +/// Test number write with info and flag. +static void test_num_write(num_info info, yyjson_write_flag flg) { +#define expect(expr) yy_assertf(expr, "num str: [%s], flg: %u", info.str, flg) + + bool to_float = (flg & YYJSON_WRITE_FP_TO_FLOAT) != 0; + u32 to_fixed = flg >> (32 - YYJSON_WRITE_FP_PREC_BITS); + yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); + + /// write as real number + f64 num = info.f; + if (to_float && !to_fixed) num = (f32)num; + yyjson_mut_val *val = yyjson_mut_real(doc, num); yyjson_mut_doc_set_root(doc, val); + char *str = yyjson_mut_write(doc, flg, NULL); + validate_real_output(str, val, flg); + free(str); - char *str = yyjson_mut_write(doc, 0, NULL); - char *str_nan_inf = yyjson_mut_write(doc, YYJSON_WRITE_ALLOW_INF_AND_NAN, NULL); - - if (isnan(num) || isinf(num)) { - yy_assertf(str == NULL, - "num write should return NULL, but returns: %s\n", - str); -#if !YYJSON_DISABLE_NON_STANDARD - yy_assertf(strcmp(str_nan_inf, line) == 0, - "num write not match:\nexpect: %s\nreturn: %s\n", - line, str_nan_inf); -#else - yy_assertf(str_nan_inf == NULL, - "num write should return NULL, but returns: %s\n", - str); -#endif - } else { - yy_assert(strcmp(str, str_nan_inf) == 0); - f64 dst_num; - yy_assert(f64_read(str, &dst_num) == strlen(str)); - u64 dst_raw = f64_to_u64_raw(dst_num); - u64 num_raw = f64_to_u64_raw(num); - yy_assertf(dst_raw == num_raw, - "real number write value not match:\nexpect: %s\nreturn: %s\n", - line, str); - yy_assertf(str_get_sig_len(str) == str_get_sig_len(line), - "real number write value not shortest:\nexpect: %s\nreturn: %s\n", - line, str); - yy_assertf(check_json_num(str) == NUM_TYPE_REAL, - "real number write value not valid JSON format: %s\nreturn: %s\n", - line, str); + /// write as uint/sint + if (info.type == NUM_TYPE_UINT && !info.int_overflow) { + char buf[64]; + snprintf(buf, sizeof(buf), "%llu", (unsigned long long)info.u); + val = yyjson_mut_uint(doc, info.u); + yyjson_mut_doc_set_root(doc, val); + str = yyjson_mut_write(doc, flg, NULL); + expect(!strcmp(str, buf)); + free(str); + } else if (info.type == NUM_TYPE_SINT && !info.int_overflow) { + char buf[64]; + snprintf(buf, sizeof(buf), "%lld", (signed long long)info.i); + val = yyjson_mut_sint(doc, info.i); + yyjson_mut_doc_set_root(doc, val); + str = yyjson_mut_write(doc, flg, NULL); + expect(!strcmp(str, buf)); + free(str); } yyjson_mut_doc_free(doc); - if (str) free(str); - if (str_nan_inf) free(str_nan_inf); -#endif +#undef expect } -static void test_real(const char *line, usize len) { - yy_assertf(check_json_num(line) != NUM_TYPE_FAIL, - "input is not number: %s\n", line); - - f64 num; - usize read_len = f64_read(line, &num); - yy_assertf(len == read_len, - "f64_read() failed: %s\ninput length: %d\nread length: %d\n", - line, (int)len, (int)read_len); - yy_assertf(!isnan(num), - "f64_read() failed: %s\nread as NaN", line); - test_real_read(line, len, num); - - yyjson_val val; - const char *ptr = yyjson_read_number(line, &val, 0, NULL, NULL); - - if (isinf(num)) { - yy_assertf(ptr == NULL, "num %s should fail, but returns %.17g\n", - line, yyjson_get_real(&val)); +/// Test number read with info and flag. +static void test_num_read(num_info info, yyjson_read_flag flg) { +#define expect(expr) yy_assertf(expr, "num str: [%s], flg: %u", str, flg) + + const char *str = info.str; + usize len = info.len; + yyjson_doc *doc = yyjson_read(str, len, flg); + yyjson_val *val = yyjson_doc_get_root(doc); + +#if YYJSON_DISABLE_NON_STANDARD + bool non_std = false; +#else + bool non_std = true; +#endif + bool flg_big_raw = (flg & YYJSON_READ_BIGNUM_AS_RAW) != 0; + bool flg_num_raw = (flg & YYJSON_READ_NUMBER_AS_RAW) != 0; + bool flg_inf_nan = (flg & YYJSON_READ_ALLOW_INF_AND_NAN) != 0; + + if (info.type == NUM_TYPE_FAIL) { + /// not a valid number + expect(val == NULL); -#if !YYJSON_DISABLE_NON_STANDARD - ptr = yyjson_read_number(line, &val, YYJSON_READ_ALLOW_INF_AND_NAN, NULL, NULL); - yy_assertf(ptr == &line[len], "real num fail: %s\n", line); + } else if (info.type == NUM_TYPE_LITERAL) { + /// nan/inf literal + if (flg_inf_nan && non_std) { + if (flg_num_raw) { + expect(yyjson_is_raw(val) && !strcmp(yyjson_get_raw(val), str)); + } else if (isnan(info.f)) { + expect(yyjson_is_real(val) && isnan(yyjson_get_real(val))); + } else { + expect(yyjson_is_real(val) && yyjson_get_real(val) == info.f); + } + } else { + expect(val == NULL); + } - yy_assertf(yyjson_is_real(&val), - "num should be read as real: %s\n", line); + } else if (flg_num_raw) { + /// uint/sint/real -> raw + expect(yyjson_is_raw(val) && !strcmp(yyjson_get_raw(val), str)); - f64 get = yyjson_get_real(&val); - yy_assertf(isinf(get), "num should be read as inf: %s\n", line); -#endif - } else { - yy_assertf(ptr == &line[len], "real num fail: %s\n", line); + } else if (info.real_overflow) { + /// uint/sint/real -> overflow + if (flg_big_raw) { + expect(yyjson_is_raw(val) && !strcmp(yyjson_get_raw(val), str)); + } else if (non_std && flg_inf_nan) { + expect(yyjson_is_real(val) && yyjson_get_real(val) == info.f); + } else { + expect(val == NULL); + } + + } else if (info.int_overflow) { + /// uint/sint overflow -> real + if (flg_big_raw) { + expect(yyjson_is_raw(val) && !strcmp(yyjson_get_raw(val), str)); + } else { + expect(yyjson_is_real(val) && yyjson_get_real(val) == info.f); + } + + } else if (info.type == NUM_TYPE_UINT) { + /// uint + expect(yyjson_is_uint(val) && yyjson_get_uint(val) == info.u); - yy_assertf(yyjson_is_real(&val), - "num should be read as real: %s\n", line); + } else if (info.type == NUM_TYPE_SINT) { + /// sint + expect(yyjson_is_sint(val) && yyjson_get_sint(val) == info.i); + + } else if (info.type == NUM_TYPE_REAL) { + /// real + expect(yyjson_is_real(val) && yyjson_get_real(val) == info.f); - f64 get = yyjson_get_real(&val); - yy_assertf(f64_to_u64_raw(num) == f64_to_u64_raw(get), - "real num read not match:\nstr: %s\n" - "return: %.17g\n" - "expect: %.17g\n", - line, get, num); } - ptr = yyjson_read_number(line, &val, YYJSON_READ_NUMBER_AS_RAW, NULL, NULL); - yy_assertf(ptr == &line[len], "uint num fail: %s\n", line); - - yy_assertf(yyjson_is_raw(&val), - "num should be read as raw: %s\n", line); - - yy_assertf(strcmp(line, yyjson_get_raw(&val)) == 0, - "sint num read as raw not match:\nstr: %s\nreturn: %s\n", - line, yyjson_get_raw(&val)); - - char buf[32] = { 0 }; - usize write_len = f64_write(buf, sizeof(buf), num); - yy_assertf(write_len > 0, - "f64_write() fail: %s\n", line); - test_real_write(buf, write_len, num); -} - - - -/*============================================================================== - * Test single number (nan/inf) - *============================================================================*/ - -static void test_nan_inf_read(const char *line, usize len, f64 num) { -#if !YYJSON_DISABLE_READER - f64 ret; - yyjson_doc *doc; - yyjson_val *val; - - // read fail - doc = yyjson_read(line, len, 0); - yy_assertf(doc == NULL, "number %s should fail in default mode\n", line); - doc = yyjson_read(line, len, YYJSON_READ_NUMBER_AS_RAW); - yy_assertf(doc == NULL, "number %s should fail in raw mode\n", line); - - // read allow - doc = yyjson_read(line, len, YYJSON_READ_ALLOW_INF_AND_NAN); - val = yyjson_doc_get_root(doc); - ret = yyjson_get_real(val); - yy_assertf(yyjson_is_real(val), "nan or inf read fail: %s \n", line); - if (isnan(num)) { - yy_assertf(isnan(ret), "num %s should read as nan\n", line); + yyjson_val val_out = { 0 }; + const char *ptr = yyjson_read_number(str, &val_out, flg, NULL, NULL); + if (val) { + expect(yyjson_equals(val, &val_out)); + expect(ptr == str + len); } else { - yy_assertf(ret == num, "num %s should read as inf\n", line); + expect(ptr != str + len); } - yyjson_doc_free(doc); - // read raw - doc = yyjson_read(line, len, YYJSON_READ_ALLOW_INF_AND_NAN | YYJSON_READ_NUMBER_AS_RAW); - val = yyjson_doc_get_root(doc); - yy_assertf(yyjson_is_raw(val), - "num should be read as raw: %s\n", line); - yy_assertf(strcmp(line, yyjson_get_raw(val)) == 0, - "num read as raw not match:\nstr: %s\nreturn: %s\n", - line, yyjson_get_raw(val)); yyjson_doc_free(doc); -#endif +#undef expect } -static void test_nan_inf_write(const char *line, usize len, f64 num) { -#if !YYJSON_DISABLE_WRITER - yyjson_mut_doc *doc = yyjson_mut_doc_new(NULL); - yyjson_mut_val *val = yyjson_mut_real(doc, num); - yyjson_mut_doc_set_root(doc, val); +/// Test number read and write. +static void test_num_info(num_info info) { + /// test read + test_num_read(info, YYJSON_READ_NOFLAG); + test_num_read(info, YYJSON_READ_BIGNUM_AS_RAW); + test_num_read(info, YYJSON_READ_NUMBER_AS_RAW); + test_num_read(info, YYJSON_READ_ALLOW_INF_AND_NAN); + test_num_read(info, YYJSON_READ_BIGNUM_AS_RAW | YYJSON_READ_ALLOW_INF_AND_NAN); + test_num_read(info, YYJSON_READ_NUMBER_AS_RAW | YYJSON_READ_ALLOW_INF_AND_NAN); - char *str = yyjson_mut_write(doc, 0, NULL); - char *str_nan_inf = yyjson_mut_write(doc, YYJSON_WRITE_ALLOW_INF_AND_NAN, NULL); + /// test write + test_num_write(info, YYJSON_WRITE_NOFLAG); + test_num_write(info, YYJSON_WRITE_ALLOW_INF_AND_NAN); + test_num_write(info, YYJSON_WRITE_INF_AND_NAN_AS_NULL); - yy_assertf(str == NULL, - "num write should return NULL, but returns: %s\n", - str); - yy_assertf(strcmp(str_nan_inf, line) == 0, - "num write not match:\nexpect: %s\nreturn: %s\n", - line, str_nan_inf); - - yyjson_mut_doc_free(doc); - if (str) free(str); - if (str_nan_inf) free(str_nan_inf); -#endif -} - -static void test_nan_inf(const char *line, usize len) { - num_type type = check_json_num(line); - yy_assertf(type == NUM_TYPE_FAIL || type == NUM_TYPE_INF_NAN_LITERAL, - "input should not be a valid JSON number: %s\n", line); - - f64 num; - usize read_len = f64_read(line, &num); - yy_assertf(len == read_len, - "f64_read() failed: %s\ninput length: %d\nread length: %d\n", - line, (int)len, (int)read_len); - yy_assertf(isnan(num) || isinf(num), - "f64_read() failed: %s\nexpect: NaN or Inf, out: %.17g", line, num); - test_nan_inf_read(line, len, num); - - yyjson_val val; - const char *ptr = yyjson_read_number(line, &val, 0, NULL, NULL); - yy_assertf(ptr != &line[len], "num should fail: %s\n", line); - - char buf[32] = { 0 }; - usize write_len = f64_write(buf, sizeof(buf), num); - yy_assertf(write_len > 0, - "f64_write() fail: %s\n", line); - test_nan_inf_write(buf, write_len, num); + /// test write fp format + test_num_write(info, YYJSON_WRITE_FP_TO_FLOAT); + test_num_write(info, YYJSON_WRITE_FP_TO_FLOAT | YYJSON_WRITE_ALLOW_INF_AND_NAN); + test_num_write(info, YYJSON_WRITE_FP_TO_FLOAT | YYJSON_WRITE_INF_AND_NAN_AS_NULL); + for (int i = 1; i <= 15; i++) { + test_num_write(info, YYJSON_WRITE_FP_TO_FIXED(i)); + } } - - -/*============================================================================== - * Test single number (fail) - *============================================================================*/ - -static void test_fail(const char *line, usize len) { -#if !YYJSON_DISABLE_READER - yyjson_doc *doc; - doc = yyjson_read(line, len, 0); - yy_assertf(doc == NULL, "num should fail: %s\n", line); - doc = yyjson_read(line, len, YYJSON_READ_ALLOW_INF_AND_NAN); - yy_assertf(doc == NULL, "num should fail: %s\n", line); - doc = yyjson_read(line, len, YYJSON_READ_NUMBER_AS_RAW); - yy_assertf(doc == NULL, "num should fail: %s\n", line); - doc = yyjson_read(line, len, YYJSON_READ_NUMBER_AS_RAW | - YYJSON_READ_ALLOW_INF_AND_NAN); - yy_assertf(doc == NULL, "num should fail: %s\n", line); +/// Test all numbers from the txt files. +static void test_all_files(void) { + /// get all files in the /test/data/num directory + char dir[YY_MAX_PATH]; + yy_path_combine(dir, YYJSON_TEST_DATA_PATH, "data", "num", NULL); + int count; + char **names = yy_dir_read(dir, &count); + yy_assertf(names != NULL && count != 0, "read dir fail:%s\n", dir); - yyjson_val val; - const char *ptr; - ptr = yyjson_read_number(line, &val, 0, NULL, NULL); - yy_assertf(ptr != &line[len], "num should fail: %s\n", line); - ptr = yyjson_read_number(line, &val, YYJSON_READ_ALLOW_INF_AND_NAN, NULL, NULL); - yy_assertf(ptr != &line[len], "num should fail: %s\n", line); - ptr = yyjson_read_number(line, &val, YYJSON_READ_NUMBER_AS_RAW, NULL, NULL); - yy_assertf(ptr != &line[len], "num should fail: %s\n", line); - ptr = yyjson_read_number(line, &val, YYJSON_READ_NUMBER_AS_RAW | - YYJSON_READ_ALLOW_INF_AND_NAN, NULL, NULL); - yy_assertf(ptr != &line[len], "num should fail: %s\n", line); -#endif -} - - - -/*============================================================================== - * Test with input file - *============================================================================*/ - -static void test_with_file(const char *name, num_type type) { - char path[YY_MAX_PATH]; - yy_path_combine(path, YYJSON_TEST_DATA_PATH, "data", "num", name, NULL); - yy_dat dat; - bool file_suc = yy_dat_init_with_file(&dat, path); - yy_assertf(file_suc == true, "file read fail: %s\n", path); - - usize len; - const char *line; - while ((line = yy_dat_copy_line(&dat, &len))) { - if (len && line[0] != '#') { - if (type == NUM_TYPE_FAIL) { - test_fail(line, len); - } else if (type == NUM_TYPE_UINT) { - test_uint(line, len); - } else if (type == NUM_TYPE_SINT) { - test_sint(line, len); - } else if (type == NUM_TYPE_REAL) { - test_real(line, len); - } else if (type == NUM_TYPE_INF_NAN_LITERAL) { -#if !YYJSON_DISABLE_NON_STANDARD - test_nan_inf(line, len); -#endif - } + for (int i = 0; i < count; i++) { + /// get full path of this file, ignore hidden and non-txt file + char *name = names[i]; + if (*name == '.') continue; + if (!yy_str_has_suffix(name, ".txt")) continue; + char path[YY_MAX_PATH]; + yy_path_combine(path, dir, name, NULL); + + /// read this file to memory + yy_dat dat; + bool file_suc = yy_dat_init_with_file(&dat, path); + yy_assertf(file_suc == true, "file read fail: %s\n", path); + + /// iterate over each line of the file + usize len; + char *line; + while ((line = yy_dat_read_line(&dat, &len))) { + /// ignore empty line and comment + if (len == 0 || line[0] == '#') continue; + /// add a null-terminator + line[len] = '\0'; + /// test one line + test_num_info(get_num_info(line)); } - free((void *)line); + + yy_dat_release(&dat); } - yy_dat_release(&dat); - - yyjson_mut_val val; - const char *ptr; - ptr = yyjson_mut_read_number(NULL, &val, 0, NULL, NULL); - yy_assertf(ptr == NULL, "read line NULL should fail\n"); - ptr = yyjson_mut_read_number("123", NULL, 0, NULL, NULL); - yy_assertf(ptr == NULL, "read val NULL should fail\n"); - ptr = yyjson_mut_read_number(NULL, NULL, 0, NULL, NULL); - yy_assertf(ptr == NULL, "read line and val NULL should fail\n"); + yy_dir_free(names); } - - -/*============================================================================== - * Test with random value - *============================================================================*/ - +/// Test some random integer read/write. static void test_random_int(void) { - char buf[32] = { 0 }; - char *end; int count = 10000; + char buf[64] = { 0 }; + char *end; + + num_info info = { 0 }; + info.str = buf; yy_rand_reset(0); for (int i = 0; i < count; i++) { - u64 rnd = yy_rand_u64(); - end = buf + snprintf(buf, 32, "%llu%c", rnd, '\0') - 1; - test_uint(buf, end - buf); + u64 r = yy_rand_u64(); + info.len = (usize)snprintf(buf, 32, "%llu", (unsigned long long)r); + info.u = r; + info.type = NUM_TYPE_UINT; + test_num_info(info); } yy_rand_reset(0); for (int i = 0; i < count; i++) { - i64 rnd = (i64)(yy_rand_u64() | ((u64)1 << 63)); - end = buf + snprintf(buf, 32, "%lld%c", rnd, '\0') - 1; - test_sint(buf, end - buf); + i64 r = (i64)(yy_rand_u64() | ((u64)1 << 63)); + info.len = (usize)snprintf(buf, 32, "%lld", (signed long long)r); + info.i = r; + info.type = NUM_TYPE_SINT; + test_num_info(info); } yy_rand_reset(0); for (int i = 0; i < count; i++) { - u32 rnd = yy_rand_u32(); - end = buf + snprintf(buf, 32, "%u%c", rnd, '\0') - 1; - test_uint(buf, end - buf); + u32 r = yy_rand_u32(); + info.len = (usize)snprintf(buf, 32, "%lu", (unsigned long)r); + info.u = r; + info.type = NUM_TYPE_UINT; + test_num_info(info); } yy_rand_reset(0); for (int i = 0; i < count; i++) { - i32 rnd = (i32)(yy_rand_u32() | ((u32)1 << 31)); - end = buf + snprintf(buf, 32, "%i%c", rnd, '\0') - 1; - test_sint(buf, end - buf); + i32 r = (i32)(yy_rand_u32() | ((u32)1 << 31)); + info.len = (usize)snprintf(buf, 32, "%li", (signed long)r); + info.i = r; + info.type = NUM_TYPE_SINT; + test_num_info(info); + } +} + +/// Test real number read/write fast (do not test all flags). +static void test_real_fast(f64 num, yyjson_alc *alc, + bool test_to_float, + bool test_to_fixed) { + char buf[64] = { 0 }; + char *str; + usize len; + + yyjson_val val = { 0 }; + yyjson_set_real(&val, num); + + /// double to shortest + str = yyjson_val_write_opts(&val, 0, alc, &len, NULL); + validate_real_output(str, &val, 0); + if (str) { + yyjson_val val_out = { 0 }; + const char *end = yyjson_read_number(str, &val_out, 0, alc, NULL); + yy_assert(end && *end == '\0'); + yy_assert(val_out.uni.f64 == val.uni.f64); + alc->free(alc->ctx, str); + } + + /// float to shortest + if (test_to_float) { + yyjson_write_flag flg = YYJSON_WRITE_FP_TO_FLOAT; + str = yyjson_val_write_opts(&val, flg, alc, &len, NULL); + validate_real_output(str, &val, flg); + if (str) { + yyjson_val val2 = { 0 }; + const char *end = yyjson_read_number(str, &val2, 0, alc, NULL); + yy_assert(end && *end == '\0'); + + f64 num2; + f64_read(str, &num2); + yy_assert(val2.uni.f64 == num2); + alc->free(alc->ctx, str); + } + } + + /// double to fixed + if (test_to_fixed) { + for (int prec = 1; prec <= 15; prec++) { + yyjson_write_flag flg = YYJSON_WRITE_FP_TO_FIXED(prec); + str = yyjson_val_write_opts(&val, flg, alc, &len, NULL); + validate_real_output(str, &val, flg); + if (str) { + yyjson_val val2 = { 0 }; + const char *end = yyjson_read_number(str, &val2, 0, alc, NULL); + yy_assert(end && *end == '\0'); + + f64 num2; + f64_read(str, &num2); + yy_assert(val2.uni.f64 == num2); + alc->free(alc->ctx, str); + } + } } } +/// Test some random real number read/write. static void test_random_real(void) { - char buf[32] = { 0 }; - char *end; int count = 10000; + char alc_buf[4096]; + yyjson_alc alc; + yyjson_alc_pool_init(&alc, alc_buf, sizeof(alc_buf)); yy_rand_reset(0); for (int i = 0; i < count; i++) { - f64 rnd = rand_f64(); - usize out_len = f64_write(buf, sizeof(buf), rnd); - end = buf + out_len; - yy_assertf(out_len > 0, "f64_write() fail: %.17g\n", rnd); - test_real(buf, end - buf); + u64 r = yy_rand_u64(); + f64 f = f64_from_raw(r); + test_real_fast(f, &alc, true, true); + } + + yy_rand_reset(0); + for (int i = 0; i < count; i++) { + u32 r = yy_rand_u32(); + f32 f = f32_from_raw(r); + test_real_fast(f, &alc, true, true); } } +/// Test some special real number read/write static void test_special_real(void) { - char buf[64] = { 0 }; + char alc_buf[4096]; + yyjson_alc alc; + yyjson_alc_pool_init(&alc, alc_buf, sizeof(alc_buf)); // short digits for (int sig = 1; sig <= 200; sig++) { for (int exp = -326; exp <= 308; exp++) { - int len = snprintf(buf, sizeof(buf), "%de%d", sig, exp); - f64 num = 0; + char buf[64]; + snprintf(buf, sizeof(buf), "%de%d", sig, exp); + f64 num; f64_read(buf, &num); - if (!isfinite(num)) continue; - test_real(buf, len); + test_real_fast(num, &alc, true, true); } } @@ -822,22 +849,73 @@ static void test_special_real(void) { for (u64 exp = 0; exp <= 2046; exp++) { for (u64 sig = 0; sig <= 100; sig++) { u64 raw = (exp << 52) | sig; - f64 num = f64_from_u64_raw(raw); - if (!isfinite(num)) continue; - usize len = f64_write(buf, sizeof(buf), num); - test_real(buf, len); + f64 num = f64_from_raw(raw); + test_real_fast(num, &alc, true, true); } for (u64 sig = 0xFFFFFFFFFFFFFULL; sig >= (0xFFFFFFFFFFFFFULL - 100); sig--) { u64 raw = (exp << 52) | sig; - f64 num = f64_from_u64_raw(raw); - if (!isfinite(num)) continue; - usize len = f64_write(buf, sizeof(buf), num); - test_real(buf, len); + f64 num = f64_from_raw(raw); + test_real_fast(num, &alc, true, true); } } } -static void test_num_types(void) { +/// Test all float number read/write. +static void test_all_float(void) { + char alc_buf[4096]; + yyjson_alc alc; + yyjson_alc_pool_init(&alc, alc_buf, sizeof(alc_buf)); + + printf("--- begin test all float ---\n"); + f64 begin_time = yy_get_time(); + for (u32 i = 0, max = (u32)1 << 31; i < max; i++) { + f32 f = f32_from_raw(i); + test_real_fast((f64)f, &alc, true, false); + + // print progress + if ((i << 8) == 0 && i) { + f64 progress = (f64)i / max; + f64 elapsed = yy_get_time() - begin_time; + f64 expected = elapsed / (i + 1) * max; + f64 remaining = expected - elapsed; + printf("progress: %.2f%%, remaining: %.1f minutes\n", + progress * 100, remaining / 60); + } + } + printf("--- end test all float ---\n"); +} + + + +// ============================================================================= +// Test input types and flags +// ============================================================================= + +/// Test reader with different parameters. +static void test_read_params(void) { + yyjson_val ival; + yyjson_mut_val mval; + const char *ptr; + + ptr = yyjson_read_number(NULL, &ival, 0, NULL, NULL); + yy_assertf(ptr == NULL, "read line NULL should fail\n"); + ptr = yyjson_read_number("123", NULL, 0, NULL, NULL); + yy_assertf(ptr == NULL, "read val NULL should fail\n"); + ptr = yyjson_read_number(NULL, NULL, 0, NULL, NULL); + yy_assertf(ptr == NULL, "read line and val NULL should fail\n"); + + ptr = yyjson_mut_read_number(NULL, &mval, 0, NULL, NULL); + yy_assertf(ptr == NULL, "read line NULL should fail\n"); + ptr = yyjson_mut_read_number("123", NULL, 0, NULL, NULL); + yy_assertf(ptr == NULL, "read val NULL should fail\n"); + ptr = yyjson_mut_read_number(NULL, NULL, 0, NULL, NULL); + yy_assertf(ptr == NULL, "read line and val NULL should fail\n"); +} + +/// Test all combinations of number types and flags. +static void test_read_flags(void) { + + /// all number types const char *num_arr[] = { "0", // uint "-0", // sint @@ -884,40 +962,91 @@ static void test_num_types(void) { "NaN", // nan "Inf", // inf + "-Infinity", // -inf "001", // fail }; + /// all number flags yyjson_read_flag flag_arr[] = { YYJSON_READ_NUMBER_AS_RAW, YYJSON_READ_BIGNUM_AS_RAW, -#if !YYJSON_DISABLE_NON_STANDARD YYJSON_READ_ALLOW_INF_AND_NAN, -#endif }; + /// test number type for (usize i = 0; i < yy_nelems(num_arr); i++) { const char *num_str = num_arr[i]; usize num_len = strlen(num_str); - num_type type = check_json_num(num_str); + num_type type = get_num_type(num_str); - // test flag combination + /// test flag combination u32 flag_count = (u32)yy_nelems(flag_arr); u32 comb_count = 1 << flag_count; for (u32 c = 0; c < comb_count; c++) { - yyjson_read_flag comb_flag = 0; + yyjson_read_flag flg = 0; for (u32 f = 0; f < flag_count; f++) { - if (c & (1 << f)) comb_flag |= flag_arr[f]; + if (c & (1 << f)) flg |= flag_arr[f]; } - yyjson_doc *doc = yyjson_read(num_str, num_len, comb_flag); + /// doc read + yyjson_doc *doc = yyjson_read(num_str, num_len, flg); yyjson_val *val = yyjson_doc_get_root(doc); + + { /// val read + yyjson_val val2; + const char *end = yyjson_read_number(num_str, &val2, flg, NULL, NULL); + if (val) { + yy_assert(yyjson_equals(val, &val2)); + yy_assert(end && *end == '\0'); + } else { + yy_assert(!end); + } + } + { /// mut val read + yyjson_mut_val val2; + const char *end = yyjson_mut_read_number(num_str, &val2, flg, NULL, NULL); + if (val) { + yy_assert(yyjson_equals(val, (yyjson_val *)&val2)); + yy_assert(end && *end == '\0'); + } else { + yy_assert(!end); + } + } + { /// minity format read + usize buf_len = num_len + 2; + char *buf = calloc(1, buf_len + 1); + buf[0] = '['; + memcpy(buf + 1, num_str, num_len); + buf[num_len + 1] = ']'; + yyjson_doc *doc2 = yyjson_read(buf, buf_len, flg); + yyjson_val *val2 = yyjson_arr_get_first(yyjson_doc_get_root(doc2)); + yy_assert(val == val2 || yyjson_equals(val, val2)); + yyjson_doc_free(doc2); + free(buf); + } + { /// pretty format read + usize buf_len = num_len + 6; + char *buf = calloc(1, buf_len + 1); + memcpy(buf, "[\n ", 4); + memcpy(buf + 4, num_str, num_len); + memcpy(buf + 4 + num_len, "\n]", 2); + yyjson_doc *doc2 = yyjson_read(buf, buf_len, flg); + yyjson_val *val2 = yyjson_arr_get_first(yyjson_doc_get_root(doc2)); + yy_assert(val == val2 || yyjson_equals(val, val2)); + yyjson_doc_free(doc2); + free(buf); + } + +#if YYJSON_DISABLE_NON_STANDARD + flg &= ~YYJSON_READ_ALLOW_INF_AND_NAN; +#endif if (type == NUM_TYPE_FAIL) { - // invalid number format + /// invalid number format yy_assert(!doc); - } else if (comb_flag & YYJSON_READ_NUMBER_AS_RAW) { - // all number should be raw - if (type == NUM_TYPE_INF_NAN_LITERAL && - !(comb_flag & YYJSON_READ_ALLOW_INF_AND_NAN)) { + } else if (flg & YYJSON_READ_NUMBER_AS_RAW) { + /// all number should be raw + if (type == NUM_TYPE_LITERAL && + !(flg & YYJSON_READ_ALLOW_INF_AND_NAN)) { yy_assert(!doc); } else { yy_assert(yyjson_is_raw(val)); @@ -926,24 +1055,24 @@ static void test_num_types(void) { } else switch (type) { case NUM_TYPE_SINT: case NUM_TYPE_UINT: { - // integer number format + /// integer number format if (!check_int_overflow(num_str, type)) { - // integer number not overflow + /// integer number not overflow yy_assert(yyjson_is_int(val)); } else if (!check_real_overflow(num_str, type)) { - // integer number overflow, but real number not overflow - if (comb_flag & YYJSON_READ_BIGNUM_AS_RAW) { + /// integer number overflow, but real number not overflow + if (flg & YYJSON_READ_BIGNUM_AS_RAW) { yy_assert(yyjson_is_raw(val)); yy_assert(!strcmp(num_str, yyjson_get_raw(val))); } else { yy_assert(yyjson_is_real(val)); } } else { - // real number overflow - if (comb_flag & YYJSON_READ_BIGNUM_AS_RAW) { + /// real number overflow + if (flg & YYJSON_READ_BIGNUM_AS_RAW) { yy_assert(yyjson_is_raw(val)); yy_assert(!strcmp(num_str, yyjson_get_raw(val))); - } else if (comb_flag & YYJSON_READ_ALLOW_INF_AND_NAN) { + } else if (flg & YYJSON_READ_ALLOW_INF_AND_NAN) { yy_assert(yyjson_is_real(val)); } else { yy_assert(!doc); @@ -952,16 +1081,16 @@ static void test_num_types(void) { break; } case NUM_TYPE_REAL: { - // real number + /// real number if (!check_real_overflow(num_str, type)) { - // real number not overflow + /// real number not overflow yy_assert(yyjson_is_real(val)); } else { - // real number overflow - if (comb_flag & YYJSON_READ_BIGNUM_AS_RAW) { + /// real number overflow + if (flg & YYJSON_READ_BIGNUM_AS_RAW) { yy_assert(yyjson_is_raw(val)); yy_assert(!strcmp(num_str, yyjson_get_raw(val))); - } else if (comb_flag & YYJSON_READ_ALLOW_INF_AND_NAN) { + } else if (flg & YYJSON_READ_ALLOW_INF_AND_NAN) { yy_assert(yyjson_is_real(val)); } else { yy_assert(!doc); @@ -969,14 +1098,9 @@ static void test_num_types(void) { } break; } - case NUM_TYPE_INF_NAN_LITERAL: { - if ((comb_flag & YYJSON_READ_ALLOW_INF_AND_NAN)) { - if (comb_flag & YYJSON_READ_BIGNUM_AS_RAW) { - yy_assert(yyjson_is_raw(val)); - yy_assert(!strcmp(num_str, yyjson_get_raw(val))); - } else { - yy_assert(yyjson_is_real(val)); - } + case NUM_TYPE_LITERAL: { + if ((flg & YYJSON_READ_ALLOW_INF_AND_NAN)) { + yy_assert(yyjson_is_real(val)); } else { yy_assert(!doc); } @@ -987,77 +1111,200 @@ static void test_num_types(void) { } } - { // check minity format - usize buf_len = num_len + 2; - char *buf = calloc(1, buf_len + 1); - buf[0] = '['; - memcpy(buf + 1, num_str, num_len); - buf[num_len + 1] = ']'; - yyjson_doc *doc2 = yyjson_read(buf, buf_len, comb_flag); - yyjson_val *val2 = yyjson_arr_get_first(yyjson_doc_get_root(doc2)); - yy_assert(val == val2 || yyjson_equals(val, val2)); - yyjson_doc_free(doc2); - free(buf); - } - { // check pretty format - usize buf_len = num_len + 6; - char *buf = calloc(1, buf_len + 1); - memcpy(buf, "[\n ", 4); - memcpy(buf + 4, num_str, num_len); - memcpy(buf + 4 + num_len, "\n]", 2); - yyjson_doc *doc2 = yyjson_read(buf, buf_len, comb_flag); - yyjson_val *val2 = yyjson_arr_get_first(yyjson_doc_get_root(doc2)); - yy_assert(val == val2 || yyjson_equals(val, val2)); - yyjson_doc_free(doc2); - free(buf); - } - yyjson_doc_free(doc); } } } +/// Test all combinations of number types and flags. +static void test_write_flags(void) { + const u32 prec = 3; + yyjson_write_flag flag_arr[] = { + YYJSON_WRITE_ALLOW_INF_AND_NAN, + YYJSON_WRITE_INF_AND_NAN_AS_NULL, + YYJSON_WRITE_FP_TO_FLOAT, + YYJSON_WRITE_FP_TO_FIXED(3), + }; + + /// test flag combination + u32 flag_count = (u32)yy_nelems(flag_arr); + u32 comb_count = 1 << flag_count; + for (u32 c = 0; c < comb_count; c++) { + yyjson_write_flag flg = 0; + for (u32 f = 0; f < flag_count; f++) { + if (c & (1 << f)) flg |= flag_arr[f]; + } + bool allow_inf_nan = (flg & YYJSON_WRITE_ALLOW_INF_AND_NAN); + bool inf_nan_as_null = (flg & YYJSON_WRITE_INF_AND_NAN_AS_NULL); + bool to_float = (flg & YYJSON_WRITE_FP_TO_FLOAT) != 0; + bool to_fixed = (flg >> (32 - YYJSON_WRITE_FP_PREC_BITS)) != 0; + + f64 num64 = 0.12345678901234567; + f32 num32 = (f32)num64; + yyjson_val val = { 0 }; + char *str; + + /// int + yyjson_set_int(&val, 321); + str = yyjson_val_write(&val, flg, NULL); + yy_assert(get_num_type(str) == NUM_TYPE_UINT); + free(str); + + /// float + yyjson_set_float(&val, num32); + str = yyjson_val_write(&val, flg, NULL); + validate_real_output(str, &val, flg); + free(str); + + /// float to fixed + yyjson_set_float(&val, num32); + yyjson_set_fp_to_fixed(&val, prec + 1); + str = yyjson_val_write(&val, flg, NULL); + validate_real_output(str, &val, flg); + free(str); + + /// double + yyjson_set_double(&val, num64); + str = yyjson_val_write(&val, flg, NULL); + validate_real_output(str, &val, flg); + free(str); + + /// double to fixed + yyjson_set_double(&val, num64); + yyjson_set_fp_to_fixed(&val, prec + 1); + str = yyjson_val_write(&val, flg, NULL); + validate_real_output(str, &val, flg); + free(str); + + /// inf + num64 = INFINITY; + yyjson_set_double(&val, num64); + str = yyjson_val_write(&val, flg, NULL); + validate_real_output(str, &val, flg); + free(str); + + /// inf to fixed + num64 = INFINITY; + yyjson_set_double(&val, num64); + yyjson_set_fp_to_fixed(&val, prec + 1); + str = yyjson_val_write(&val, flg, NULL); + validate_real_output(str, &val, flg); + free(str); + + /// float inf + num64 = 1e100; + yyjson_set_double(&val, num64); + str = yyjson_val_write(&val, flg, NULL); + validate_real_output(str, &val, flg); + free(str); + + /// float inf to fixed + num64 = 1e100; + yyjson_set_double(&val, num64); + yyjson_set_fp_to_fixed(&val, prec + 1); + str = yyjson_val_write(&val, flg, NULL); + validate_real_output(str, &val, flg); + free(str); + } + + { /// set val format + yyjson_val val = { 0 }; + + /// set float/double + yyjson_set_float(&val, (float)1.23); + yy_assert(yyjson_is_real(&val)); + yy_assert((float)yyjson_get_real(&val) == (float)1.23); + yy_assert((val.tag >> 32) == YYJSON_WRITE_FP_TO_FLOAT); + yyjson_set_double(&val, 1.23); + yy_assert(yyjson_is_real(&val)); + yy_assert(yyjson_get_real(&val) == 1.23); + yy_assert((val.tag >> 32) == 0); + + /// set to fixed + yyjson_set_fp_to_fixed(&val, 12); + yy_assert(yyjson_is_real(&val)); + yy_assert((val.tag >> 32) == YYJSON_WRITE_FP_TO_FIXED(12)); + yyjson_set_fp_to_fixed(&val, 0); + yy_assert(yyjson_is_real(&val)); + yy_assert((val.tag >> 32) == YYJSON_WRITE_FP_TO_FIXED(0)); + + /// set to float + yyjson_set_fp_to_float(&val, true); + yy_assert(yyjson_is_real(&val)); + yy_assert((val.tag >> 32) == YYJSON_WRITE_FP_TO_FLOAT); + yyjson_set_fp_to_float(&val, false); + yy_assert(yyjson_is_real(&val)); + yy_assert((val.tag >> 32) == 0); + } + { /// set mut val format + yyjson_mut_val val = { 0 }; + + /// set float/double + yyjson_mut_set_float(&val, (float)1.23); + yy_assert(yyjson_mut_is_real(&val)); + yy_assert((float)yyjson_mut_get_real(&val) == (float)1.23); + yy_assert((val.tag >> 32) == YYJSON_WRITE_FP_TO_FLOAT); + yyjson_mut_set_double(&val, 1.23); + yy_assert(yyjson_mut_is_real(&val)); + yy_assert(yyjson_mut_get_real(&val) == 1.23); + yy_assert((val.tag >> 32) == 0); + + /// set to fixed + yyjson_mut_set_fp_to_fixed(&val, 12); + yy_assert(yyjson_mut_is_real(&val)); + yy_assert((val.tag >> 32) == YYJSON_WRITE_FP_TO_FIXED(12)); + yyjson_mut_set_fp_to_fixed(&val, 0); + yy_assert(yyjson_mut_is_real(&val)); + yy_assert((val.tag >> 32) == YYJSON_WRITE_FP_TO_FIXED(0)); + + /// set to float + yyjson_mut_set_fp_to_float(&val, true); + yy_assert(yyjson_mut_is_real(&val)); + yy_assert((val.tag >> 32) == YYJSON_WRITE_FP_TO_FLOAT); + yyjson_mut_set_fp_to_float(&val, false); + yy_assert(yyjson_mut_is_real(&val)); + yy_assert((val.tag >> 32) == 0); + } +} + -/*============================================================================== - * Test all - *============================================================================*/ + +// ============================================================================= +// Test locale +// ============================================================================= static void test_number_locale(void) { - test_with_file("num_fail.txt", NUM_TYPE_FAIL); - test_with_file("uint_pass.txt", NUM_TYPE_UINT); - test_with_file("sint_pass.txt", NUM_TYPE_SINT); - test_with_file("uint_bignum.txt", NUM_TYPE_REAL); - test_with_file("sint_bignum.txt", NUM_TYPE_REAL); - test_with_file("real_pass_1.txt", NUM_TYPE_REAL); - test_with_file("real_pass_2.txt", NUM_TYPE_REAL); - test_with_file("real_pass_3.txt", NUM_TYPE_REAL); - test_with_file("real_pass_4.txt", NUM_TYPE_REAL); - test_with_file("real_pass_5.txt", NUM_TYPE_REAL); - test_with_file("real_pass_6.txt", NUM_TYPE_REAL); - test_with_file("real_pass_7.txt", NUM_TYPE_REAL); - test_with_file("nan_inf_literal_pass.txt", NUM_TYPE_INF_NAN_LITERAL); - test_with_file("nan_inf_literal_fail.txt", NUM_TYPE_FAIL); + test_read_params(); + test_read_flags(); + test_write_flags(); + test_all_files(); +} + +static void test_number_extra(void) { test_random_int(); test_random_real(); test_special_real(); - test_num_types(); + +#if YYJSON_TEST_ALL_FLOAT || 0 + test_all_float(); /// costs too much time, disabled for regular testing +#endif } yy_test_case(test_number) { - setlocale(LC_ALL, "C"); // decimal point is '.' + /// change locale (decimal point is ',') + setlocale(LC_ALL, "fr_FR"); + update_locale_decimal_point(); test_number_locale(); - setlocale(LC_ALL, "fr_FR"); // decimal point is ',' + + /// reset locale (decimal point is '.') + setlocale(LC_ALL, "C"); + update_locale_decimal_point(); test_number_locale(); + + /// test some extra numbers + test_number_extra(); } - #else yy_test_case(test_number) {} #endif - - -#if defined(__clang__) -# pragma clang diagnostic pop -#elif defined(__GNUC__) -# pragma GCC diagnostic pop -#endif diff --git a/test/util/goo_double_conv.c b/test/util/goo_double_conv.c index c7dac89..994d781 100644 --- a/test/util/goo_double_conv.c +++ b/test/util/goo_double_conv.c @@ -1,6 +1,6 @@ -// Source code from: https://github.com/google/double-conversion (2022-01-31) -// The code was rewritten from C++ into a single C file for easier integration. - +// Source code from: https://github.com/google/double-conversion (v3.3.0) +// Rewritten from C++ to a single C file for easier integration. +// Original code released under BSD 3-Clause, see full license below. // Copyright 2006-2011 the V8 project authors. All rights reserved. @@ -91,23 +91,23 @@ static void fp_force_eval(double x) { static const double toint = 1/EPS; static double fp_ceil(double x) { - union {double f; uint64_t i;} u; + uint64_t u; memcpy((void *)&u, (void *)&x, sizeof(uint64_t)); - int e = u.i >> 52 & 0x7ff; + int e = u >> 52 & 0x7ff; double y; if (e >= 0x3ff+52 || x == 0) return x; /* y = int(x) - x, where int(x) is an integer neighbor of x */ - if (u.i >> 63) + if (u >> 63) y = x - toint + toint - x; else y = x + toint - toint - x; /* special case because of non-nearest rounding modes */ if (e <= 0x3ff-1) { fp_force_eval(y); - return u.i >> 63 ? -0.0 : 1; + return u >> 63 ? -0.0 : 1; } if (y < 0) return x + y + 1; @@ -128,6 +128,13 @@ static double fp_ceil(double x) { #define MIN(a, b) ((a) < (b) ? (a) : (b)) #endif +// For C++11 and C23 compatibility +#if __cplusplus >= 201103L || __STDC_VERSION__ >= 202311L +#define DOUBLE_CONVERSION_NULLPTR nullptr +#else +#define DOUBLE_CONVERSION_NULLPTR NULL +#endif + // Use DOUBLE_CONVERSION_NON_PREFIXED_MACROS to get unprefixed macros as was // the case in double-conversion releases prior to 3.1.6 @@ -298,7 +305,7 @@ typedef struct Vector { } Vector; static void Vector_init(Vector *vec, char *data, int len) { - DOUBLE_CONVERSION_ASSERT(len == 0 || (len > 0 && data != NULL)); + DOUBLE_CONVERSION_ASSERT(len == 0 || (len > 0 && data != DOUBLE_CONVERSION_NULLPTR)); vec->start = data; vec->length = len; } @@ -417,7 +424,7 @@ static void StringBuilder_AddCharacter(StringBuilder *sb, char c) { static void StringBuilder_AddSubstring(StringBuilder *sb, const char* s, int n) { DOUBLE_CONVERSION_ASSERT(!StringBuilder_is_finalized(sb) && sb->position + n < sb->buffer.length); DOUBLE_CONVERSION_ASSERT((size_t)(n) <= strlen(s)); - memmove(sb->buffer.start + sb->position, s, n); + memmove(sb->buffer.start + sb->position, s, (size_t)n); sb->position += n; } @@ -1062,8 +1069,8 @@ static void Bignum_Align(Bignum *b, Bignum *other) { for (int i = 0; i < zero_bigits; ++i) { *Bignum_RawBigit(b, i) = 0; } - b->used_bigits += zero_bigits; - b->exponent -= zero_bigits; + b->used_bigits += (int16_t)zero_bigits; + b->exponent -= (int16_t)zero_bigits; DOUBLE_CONVERSION_ASSERT(b->used_bigits >= 0); DOUBLE_CONVERSION_ASSERT(b->exponent >= 0); @@ -1332,7 +1339,7 @@ static void Bignum_ShiftLeft(Bignum *b, int shift_amount) { if (b->used_bigits == 0) { return; } - b->exponent += (shift_amount / Bignum_kBigitSize); + b->exponent += (int16_t)(shift_amount / Bignum_kBigitSize); const int local_shift = shift_amount % Bignum_kBigitSize; Bignum_EnsureCapacity(b->used_bigits + 1); Bignum_BigitsShiftLeft(b, local_shift); @@ -1491,7 +1498,7 @@ static void Bignum_AssignHexString(Bignum *b, Vector *value) { } if (tmp > 0) { DOUBLE_CONVERSION_ASSERT(tmp <= Bignum_kBigitMask); - *Bignum_RawBigit(b, b->used_bigits++) = (tmp & Bignum_kBigitMask); + *Bignum_RawBigit(b, b->used_bigits++) = (Bignum_Chunk)(tmp & Bignum_kBigitMask); } Bignum_Clamp(b); } @@ -5266,7 +5273,9 @@ typedef enum D2S_Flags { D2S_EMIT_TRAILING_DECIMAL_POINT = 2, D2S_EMIT_TRAILING_ZERO_AFTER_POINT = 4, D2S_UNIQUE_ZERO = 8, - D2S_NO_TRAILING_ZERO = 16 + D2S_NO_TRAILING_ZERO = 16, + EMIT_TRAILING_DECIMAL_POINT_IN_EXPONENTIAL = 32, + EMIT_TRAILING_ZERO_AFTER_POINT_IN_EXPONENTIAL = 64 } D2S_Flags; // Flags should be a bit-or combination of the possible Flags-enum. @@ -5285,6 +5294,13 @@ typedef enum D2S_Flags { // of the result in precision mode. Matches printf's %g. // When EMIT_TRAILING_ZERO_AFTER_POINT is also given, one trailing zero is // preserved. +// - EMIT_TRAILING_DECIMAL_POINT_IN_EXPONENTIAL: when the input number has +// exactly one significant digit and is converted into exponent form then a +// trailing decimal point is appended to the significand in shortest mode +// or in precision mode with one requested digit. +// - EMIT_TRAILING_ZERO_AFTER_POINT_IN_EXPONENTIAL: in addition to a trailing +// decimal point emits a trailing '0'-character. This flag requires the +// EMIT_TRAILING_DECIMAL_POINT_IN_EXPONENTIAL flag. // // Infinity symbol and nan_symbol provide the string representation for these // special values. If the string is NULL and the special value is encountered @@ -5320,6 +5336,22 @@ typedef enum D2S_Flags { // ToPrecision(230.0, 2) -> "230." with EMIT_TRAILING_DECIMAL_POINT. // ToPrecision(230.0, 2) -> "2.3e2" with EMIT_TRAILING_ZERO_AFTER_POINT. // +// When converting numbers with exactly one significant digit to exponent +// form in shortest mode or in precision mode with one requested digit, the +// EMIT_TRAILING_DECIMAL_POINT and EMIT_TRAILING_ZERO_AFTER_POINT flags have +// no effect. Use the EMIT_TRAILING_DECIMAL_POINT_IN_EXPONENTIAL flag to +// append a decimal point in this case and the +// EMIT_TRAILING_ZERO_AFTER_POINT_IN_EXPONENTIAL flag to also append a +// '0'-character in this case. +// Example with decimal_in_shortest_low = 0: +// ToShortest(0.0009) -> "9e-4" +// with EMIT_TRAILING_DECIMAL_POINT_IN_EXPONENTIAL deactivated. +// ToShortest(0.0009) -> "9.e-4" +// with EMIT_TRAILING_DECIMAL_POINT_IN_EXPONENTIAL activated. +// ToShortest(0.0009) -> "9.0e-4" +// with EMIT_TRAILING_DECIMAL_POINT_IN_EXPONENTIAL activated and +// EMIT_TRAILING_ZERO_AFTER_POINT_IN_EXPONENTIAL activated. +// // The min_exponent_width is used for exponential representations. // The converter adds leading '0's to the exponent until the exponent // is at least min_exponent_width digits long. @@ -5641,7 +5673,7 @@ static bool D2S_HandleSpecialValues(DoubleToStringConverter *conv, StringBuilder *sb) { Double double_inspect = Double_make(value); if (Double_IsInfinite(&double_inspect)) { - if (conv->infinity_symbol == NULL) return false; + if (conv->infinity_symbol == DOUBLE_CONVERSION_NULLPTR) return false; if (value < 0) { StringBuilder_AddCharacter(sb, '-'); } @@ -5649,7 +5681,7 @@ static bool D2S_HandleSpecialValues(DoubleToStringConverter *conv, return true; } if (Double_IsNan(&double_inspect)) { - if (conv->nan_symbol == NULL) return false; + if (conv->nan_symbol == DOUBLE_CONVERSION_NULLPTR) return false; StringBuilder_AddString(sb, conv->nan_symbol); return true; } @@ -5663,7 +5695,14 @@ static void D2S_CreateExponentialRepresentation(DoubleToStringConverter *conv, StringBuilder *sb) { DOUBLE_CONVERSION_ASSERT(length != 0); StringBuilder_AddCharacter(sb, decimal_digits[0]); - if (length != 1) { + if (length == 1) { + if ((conv->flags & EMIT_TRAILING_DECIMAL_POINT_IN_EXPONENTIAL) != 0) { + StringBuilder_AddCharacter(sb, '.'); + if ((conv->flags & EMIT_TRAILING_ZERO_AFTER_POINT_IN_EXPONENTIAL) != 0) { + StringBuilder_AddCharacter(sb, '0'); + } + } + } else { StringBuilder_AddCharacter(sb, '.'); StringBuilder_AddSubstring(sb, &decimal_digits[1], length-1); } @@ -6029,23 +6068,36 @@ static void D2S_DoubleToAscii(double v, /// C wrapper /// ============================================================================ -int goo_dtoa(double val, char *buf, int len) { - if (!buf || len <= 1) return 0; +static int imp_dtoa(bool is_double, double val, goo_fmt fmt, int prec, char *buf, int len) { + if (!buf || len < 1) return 0; StringBuilder sb = StringBuilder_make(buf, len); + DoubleToStringConverter conv = D2S_EcmaScriptConverter; conv.flags = D2S_EMIT_TRAILING_DECIMAL_POINT | D2S_EMIT_TRAILING_ZERO_AFTER_POINT; - if (!D2S_ToShortest(&conv, val, &sb)) return 0; + if (fmt == GOO_FMT_SHORTEST) { + if (is_double) { + if (!D2S_ToShortest(&conv, val, &sb)) return 0; + } else { + if (!D2S_ToShortestSingle(&conv, (float)val, &sb)) return 0; + } + } else if (fmt == GOO_FMT_FIXED) { + if (!D2S_ToFixed(&conv, val, prec, &sb)) return 0; + } else if (fmt == GOO_FMT_PRECISION) { + if (!D2S_ToPrecision(&conv, val, prec, &sb)) return 0; + } else if (fmt == GOO_FMT_EXPONENTIAL) { + if (!D2S_ToExponential(&conv, val, prec, &sb)) return 0; + } + int pos = sb.position; if (pos >= len) return 0; buf[pos] = '\0'; return pos; } -double goo_strtod(const char *str, int *len) { - if (!len) return 0.0; - int input_len = *len; - *len = 0; - if (!str || input_len < 1) return 0.0; +double imp_strtod(bool is_double, const char *str, int len, int *proc_out) { + if (proc_out) *proc_out = 0; + if (!str || !len) return 0.0; + int proc = 0; StringToDoubleConverter conv; conv.flags = @@ -6061,36 +6113,34 @@ double goo_strtod(const char *str, int *len) { conv.nan_symbol = "nan"; conv.separator = '\0'; - int processed = 0; - double val = StringToDouble(&conv, str, input_len, &processed); - if (processed == 0) { - *len = 0; + double val = StringToIeee(&conv, str, len, is_double, &proc); + if (proc == 0) { + if (proc_out) *proc_out = proc; return 0.0; } // process "infinity" literal Double d = Double_make(val); if (Double_IsInfinite(&d)) { - for (int i = 0; i < processed; i++) { - const char *full = "infinity"; - int full_len = (int)strlen(full); + const char *cur = str; + while (S2D_isWhitespace(*cur)) cur++; + if (*cur == '-' || *cur == '+') cur++; + + const char *full = "infinity"; + int full_len = (int)strlen(full); + int full_proc = (int)(cur - str) + full_len; + if (full_proc <= len) { bool full_match = true; - for (int j = 0; j < full_len; j++) { - if (S2D_ToLower(str[i + j]) != full[j]) { - full_match = false; - break; - } - } - if (full_match) { - processed += (int)(strlen(full) - strlen(conv.infinity_symbol)); - break; + for (int i = 0; i < full_len; i++) { + if (S2D_ToLower(cur[i]) != full[i]) full_match = false; } + if (full_match) proc = full_proc; } } // process -0.0 - if (val == 0.0) { - for (int i = 0; i < processed; i++) { + if (d.d64 == 0) { + for (int i = 0; i < proc; i++) { if (!S2D_isWhitespace(str[i])) { if (str[i] == '-') val = -0.0; break; @@ -6098,10 +6148,26 @@ double goo_strtod(const char *str, int *len) { } } - *len = processed; + if (proc_out) *proc_out = proc; return val; } +int goo_dtoa(double val, goo_fmt fmt, int prec, char *buf, int len) { + return imp_dtoa(true, val, fmt, prec, buf, len); +} + +int goo_ftoa(float val, goo_fmt fmt, int prec, char *buf, int len) { + return imp_dtoa(false, val, fmt, prec, buf, len); +} + +double goo_strtod(const char *str, int len, int *proc) { + return imp_strtod(true, str, len, proc); +} + +float goo_strtof(const char *str, int len, int *proc) { + return (float)imp_strtod(false, str, len, proc); +} + /// ============================================================================ diff --git a/test/util/goo_double_conv.h b/test/util/goo_double_conv.h index 80a26b2..0583d15 100644 --- a/test/util/goo_double_conv.h +++ b/test/util/goo_double_conv.h @@ -1,35 +1,55 @@ #ifndef goo_double_conv_h #define goo_double_conv_h -#include - /// IEEE 754 floating-point binary representation detection. -/// The functions below may produce incorrect results if GOO_HAS_IEEE_754 == 0. +/// The functions below may produce incorrect results if `GOO_HAS_IEEE_754 == 0`. +#include #if defined(__STDC_IEC_559__) || defined(__STDC_IEC_60559_BFP__) # define GOO_HAS_IEEE_754 1 -#elif (FLT_RADIX == 2) && (DBL_MANT_DIG == 53) && (DBL_DIG == 15) && \ - (DBL_MIN_EXP == -1021) && (DBL_MAX_EXP == 1024) && \ - (DBL_MIN_10_EXP == -307) && (DBL_MAX_10_EXP == 308) +#elif FLT_RADIX == 2 && \ + FLT_MANT_DIG == 24 && FLT_DIG == 6 && \ + FLT_MIN_EXP == -125 && FLT_MAX_EXP == 128 && \ + FLT_MIN_10_EXP == -37 && FLT_MAX_10_EXP == 38 && \ + DBL_MANT_DIG == 53 && DBL_DIG == 15 && \ + DBL_MIN_EXP == -1021 && DBL_MAX_EXP == 1024 && \ + DBL_MIN_10_EXP == -307 && DBL_MAX_10_EXP == 308 # define GOO_HAS_IEEE_754 1 #else # define GOO_HAS_IEEE_754 0 #endif -/// Convert double number to shortest string (with null-terminator). +/// Number to string format. +typedef enum { + /// Shortest string, `prec` is ignored. + GOO_FMT_SHORTEST, + /// Fixed-point notation, `prec` is the number of digits after decimal point. + GOO_FMT_FIXED, + /// Precision notation, `prec` is the number of significant digits. + GOO_FMT_PRECISION, + /// Exponential notation, `prec` is the number of digits after decimal point. + GOO_FMT_EXPONENTIAL +} goo_fmt; + +/// Write double number to string (null-terminated). /// The string format follows the ECMAScript spec with the following changes: -/// 1. Keep the negative sign of 0.0 to preserve input information. +/// 1. Keep the negative sign of `-0.0` to preserve input information. /// 2. Keep decimal point to indicate the number is floating point. /// 3. Remove positive sign of exponent part. -/// @param val A double value. -/// @param buf A string buffer to receive output. +/// @param val The double value. +/// @param fmt The output format (pass NULL for shortest format). +/// @param prec The precision value for the `fmt`. +/// @param buf The string buffer for the output. /// @param len The string buffer length. -/// @return The string length, or 0 if failed. -int goo_dtoa(double val, char *buf, int len); +/// @return The output string length, or 0 if failed. +int goo_dtoa(double val, goo_fmt fmt, int prec, char *buf, int len); +int goo_ftoa(float val, goo_fmt fmt, int prec, char *buf, int len); -/// Read double number from string, support same format as libc's strtod(). -/// @param str A string with double number. -/// @param len In: the string length. Out: the processed length, or 0 if failed. +/// Read double number from string. +/// @param str The string containing a double number. +/// @param len The string length. +/// @param proc The processed length, or 0 if failed (pass NULL to ignore). /// @return The double value, or 0.0 if failed. -double goo_strtod(const char *str, int *len); +double goo_strtod(const char *str, int len, int *proc); +float goo_strtof(const char *str, int len, int *proc); #endif /* goo_double_conv_h */ diff --git a/test/util/yy_test_utils.c b/test/util/yy_test_utils.c index da1915c..d4fec5c 100644 --- a/test/util/yy_test_utils.c +++ b/test/util/yy_test_utils.c @@ -762,3 +762,73 @@ char *yy_dat_copy_line(yy_dat *dat, usize *len) { if (len) *len = _len; return str; } + + + +/*============================================================================== + * Time Utils + *============================================================================*/ + +#ifdef __APPLE__ +#include +#endif + +double yy_get_time(void) { +#if defined(_WIN32) + // Available since Windows 2000. + // precision: 1e-6 seconds (1us) + LARGE_INTEGER counter; + LARGE_INTEGER freq; + QueryPerformanceCounter(&counter); + QueryPerformanceFrequency(&freq); + return (double)counter.QuadPart / (double)freq.QuadPart; + +#elif defined(__APPLE__) + // mach_timebase_info is stable + static mach_timebase_info_data_t clock_timebase = { 0 }; + if (!clock_timebase.denom) { + mach_timebase_info(&clock_timebase); + } + uint64_t t = mach_absolute_time(); + return ((double)t * clock_timebase.numer) / clock_timebase.denom / 1e9; + +#else +# if defined(CLOCK_MONOTONIC) + // Elapsed wall-clock time, monotonic. + // https://man7.org/linux/man-pages/man2/clock_gettime.2.html + // https://man.freebsd.org/cgi/man.cgi?query=clock_gettime + struct timespec ts; + if (clock_gettime(CLOCK_MONOTONIC, &ts) == 0) { // Linux/BSD + return (double)ts.tv_sec + ts.tv_nsec / 1e9; + } +# endif + // fallback... + // Available since POSIX Issue 4, . + // precision: 1e-6 seconds (1us) + struct timeval now; + if (gettimeofday(&now, NULL) == -1) return 0; + return (double)now.tv_sec + (double)now.tv_usec * 1e-6; +#endif +} + +double yy_get_timestamp(void) { +#ifdef _WIN32 + // Available since Windows 2000. + // precision: 1e-3 seconds (1ms) + FILETIME ft; + GetSystemTimeAsFileTime(&ft); + + ULARGE_INTEGER ui; + ui.LowPart = ft.dwLowDateTime; + ui.HighPart = ft.dwHighDateTime; + + long long t = ui.QuadPart; + return (double)t * 1e-7 - 11644473600.0; +#else + // Available since POSIX Issue 4, . + // precision: 1e-6 seconds (1us) + struct timeval now; + if (gettimeofday(&now, NULL) == -1) return 0; + return (double)now.tv_sec + (double)now.tv_usec * 1e-6; +#endif +} diff --git a/test/util/yy_test_utils.h b/test/util/yy_test_utils.h index 952acb5..cc13bc6 100644 --- a/test/util/yy_test_utils.h +++ b/test/util/yy_test_utils.h @@ -22,7 +22,23 @@ # include #else # include +# include # include +# include +#endif + +/* warning suppress for tests */ +#if defined(__clang__) +# pragma clang diagnostic ignored "-Wunused-function" +# pragma clang diagnostic ignored "-Wunused-parameter" +# pragma clang diagnostic ignored "-Wunused-variable" +#elif defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wunused-function" +# pragma GCC diagnostic ignored "-Wunused-parameter" +# pragma GCC diagnostic ignored "-Wunused-variable" +#elif defined(_MSC_VER) +# pragma warning(disable:4101) /* unused-parameter */ +# pragma warning(disable:4100) /* unused-variable */ #endif /* compiler builtin check (clang) */ @@ -391,6 +407,18 @@ char *yy_dat_copy_line(yy_dat *dat, usize *len); +/*============================================================================== + * Time Utils + *============================================================================*/ + +/**Get monotonic time in seconds (used to measure elapsed time). */ +double yy_get_time(void); + +/** Get UNIX timestamp in seconds since 1970 (system's wall clock). */ +double yy_get_timestamp(void); + + + #ifdef __cplusplus } #endif