From 1d856b2761ced15984360e60ad3b372b67d6e5cd Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Thu, 11 Feb 2016 16:08:17 +0800 Subject: [PATCH 1/2] Add Writer::SetMaxDecimalPlaces() --- include/rapidjson/internal/dtoa.h | 36 ++++++++++-- include/rapidjson/writer.h | 40 ++++++++++++-- test/unittest/CMakeLists.txt | 1 + test/unittest/dtoatest.cpp | 91 +++++++++++++++++++++++++++++++ 4 files changed, 159 insertions(+), 9 deletions(-) create mode 100644 test/unittest/dtoatest.cpp diff --git a/include/rapidjson/internal/dtoa.h b/include/rapidjson/internal/dtoa.h index d04ae21..940d61a 100644 --- a/include/rapidjson/internal/dtoa.h +++ b/include/rapidjson/internal/dtoa.h @@ -145,7 +145,7 @@ inline char* WriteExponent(int K, char* buffer) { return buffer; } -inline char* Prettify(char* buffer, int length, int k) { +inline char* Prettify(char* buffer, int length, int k, int maxDecimalPlaces) { const int kk = length + k; // 10^(kk-1) <= v < 10^kk if (length <= kk && kk <= 21) { @@ -160,7 +160,16 @@ inline char* Prettify(char* buffer, int length, int k) { // 1234e-2 -> 12.34 std::memmove(&buffer[kk + 1], &buffer[kk], static_cast(length - kk)); buffer[kk] = '.'; - return &buffer[length + 1]; + if (length > kk + maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 1.2345 -> 1.23, 1.102 -> 1.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = kk + maxDecimalPlaces; i > kk + 1; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[kk + 2]; // Reserve one zero + } + else + return &buffer[length + 1]; } else if (-6 < kk && kk <= 0) { // 1234e-6 -> 0.001234 @@ -170,7 +179,23 @@ inline char* Prettify(char* buffer, int length, int k) { buffer[1] = '.'; for (int i = 2; i < offset; i++) buffer[i] = '0'; - return &buffer[length + offset]; + if (length + offset > maxDecimalPlaces) { + // When maxDecimalPlaces = 2, 0.123 -> 0.12, 0.102 -> 0.1 + // Remove extra trailing zeros (at least one) after truncation. + for (int i = maxDecimalPlaces + 1; i > 2; i--) + if (buffer[i] != '0') + return &buffer[i + 1]; + return &buffer[3]; // Reserve one zero + } + else + return &buffer[length + offset]; + } + else if (kk < -maxDecimalPlaces) { + // Truncate to zero + buffer[0] = '0'; + buffer[1] = '.'; + buffer[2] = '0'; + return &buffer[3]; } else if (length == 1) { // 1e30 @@ -186,7 +211,8 @@ inline char* Prettify(char* buffer, int length, int k) { } } -inline char* dtoa(double value, char* buffer) { +inline char* dtoa(double value, char* buffer, int maxDecimalPlaces = 324) { + RAPIDJSON_ASSERT(maxDecimalPlaces >= 1); Double d(value); if (d.IsZero()) { if (d.Sign()) @@ -203,7 +229,7 @@ inline char* dtoa(double value, char* buffer) { } int length, K; Grisu2(value, buffer, &length, &K); - return Prettify(buffer, length, K); + return Prettify(buffer, length, K, maxDecimalPlaces); } } diff --git a/include/rapidjson/writer.h b/include/rapidjson/writer.h index 4f42b9e..fbf6715 100644 --- a/include/rapidjson/writer.h +++ b/include/rapidjson/writer.h @@ -56,6 +56,8 @@ class Writer { public: typedef typename SourceEncoding::Ch Ch; + static const int kDefaultMaxDecimalPlaces = 324; + //! Constructor /*! \param os Output stream. \param stackAllocator User supplied allocator. If it is null, it will create a private one. @@ -63,11 +65,11 @@ public: */ explicit Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : - os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), hasRoot_(false) {} + os_(&os), level_stack_(stackAllocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} explicit Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : - os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), hasRoot_(false) {} + os_(0), level_stack_(allocator, levelDepth * sizeof(Level)), maxDecimalPlaces_(kDefaultMaxDecimalPlaces), hasRoot_(false) {} //! Reset the writer with a new stream. /*! @@ -101,6 +103,35 @@ public: return hasRoot_ && level_stack_.Empty(); } + int GetMaxDecimalPlaces() const { + return maxDecimalPlaces_; + } + + //! Sets the maximum number of decimal places for double output. + /*! + This setting truncates the output with specified number of decimal places. + + For example, + + \code + writer.SetMaxDecimalPlaces(3); + writer.StartArray(); + writer.Double(0.12345); // "0.123" + writer.Double(0.0001); // "0.0" + writer.Double(1.234567890123456e30); // "1.234567890123456e30" (do not truncate significand for positive exponent) + writer.Double(1.23e-4); // "0.0" (do truncate significand for negative exponent) + writer.EndArray(); + \endcode + + The default setting does not truncate any decimal places. You can restore to this setting by calling + \code + writer.SetMaxDecimalPlaces(Writer::kDefaultMaxDecimalPlaces); + \endcode + */ + void SetMaxDecimalPlaces(int maxDecimalPlaces) { + maxDecimalPlaces_ = maxDecimalPlaces; + } + /*!@name Implementation of Handler \see Handler */ @@ -246,7 +277,7 @@ protected: return false; char buffer[25]; - char* end = internal::dtoa(d, buffer); + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); PutReserve(*os_, static_cast(end - buffer)); for (char* p = buffer; p != end; ++p) PutUnsafe(*os_, static_cast(*p)); @@ -353,6 +384,7 @@ protected: OutputStream* os_; internal::Stack level_stack_; + int maxDecimalPlaces_; bool hasRoot_; private: @@ -401,7 +433,7 @@ inline bool Writer::WriteDouble(double d) { return false; char *buffer = os_->Push(25); - char* end = internal::dtoa(d, buffer); + char* end = internal::dtoa(d, buffer, maxDecimalPlaces_); os_->Pop(static_cast(25 - (end - buffer))); return true; } diff --git a/test/unittest/CMakeLists.txt b/test/unittest/CMakeLists.txt index 9c13816..0ae1662 100644 --- a/test/unittest/CMakeLists.txt +++ b/test/unittest/CMakeLists.txt @@ -2,6 +2,7 @@ set(UNITTEST_SOURCES allocatorstest.cpp bigintegertest.cpp documenttest.cpp + dtoatest.cpp encodedstreamtest.cpp encodingstest.cpp fwdtest.cpp diff --git a/test/unittest/dtoatest.cpp b/test/unittest/dtoatest.cpp new file mode 100644 index 0000000..da02095 --- /dev/null +++ b/test/unittest/dtoatest.cpp @@ -0,0 +1,91 @@ +// Tencent is pleased to support the open source community by making RapidJSON available. +// +// Copyright (C) 2015 THL A29 Limited, a Tencent company, and Milo Yip. All rights reserved. +// +// Licensed under the MIT License (the "License"); you may not use this file except +// in compliance with the License. You may obtain a copy of the License at +// +// http://opensource.org/licenses/MIT +// +// Unless required by applicable law or agreed to in writing, software distributed +// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR +// CONDITIONS OF ANY KIND, either express or implied. See the License for the +// specific language governing permissions and limitations under the License. + +#include "unittest.h" +#include "rapidjson/internal/dtoa.h" + +#ifdef __GNUC__ +RAPIDJSON_DIAG_PUSH +RAPIDJSON_DIAG_OFF(type-limits) +#endif + +using namespace rapidjson::internal; + +TEST(dtoa, normal) { + char buffer[30]; + +#define TEST_DTOA(d, a)\ + *dtoa(d, buffer) = '\0';\ + EXPECT_STREQ(a, buffer) + + TEST_DTOA(0.0, "0.0"); + TEST_DTOA(-0.0, "-0.0"); + TEST_DTOA(1.0, "1.0"); + TEST_DTOA(-1.0, "-1.0"); + TEST_DTOA(1.2345, "1.2345"); + TEST_DTOA(1.2345678, "1.2345678"); + TEST_DTOA(0.123456789012, "0.123456789012"); + TEST_DTOA(1234567.8, "1234567.8"); + TEST_DTOA(0.000001, "0.000001"); + TEST_DTOA(0.0000001, "1e-7"); + TEST_DTOA(1e30, "1e30"); + TEST_DTOA(1.234567890123456e30, "1.234567890123456e30"); + TEST_DTOA(5e-324, "5e-324"); // Min subnormal positive double + TEST_DTOA(2.225073858507201e-308, "2.225073858507201e-308"); // Max subnormal positive double + TEST_DTOA(2.2250738585072014e-308, "2.2250738585072014e-308"); // Min normal positive double + TEST_DTOA(1.7976931348623157e308, "1.7976931348623157e308"); // Max double + +#undef TEST_DTOA +} + +TEST(dtoa, maxDecimalPlaces) { + char buffer[30]; + +#define TEST_DTOA(m, d, a)\ + *dtoa(d, buffer, m) = '\0';\ + EXPECT_STREQ(a, buffer) + + TEST_DTOA(3, 0.0, "0.0"); + TEST_DTOA(1, 0.0, "0.0"); + TEST_DTOA(3, -0.0, "-0.0"); + TEST_DTOA(3, 1.0, "1.0"); + TEST_DTOA(3, -1.0, "-1.0"); + TEST_DTOA(3, 1.2345, "1.234"); + TEST_DTOA(2, 1.2345, "1.23"); + TEST_DTOA(1, 1.2345, "1.2"); + TEST_DTOA(3, 1.2345678, "1.234"); + TEST_DTOA(3, 1.0001, "1.0"); + TEST_DTOA(2, 1.0001, "1.0"); + TEST_DTOA(1, 1.0001, "1.0"); + TEST_DTOA(3, 0.123456789012, "0.123"); + TEST_DTOA(2, 0.123456789012, "0.12"); + TEST_DTOA(1, 0.123456789012, "0.1"); + TEST_DTOA(4, 0.0001, "0.0001"); + TEST_DTOA(3, 0.0001, "0.0"); + TEST_DTOA(2, 0.0001, "0.0"); + TEST_DTOA(1, 0.0001, "0.0"); + TEST_DTOA(3, 1234567.8, "1234567.8"); + TEST_DTOA(3, 1e30, "1e30"); + TEST_DTOA(3, 5e-324, "0.0"); // Min subnormal positive double + TEST_DTOA(3, 2.225073858507201e-308, "0.0"); // Max subnormal positive double + TEST_DTOA(3, 2.2250738585072014e-308, "0.0"); // Min normal positive double + TEST_DTOA(3, 1.7976931348623157e308, "1.7976931348623157e308"); // Max double + +#undef TEST_DTOA +} + + +#ifdef __GNUC__ +RAPIDJSON_DIAG_POP +#endif From e7cb2b1cbf5220b614c62d8a3ce1fa45965f1ffe Mon Sep 17 00:00:00 2001 From: Milo Yip Date: Sun, 14 Feb 2016 15:31:24 +0800 Subject: [PATCH 2/2] Add (Pretty)Writer::RawValue() Fix #205 --- include/rapidjson/prettywriter.h | 12 ++++++++++++ include/rapidjson/writer.h | 19 +++++++++++++++++++ test/unittest/prettywritertest.cpp | 19 +++++++++++++++++++ test/unittest/writertest.cpp | 14 ++++++++++++++ 4 files changed, 64 insertions(+) diff --git a/include/rapidjson/prettywriter.h b/include/rapidjson/prettywriter.h index dd0e516..4f8eba9 100644 --- a/include/rapidjson/prettywriter.h +++ b/include/rapidjson/prettywriter.h @@ -146,6 +146,18 @@ public: bool Key(const Ch* str) { return Key(str, internal::StrLen(str)); } //@} + + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + \note When using PrettyWriter::RawValue(), the result json may not be indented correctly. + */ + bool RawValue(const Ch* json, size_t length, Type type) { PrettyPrefix(type); return Base::WriteRawValue(json, length); } + protected: void PrettyPrefix(Type type) { (void)type; diff --git a/include/rapidjson/writer.h b/include/rapidjson/writer.h index 6e6f2fd..a6cc5c8 100644 --- a/include/rapidjson/writer.h +++ b/include/rapidjson/writer.h @@ -198,6 +198,16 @@ public: //@} + //! Write a raw JSON value. + /*! + For user to write a stringified JSON as a value. + + \param json A well-formed JSON value. It should not contain null character within [0, length - 1] range. + \param length Length of the json. + \param type Type of the root of json. + */ + bool RawValue(const Ch* json, size_t length, Type type) { Prefix(type); return WriteRawValue(json, length); } + protected: //! Information for each nested level struct Level { @@ -352,6 +362,15 @@ protected: bool WriteStartArray() { os_->Put('['); return true; } bool WriteEndArray() { os_->Put(']'); return true; } + bool WriteRawValue(const Ch* json, size_t length) { + PutReserve(*os_, length); + for (size_t i = 0; i < length; i++) { + RAPIDJSON_ASSERT(json[i] != '\0'); + PutUnsafe(*os_, json[i]); + } + return true; + } + void Prefix(Type type) { (void)type; if (RAPIDJSON_LIKELY(level_stack_.GetSize() != 0)) { // this value is not at root diff --git a/test/unittest/prettywritertest.cpp b/test/unittest/prettywritertest.cpp index d3c69c0..e05d710 100644 --- a/test/unittest/prettywritertest.cpp +++ b/test/unittest/prettywritertest.cpp @@ -159,3 +159,22 @@ TEST(PrettyWriter, FileWriteStream) { EXPECT_STREQ(kPrettyJson, json); free(json); } + +TEST(PrettyWriter, RawValue) { + StringBuffer buffer; + PrettyWriter writer(buffer); + writer.StartObject(); + writer.Key("a"); + writer.Int(1); + writer.Key("raw"); + const char json[] = "[\"Hello\\nWorld\", 123.456]"; + writer.RawValue(json, strlen(json), kArrayType); + writer.EndObject(); + EXPECT_TRUE(writer.IsComplete()); + EXPECT_STREQ( + "{\n" + " \"a\": 1,\n" + " \"raw\": [\"Hello\\nWorld\", 123.456]\n" // no indentation within raw value + "}", + buffer.GetString()); +} diff --git a/test/unittest/writertest.cpp b/test/unittest/writertest.cpp index 3b1dfe8..3c63ea2 100644 --- a/test/unittest/writertest.cpp +++ b/test/unittest/writertest.cpp @@ -425,3 +425,17 @@ TEST(Writer, Inf) { EXPECT_FALSE(writer.Double(-inf)); } } + +TEST(Writer, RawValue) { + StringBuffer buffer; + Writer writer(buffer); + writer.StartObject(); + writer.Key("a"); + writer.Int(1); + writer.Key("raw"); + const char json[] = "[\"Hello\\nWorld\", 123.456]"; + writer.RawValue(json, strlen(json), kArrayType); + writer.EndObject(); + EXPECT_TRUE(writer.IsComplete()); + EXPECT_STREQ("{\"a\":1,\"raw\":[\"Hello\\nWorld\", 123.456]}", buffer.GetString()); +}