Fix MSVC v3 C JSON float conversion
diff --git a/tinygltf_json_c.h b/tinygltf_json_c.h index abe3a2b..1469bc3 100644 --- a/tinygltf_json_c.h +++ b/tinygltf_json_c.h
@@ -129,6 +129,14 @@ #ifndef TINYGLTF_JSON_NO_STDLIB #include <stdlib.h> #include <string.h> + #include <stdio.h> + #include <float.h> +#if defined(_MSC_VER) || (defined(LDBL_MANT_DIG) && LDBL_MANT_DIG <= 53) +#define TG3JSON_USE_STDLIB_FPCONV 1 +#endif +#endif +#ifndef TG3JSON_USE_STDLIB_FPCONV +#define TG3JSON_USE_STDLIB_FPCONV 0 #endif #ifdef TINYGLTF_JSON_NO_STDLIB @@ -266,12 +274,6 @@ return bits; } -static double tg3json__double_from_bits(uint64_t bits) { - double v = 0.0; - TINYGLTF_JSON_MEMCPY(&v, &bits, sizeof(v)); - return v; -} - static int tg3json__is_nan_bits(uint64_t bits) { return ((bits & 0x7ff0000000000000ULL) == 0x7ff0000000000000ULL) && ((bits & 0x000fffffffffffffULL) != 0); @@ -281,6 +283,13 @@ return (bits & 0x7fffffffffffffffULL) == 0x7ff0000000000000ULL; } +#if !TG3JSON_USE_STDLIB_FPCONV +static double tg3json__double_from_bits(uint64_t bits) { + double v = 0.0; + TINYGLTF_JSON_MEMCPY(&v, &bits, sizeof(v)); + return v; +} + static long double tg3json__pow10_ld(int exp10) { long double v = 1.0L; if (exp10 < 0) { @@ -299,8 +308,30 @@ } return v; } +#endif static int tg3json__parse_f64_c(const char *start, const char *end, double *out) { +#if TG3JSON_USE_STDLIB_FPCONV + size_t len = (size_t)(end - start); + char stack_buf[128]; + char *buf = stack_buf; + char *parse_end = NULL; + double v; + if (len + 1 > sizeof(stack_buf)) { + buf = (char *)TINYGLTF_JSON_MALLOC(len + 1); + if (!buf) return 0; + } + if (len > 0) TINYGLTF_JSON_MEMCPY(buf, start, len); + buf[len] = '\0'; + v = strtod(buf, &parse_end); + if (parse_end != buf + len || tg3json__is_inf_bits(tg3json__double_bits(v))) { + if (buf != stack_buf) TINYGLTF_JSON_FREE(buf); + return 0; + } + if (buf != stack_buf) TINYGLTF_JSON_FREE(buf); + *out = v; + return 1; +#else const char *p = start; int neg = 0; int saw_digit = 0; @@ -399,6 +430,7 @@ *out = (double)v; if (tg3json__is_inf_bits(tg3json__double_bits(*out))) return 0; return 1; +#endif } static char *tg3json__write_exp(char *p, int exp10) { @@ -452,6 +484,97 @@ return tg3json__double_bits(a) == tg3json__double_bits(b); } +#if TG3JSON_USE_STDLIB_FPCONV +static void tg3json__normalize_exponent(char *s) { + char *e = s; + char *dst; + int neg = 0; + while (*e && *e != 'e' && *e != 'E') ++e; + if (!*e) return; + *e++ = 'e'; + dst = e; + if (*e == '+' || *e == '-') { + neg = (*e == '-'); + ++e; + } + while (*e == '0') ++e; + if (neg) *dst++ = '-'; + if (!*e) { + *dst++ = '0'; + } else { + while (*e) *dst++ = *e++; + } + *dst = '\0'; +} + +static void tg3json__expand_short_plain_decimal(char *s) { + char *p = s; + char *e; + char digits[32]; + char out[96]; + int negative = 0; + int ndigits = 0; + int int_digits = 0; + int exp10 = 0; + int new_point; + int i; + char *q = out; + + if (*p == '-') { + negative = 1; + ++p; + } + e = p; + while (*e && *e != 'e') ++e; + if (!*e) return; + for (; p < e; ++p) { + if (*p == '.') { + int_digits = ndigits; + } else { + if (ndigits < (int)(sizeof(digits) - 1)) digits[ndigits++] = *p; + } + } + if (int_digits == 0) int_digits = ndigits; + ++e; + if (*e == '-') { + int sign = -1; + ++e; + while (*e >= '0' && *e <= '9') exp10 = exp10 * 10 + (*e++ - '0'); + exp10 *= sign; + } else { + while (*e >= '0' && *e <= '9') exp10 = exp10 * 10 + (*e++ - '0'); + } + if (exp10 < -4 || exp10 >= 16) return; + + new_point = int_digits + exp10; + if (negative) *q++ = '-'; + if (new_point <= 0) { + *q++ = '0'; + *q++ = '.'; + for (i = 0; i < -new_point; ++i) *q++ = '0'; + for (i = 0; i < ndigits; ++i) *q++ = digits[i]; + } else if (new_point >= ndigits) { + for (i = 0; i < ndigits; ++i) *q++ = digits[i]; + for (i = ndigits; i < new_point; ++i) *q++ = '0'; + } else { + for (i = 0; i < new_point; ++i) *q++ = digits[i]; + *q++ = '.'; + for (; i < ndigits; ++i) *q++ = digits[i]; + } + *q = '\0'; + if (q > out) { + char *dot = out; + while (*dot && *dot != '.') ++dot; + if (*dot == '.') { + char *end = q - 1; + while (end > dot && *end == '0') *end-- = '\0'; + if (end == dot) *end = '\0'; + } + } + TINYGLTF_JSON_MEMCPY(s, out, (size_t)(TINYGLTF_JSON_STRLEN(out) + 1)); +} +#endif + static char *tg3json__dtoa_c(double value, char *buf) { uint64_t bits = tg3json__double_bits(value); int negative = (int)(bits >> 63); @@ -490,6 +613,39 @@ return buf; } +#if TG3JSON_USE_STDLIB_FPCONV + { + int precision; + char candidate[96]; + char shortest[96]; + shortest[0] = '\0'; + for (precision = 1; precision <= 17; ++precision) { + char fmt[8]; + double parsed = 0.0; + int n; + n = snprintf(fmt, sizeof(fmt), "%%.%dg", precision); + if (n <= 0 || n >= (int)sizeof(fmt)) continue; + n = snprintf(candidate, sizeof(candidate), fmt, value); + if (n <= 0 || n >= (int)sizeof(candidate)) continue; + tg3json__normalize_exponent(candidate); + tg3json__expand_short_plain_decimal(candidate); + if (tg3json__parse_f64_c(candidate, candidate + TINYGLTF_JSON_STRLEN(candidate), &parsed) && + tg3json__same_f64(parsed, value)) { + TINYGLTF_JSON_MEMCPY(shortest, candidate, TINYGLTF_JSON_STRLEN(candidate) + 1); + break; + } + } + if (!shortest[0]) { + int n = snprintf(shortest, sizeof(shortest), "%.17g", value); + if (n <= 0 || n >= (int)sizeof(shortest)) return buf; + tg3json__normalize_exponent(shortest); + tg3json__expand_short_plain_decimal(shortest); + } + TINYGLTF_JSON_MEMCPY(buf, shortest, TINYGLTF_JSON_STRLEN(shortest)); + return buf + TINYGLTF_JSON_STRLEN(shortest); + } +#endif + x = negative ? -(long double)value : (long double)value; while (x >= 1.0e16L) { x *= 1.0e-16L;