Ported protobuf's dtoa() function for text format and JSON.
diff --git a/upb/json_encode.c b/upb/json_encode.c index a63b3c6..64826e3 100644 --- a/upb/json_encode.c +++ b/upb/json_encode.c
@@ -38,6 +38,7 @@ #include "upb/decode.h" #include "upb/reflection.h" +#include "upb/upb_internal.h" /* Must be last. */ #include "upb/port_def.inc" @@ -305,7 +306,7 @@ jsonenc_putstr(e, "\""); } -static void jsonenc_double(jsonenc* e, const char* fmt, double val) { +static bool upb_JsonEncode_HandleSpecialDoubles(jsonenc* e, double val) { if (val == INFINITY) { jsonenc_putstr(e, "\"Infinity\""); } else if (val == -INFINITY) { @@ -313,18 +314,23 @@ } else if (val != val) { jsonenc_putstr(e, "\"NaN\""); } else { - char* p = e->ptr; - jsonenc_printf(e, fmt, val); - - /* printf() is dependent on locales; sadly there is no easy and portable way - * to avoid this. This little post-processing step will translate 1,2 -> 1.2 - * since JSON needs the latter. Arguably a hack, but it is simple and the - * alternatives are far more complicated, platform-dependent, and/or larger - * in code size. */ - for (char* end = e->ptr; p < end; p++) { - if (*p == ',') *p = '.'; - } + return false; } + return true; +} + +static void upb_JsonEncode_Double(jsonenc* e, double val) { + if (upb_JsonEncode_HandleSpecialDoubles(e, val)) return; + char buf[32]; + _upb_EncodeRoundTripDouble(val, buf, sizeof(buf)); + jsonenc_putstr(e, buf); +} + +static void upb_JsonEncode_Float(jsonenc* e, float val) { + if (upb_JsonEncode_HandleSpecialDoubles(e, val)) return; + char buf[32]; + _upb_EncodeRoundTripFloat(val, buf, sizeof(buf)); + jsonenc_putstr(e, buf); } static void jsonenc_wrapper(jsonenc* e, const upb_Message* msg, @@ -517,7 +523,7 @@ jsonenc_putstr(e, "null"); break; case 2: - jsonenc_double(e, "%.17g", val.double_val); + upb_JsonEncode_Double(e, val.double_val); break; case 3: jsonenc_string(e, val.str_val); @@ -582,10 +588,10 @@ jsonenc_putstr(e, val.bool_val ? "true" : "false"); break; case kUpb_CType_Float: - jsonenc_double(e, "%.9g", val.float_val); + upb_JsonEncode_Float(e, val.float_val); break; case kUpb_CType_Double: - jsonenc_double(e, "%.17g", val.double_val); + upb_JsonEncode_Double(e, val.double_val); break; case kUpb_CType_Int32: jsonenc_printf(e, "%" PRId32, val.int32_val);
diff --git a/upb/text_encode.c b/upb/text_encode.c index 5067a27..9612375 100644 --- a/upb/text_encode.c +++ b/upb/text_encode.c
@@ -35,6 +35,7 @@ #include <string.h> #include "upb/reflection.h" +#include "upb/upb_internal.h" // Must be last. #include "upb/port_def.inc" @@ -177,12 +178,18 @@ case kUpb_CType_Bool: txtenc_putstr(e, val.bool_val ? "true" : "false"); break; - case kUpb_CType_Float: - txtenc_printf(e, "%f", val.float_val); + case kUpb_CType_Float: { + char buf[32]; + _upb_EncodeRoundTripFloat(val.float_val, buf, sizeof(buf)); + txtenc_putstr(e, buf); break; - case kUpb_CType_Double: - txtenc_printf(e, "%f", val.double_val); + } + case kUpb_CType_Double: { + char buf[32]; + _upb_EncodeRoundTripDouble(val.double_val, buf, sizeof(buf)); + txtenc_putstr(e, buf); break; + } case kUpb_CType_Int32: txtenc_printf(e, "%" PRId32, val.int32_val); break;
diff --git a/upb/upb.c b/upb/upb.c index 43ca2fd..a702d48 100644 --- a/upb/upb.c +++ b/upb/upb.c
@@ -26,6 +26,7 @@ */ #include <errno.h> +#include <float.h> #include <stdarg.h> #include <stddef.h> #include <stdint.h> @@ -326,3 +327,37 @@ r2->parent = r1; return true; } + +/* Miscellaneous utilities ****************************************************/ + +static void upb_FixLocale(char* p) { + /* printf() is dependent on locales; sadly there is no easy and portable way + * to avoid this. This little post-processing step will translate 1,2 -> 1.2 + * since JSON needs the latter. Arguably a hack, but it is simple and the + * alternatives are far more complicated, platform-dependent, and/or larger + * in code size. */ + for (; *p; p++) { + if (*p == ',') *p = '.'; + } +} + + +void _upb_EncodeRoundTripDouble(double val, char* buf, size_t size) { + assert(size >= kUpb_RoundTripBufferSize); + snprintf(buf, size, "%.*g", DBL_DIG, val); + if (strtod(buf, NULL) != val) { + snprintf(buf, size, "%.*g", DBL_DIG + 2, val); + assert(strtod(buf, NULL) == val); + } + upb_FixLocale(buf); +} + +void _upb_EncodeRoundTripFloat(float val, char* buf, size_t size) { + assert(size >= kUpb_RoundTripBufferSize); + snprintf(buf, size, "%.*g", FLT_DIG, val); + if (strtof(buf, NULL) != val) { + snprintf(buf, size, "%.*g", FLT_DIG + 3, val); + assert(strtof(buf, NULL) == val); + } + upb_FixLocale(buf); +}
diff --git a/upb/upb_internal.h b/upb/upb_internal.h index 74f7c79..d14a175 100644 --- a/upb/upb_internal.h +++ b/upb/upb_internal.h
@@ -55,4 +55,16 @@ mem_block *freelist, *freelist_tail; }; +// Encodes a float or double that is round-trippable, but as short as possible. +// These routines are not fully optimal (not guaranteed to be shortest), but are +// short-ish and match the implementation that has been used in protobuf since +// the beginning. +// +// The given buffer size must be at least kUpb_RoundTripBufferSize. +enum { + kUpb_RoundTripBufferSize = 32 +}; +void _upb_EncodeRoundTripDouble(double val, char* buf, size_t size); +void _upb_EncodeRoundTripFloat(float val, char* buf, size_t size); + #endif /* UPB_INT_H_ */