Commit 9ecf073a authored by Milo Yip's avatar Milo Yip

Merge pull request #536 from miloyip/issue362

Add Writer::SetMaxDecimalPlaces()
parents 716f16b1 1d856b27
...@@ -145,7 +145,7 @@ inline char* WriteExponent(int K, char* buffer) { ...@@ -145,7 +145,7 @@ inline char* WriteExponent(int K, char* buffer) {
return 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 const int kk = length + k; // 10^(kk-1) <= v < 10^kk
if (length <= kk && kk <= 21) { if (length <= kk && kk <= 21) {
...@@ -160,7 +160,16 @@ inline char* Prettify(char* buffer, int length, int k) { ...@@ -160,7 +160,16 @@ inline char* Prettify(char* buffer, int length, int k) {
// 1234e-2 -> 12.34 // 1234e-2 -> 12.34
std::memmove(&buffer[kk + 1], &buffer[kk], static_cast<size_t>(length - kk)); std::memmove(&buffer[kk + 1], &buffer[kk], static_cast<size_t>(length - kk));
buffer[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) { else if (-6 < kk && kk <= 0) {
// 1234e-6 -> 0.001234 // 1234e-6 -> 0.001234
...@@ -170,7 +179,23 @@ inline char* Prettify(char* buffer, int length, int k) { ...@@ -170,7 +179,23 @@ inline char* Prettify(char* buffer, int length, int k) {
buffer[1] = '.'; buffer[1] = '.';
for (int i = 2; i < offset; i++) for (int i = 2; i < offset; i++)
buffer[i] = '0'; 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) { else if (length == 1) {
// 1e30 // 1e30
...@@ -186,7 +211,8 @@ inline char* Prettify(char* buffer, int length, int k) { ...@@ -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); Double d(value);
if (d.IsZero()) { if (d.IsZero()) {
if (d.Sign()) if (d.Sign())
...@@ -203,7 +229,7 @@ inline char* dtoa(double value, char* buffer) { ...@@ -203,7 +229,7 @@ inline char* dtoa(double value, char* buffer) {
} }
int length, K; int length, K;
Grisu2(value, buffer, &length, &K); Grisu2(value, buffer, &length, &K);
return Prettify(buffer, length, K); return Prettify(buffer, length, K, maxDecimalPlaces);
} }
} }
......
...@@ -76,6 +76,8 @@ class Writer { ...@@ -76,6 +76,8 @@ class Writer {
public: public:
typedef typename SourceEncoding::Ch Ch; typedef typename SourceEncoding::Ch Ch;
static const int kDefaultMaxDecimalPlaces = 324;
//! Constructor //! Constructor
/*! \param os Output stream. /*! \param os Output stream.
\param stackAllocator User supplied allocator. If it is null, it will create a private one. \param stackAllocator User supplied allocator. If it is null, it will create a private one.
...@@ -83,11 +85,11 @@ public: ...@@ -83,11 +85,11 @@ public:
*/ */
explicit explicit
Writer(OutputStream& os, StackAllocator* stackAllocator = 0, size_t levelDepth = kDefaultLevelDepth) : 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 explicit
Writer(StackAllocator* allocator = 0, size_t levelDepth = kDefaultLevelDepth) : 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. //! Reset the writer with a new stream.
/*! /*!
...@@ -121,6 +123,35 @@ public: ...@@ -121,6 +123,35 @@ public:
return hasRoot_ && level_stack_.Empty(); 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 /*!@name Implementation of Handler
\see Handler \see Handler
*/ */
...@@ -266,7 +297,7 @@ protected: ...@@ -266,7 +297,7 @@ protected:
return false; return false;
char buffer[25]; char buffer[25];
char* end = internal::dtoa(d, buffer); char* end = internal::dtoa(d, buffer, maxDecimalPlaces_);
PutReserve(*os_, static_cast<size_t>(end - buffer)); PutReserve(*os_, static_cast<size_t>(end - buffer));
for (char* p = buffer; p != end; ++p) for (char* p = buffer; p != end; ++p)
PutUnsafe(*os_, static_cast<typename TargetEncoding::Ch>(*p)); PutUnsafe(*os_, static_cast<typename TargetEncoding::Ch>(*p));
...@@ -378,6 +409,7 @@ protected: ...@@ -378,6 +409,7 @@ protected:
OutputStream* os_; OutputStream* os_;
internal::Stack<StackAllocator> level_stack_; internal::Stack<StackAllocator> level_stack_;
int maxDecimalPlaces_;
bool hasRoot_; bool hasRoot_;
private: private:
...@@ -426,7 +458,7 @@ inline bool Writer<StringBuffer>::WriteDouble(double d) { ...@@ -426,7 +458,7 @@ inline bool Writer<StringBuffer>::WriteDouble(double d) {
return false; return false;
char *buffer = os_->Push(25); char *buffer = os_->Push(25);
char* end = internal::dtoa(d, buffer); char* end = internal::dtoa(d, buffer, maxDecimalPlaces_);
os_->Pop(static_cast<size_t>(25 - (end - buffer))); os_->Pop(static_cast<size_t>(25 - (end - buffer)));
return true; return true;
} }
......
...@@ -2,6 +2,7 @@ set(UNITTEST_SOURCES ...@@ -2,6 +2,7 @@ set(UNITTEST_SOURCES
allocatorstest.cpp allocatorstest.cpp
bigintegertest.cpp bigintegertest.cpp
documenttest.cpp documenttest.cpp
dtoatest.cpp
encodedstreamtest.cpp encodedstreamtest.cpp
encodingstest.cpp encodingstest.cpp
fwdtest.cpp fwdtest.cpp
......
// 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
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment