diff --git a/include/rapidjson/prettywriter.h b/include/rapidjson/prettywriter.h index b917d77..5b154ad 100644 --- a/include/rapidjson/prettywriter.h +++ b/include/rapidjson/prettywriter.h @@ -26,6 +26,9 @@ public: PrettyWriter(OutputStream& os, Allocator* allocator = 0, size_t levelDepth = Base::kDefaultLevelDepth) : Base(os, allocator, levelDepth), indentChar_(' '), indentCharCount_(4) {} + //! Overridden for fluent API, see \ref Writer::SetDoublePrecision() + PrettyWriter& SetDoublePrecision(int p) { Base::SetDoublePrecision(p); return *this; } + //! Set custom indentation. /*! \param indentChar Character for indentation. Must be whitespace character (' ', '\\t', '\\n', '\\r'). \param indentCharCount Number of indent characters for each indentation level. @@ -48,6 +51,11 @@ public: PrettyWriter& Int64(int64_t i64) { PrettyPrefix(kNumberType); Base::WriteInt64(i64); return *this; } PrettyWriter& Uint64(uint64_t u64) { PrettyPrefix(kNumberType); Base::WriteUint64(u64); return *this; } PrettyWriter& Double(double d) { PrettyPrefix(kNumberType); Base::WriteDouble(d); return *this; } + //! Overridden for fluent API, see \ref Writer::Double() + PrettyWriter& Double(double d, int precision) { + int oldPrecision = Base::GetDoublePrecision(); + return SetDoublePrecision(precision).Double(d).SetDoublePrecision(oldPrecision); + } PrettyWriter& String(const Ch* str, SizeType length, bool copy = false) { (void)copy; diff --git a/include/rapidjson/writer.h b/include/rapidjson/writer.h index 38dc8b8..f3b088c 100644 --- a/include/rapidjson/writer.h +++ b/include/rapidjson/writer.h @@ -35,7 +35,23 @@ public: typedef typename SourceEncoding::Ch Ch; Writer(OutputStream& os, Allocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : - os_(os), level_stack_(allocator, levelDepth * sizeof(Level)) {} + os_(os), level_stack_(allocator, levelDepth * sizeof(Level)), + doublePrecision_(kDefaultDoublePrecision) {} + + //! Set the number of significant digits for \c double values + /*! When writing a \c double value to the \c OutputStream, the number + of significant digits is limited to 6 by default. + \param p maximum number of significant digits (default: 6) + \return The Writer itself for fluent API. + */ + Writer& SetDoublePrecision(int p = kDefaultDoublePrecision) { + if (p < 0) p = kDefaultDoublePrecision; // negative precision is ignored + doublePrecision_ = p; + return *this; + } + + //! \see SetDoublePrecision() + int GetDoublePrecision() const { return doublePrecision_; } //@name Implementation of Handler //@{ @@ -45,8 +61,34 @@ public: Writer& Uint(unsigned u) { Prefix(kNumberType); WriteUint(u); return *this; } Writer& Int64(int64_t i64) { Prefix(kNumberType); WriteInt64(i64); return *this; } Writer& Uint64(uint64_t u64) { Prefix(kNumberType); WriteUint64(u64); return *this; } + + //! Writes the given \c double value to the stream + /*! + The number of significant digits (the precision) to be written + can be set by \ref SetDoublePrecision() for the Writer: + \code + Writer<...> writer(...); + writer.SetDoublePrecision(12).Double(M_PI); + \endcode + \param d The value to be written. + \return The Writer itself for fluent API. + */ Writer& Double(double d) { Prefix(kNumberType); WriteDouble(d); return *this; } + //! Writes the given \c double value to the stream (explicit precision) + /*! + The currently set double precision is ignored in favor of the explicitly + given precision for this value. + \see Double(), SetDoublePrecision(), GetDoublePrecision() + \param d The value to be written + \param precision The number of significant digits for this value + \return The Writer itself for fluent API. + */ + Writer& Double(double d, int precision) { + int oldPrecision = GetDoublePrecision(); + return SetDoublePrecision(precision).Double(d).SetDoublePrecision(oldPrecision); + } + Writer& String(const Ch* str, SizeType length, bool copy = false) { (void)copy; Prefix(kStringType); @@ -161,18 +203,21 @@ protected: } while (p != buffer); } +#ifdef _MSC_VER +#define RAPIDJSON_SNPRINTF sprintf_s +#else +#define RAPIDJSON_SNPRINTF snprintf +#endif + //! \todo Optimization with custom double-to-string converter. void WriteDouble(double d) { char buffer[100]; -#ifdef _MSC_VER - int ret = sprintf_s(buffer, sizeof(buffer), "%g", d); -#else - int ret = snprintf(buffer, sizeof(buffer), "%g", d); -#endif + int ret = RAPIDJSON_SNPRINTF(buffer, sizeof(buffer), "%.*g", doublePrecision_, d); RAPIDJSON_ASSERT(ret >= 1); for (int i = 0; i < ret; i++) os_.Put(buffer[i]); } +#undef RAPIDJSON_SNPRINTF void WriteString(const Ch* str, SizeType length) { static const char hexDigits[16] = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; @@ -234,6 +279,9 @@ protected: OutputStream& os_; internal::Stack level_stack_; + int doublePrecision_; + + static const int kDefaultDoublePrecision = 6; private: // Prohibit assignment for VC C4512 warning diff --git a/test/unittest/writertest.cpp b/test/unittest/writertest.cpp index 9d81018..f198b89 100644 --- a/test/unittest/writertest.cpp +++ b/test/unittest/writertest.cpp @@ -1,4 +1,5 @@ #include "unittest.h" +#include "rapidjson/document.h" #include "rapidjson/reader.h" #include "rapidjson/writer.h" #include "rapidjson/stringbuffer.h" @@ -56,6 +57,61 @@ TEST(Writer, String) { TEST_ROUNDTRIP("[\"\\\"\\\\/\\b\\f\\n\\r\\t\"]"); } +TEST(Writer,DoublePrecision) { + const char json[] = "[1.2345,1.2345678,0.123456789012,1234567.8]"; + + StringBuffer buffer; + Writer writer(buffer); + + const int kDefaultDoublePrecision = 6; + // handling the double precision + EXPECT_EQ(writer.GetDoublePrecision(), kDefaultDoublePrecision); + writer.SetDoublePrecision(17); + EXPECT_EQ(writer.GetDoublePrecision(), 17); + writer.SetDoublePrecision(-1); // negative equivalent to reset + EXPECT_EQ(writer.GetDoublePrecision(), kDefaultDoublePrecision); + writer.SetDoublePrecision(1); + writer.SetDoublePrecision(); // reset again + EXPECT_EQ(writer.GetDoublePrecision(), kDefaultDoublePrecision); + + { // write with explicitly increased precision + StringStream s(json); + Reader reader; + reader.Parse<0>(s, writer.SetDoublePrecision(12)); + EXPECT_EQ(writer.GetDoublePrecision(), 12); + EXPECT_STREQ(json, buffer.GetString()); + buffer.Clear(); + } + { // explicit individual double precisions + writer.SetDoublePrecision(2) + .StartArray() + .Double(1.2345,5) + .Double(1.2345678,9) + .Double(0.123456789012,12) + .Double(1234567.8,8) + .EndArray(); + + EXPECT_EQ(writer.GetDoublePrecision(), 2); + EXPECT_STREQ(json, buffer.GetString()); + buffer.Clear(); + } + { // write with default precision (output with precision loss) + Document d; + d.Parse<0>(json); + d.Accept(writer.SetDoublePrecision()); + + // parsed again to avoid platform-dependent floating point outputs + // (e.g. width of exponents) + d.Parse<0>(buffer.GetString()); + EXPECT_EQ(writer.GetDoublePrecision(), kDefaultDoublePrecision); + EXPECT_DOUBLE_EQ(d[0u].GetDouble(), 1.2345); + EXPECT_DOUBLE_EQ(d[1u].GetDouble(), 1.23457); + EXPECT_DOUBLE_EQ(d[2u].GetDouble(), 0.123457); + EXPECT_DOUBLE_EQ(d[3u].GetDouble(), 1234570); + buffer.Clear(); + } +} + TEST(Writer, Transcode) { // UTF8 -> UTF16 -> UTF8 StringStream s("{ \"hello\" : \"world\", \"t\" : true , \"f\" : false, \"n\": null, \"i\":123, \"pi\": 3.1416, \"a\":[1, 2, 3], \"dollar\":\"\x24\", \"cents\":\"\xC2\xA2\", \"euro\":\"\xE2\x82\xAC\", \"gclef\":\"\xF0\x9D\x84\x9E\" } ");