diff --git a/include/json/value.h b/include/json/value.h index 2643782..3474cd8 100644 --- a/include/json/value.h +++ b/include/json/value.h @@ -109,6 +109,13 @@ enum CommentPlacement { numberOfCommentPlacement }; +/** \brief Type of precision for formatting of real values. + */ +enum PrecisionType { + significantDigits = 0, ///< we set max number of significant digits in string + decimalPlaces ///< we set max number of digits after "." in string +}; + //# ifdef JSON_USE_CPPTL // typedef CppTL::AnyEnumerator EnumMemberNames; // typedef CppTL::AnyEnumerator EnumValues; @@ -220,6 +227,9 @@ public: static const UInt64 maxUInt64; #endif // defined(JSON_HAS_INT64) + /// Default precision for real value for string representation. + static const UInt defaultRealPrecision; + // Workaround for bug in the NVIDIAs CUDA 9.1 nvcc compiler // when using gcc and clang backend compilers. CZString // cannot be defined as private. See issue #486 diff --git a/include/json/writer.h b/include/json/writer.h index 35ec396..767b3bd 100644 --- a/include/json/writer.h +++ b/include/json/writer.h @@ -106,6 +106,10 @@ public: - If true, outputs non-finite floating point values in the following way: NaN values as "NaN", positive infinity as "Infinity", and negative infinity as "-Infinity". + - "precision": int + - Number of precision digits for formatting of real values. + - "precisionType": "significant"(default) or "decimal" + - Type of precision for formatting of real values. You can examine 'settings_` yourself to see the defaults. You can also write and read them just like any @@ -339,7 +343,8 @@ JSONCPP_STRING JSON_API valueToString(UInt value); #endif // if defined(JSON_HAS_INT64) JSONCPP_STRING JSON_API valueToString(LargestInt value); JSONCPP_STRING JSON_API valueToString(LargestUInt value); -JSONCPP_STRING JSON_API valueToString(double value); +JSONCPP_STRING JSON_API valueToString(double value, unsigned int precision = Value::defaultRealPrecision, + PrecisionType precisionType = PrecisionType::significantDigits); JSONCPP_STRING JSON_API valueToString(bool value); JSONCPP_STRING JSON_API valueToQuotedString(const char* value); diff --git a/src/lib_json/json_tool.h b/src/lib_json/json_tool.h index 4590bfc..86f1f2c 100644 --- a/src/lib_json/json_tool.h +++ b/src/lib_json/json_tool.h @@ -109,6 +109,20 @@ static inline void fixNumericLocaleInput(char* begin, char* end) { } } +/** + * Delete zeros in the end of string, if it isn't last zero before '.' character. + */ +static inline void fixZerosInTheEnd(char* begin, char* end) { + end--; + while ((begin < end) && (*end == '0')) { + // don't delete last zero before point. + if (*(end - 1) != '.') { + *end = '\0'; + } + end--; + } +} + } // namespace Json { #endif // LIB_JSONCPP_JSON_TOOL_H_INCLUDED diff --git a/src/lib_json/json_value.cpp b/src/lib_json/json_value.cpp index 1633f49..ed49df2 100644 --- a/src/lib_json/json_value.cpp +++ b/src/lib_json/json_value.cpp @@ -64,6 +64,8 @@ const LargestInt Value::minLargestInt = LargestInt(~(LargestUInt(-1) / 2)); const LargestInt Value::maxLargestInt = LargestInt(LargestUInt(-1) / 2); const LargestUInt Value::maxLargestUInt = LargestUInt(-1); +const UInt Value::defaultRealPrecision = 17; + #if !defined(JSON_USE_INT64_DOUBLE_CONVERSION) template static inline bool InRange(double d, T min, U max) { diff --git a/src/lib_json/json_writer.cpp b/src/lib_json/json_writer.cpp index 5167c31..be03a66 100644 --- a/src/lib_json/json_writer.cpp +++ b/src/lib_json/json_writer.cpp @@ -118,14 +118,18 @@ JSONCPP_STRING valueToString(UInt value) { #endif // # if defined(JSON_HAS_INT64) namespace { -JSONCPP_STRING valueToString(double value, bool useSpecialFloats, unsigned int precision) { +JSONCPP_STRING valueToString(double value, bool useSpecialFloats, unsigned int precision, PrecisionType precisionType) { // Allocate a buffer that is more than large enough to store the 16 digits of // precision requested below. char buffer[36]; int len = -1; char formatString[15]; - snprintf(formatString, sizeof(formatString), "%%.%ug", precision); + if (precisionType == PrecisionType::significantDigits) { + snprintf(formatString, sizeof(formatString), "%%.%ug", precision); + } else { + snprintf(formatString, sizeof(formatString), "%%.%uf", precision); + } // Print into the buffer. We need not request the alternative representation // that always has a decimal point because JSON doesn't distinguish the @@ -133,6 +137,10 @@ JSONCPP_STRING valueToString(double value, bool useSpecialFloats, unsigned int p if (isfinite(value)) { len = snprintf(buffer, sizeof(buffer), formatString, value); fixNumericLocale(buffer, buffer + len); + // to delete use-less too much zeros in the end of string + if (precisionType == PrecisionType::decimalPlaces) { + fixZerosInTheEnd(buffer, buffer + len); + } // try to ensure we preserve the fact that this was given to us as a double on input if (!strchr(buffer, '.') && !strchr(buffer, 'e')) { @@ -154,7 +162,9 @@ JSONCPP_STRING valueToString(double value, bool useSpecialFloats, unsigned int p } } -JSONCPP_STRING valueToString(double value) { return valueToString(value, false, 17); } +JSONCPP_STRING valueToString(double value, unsigned int precision, PrecisionType precisionType) { + return valueToString(value, false, precision, precisionType); +} JSONCPP_STRING valueToString(bool value) { return value ? "true" : "false"; } @@ -856,7 +866,8 @@ struct BuiltStyledStreamWriter : public StreamWriter JSONCPP_STRING const& nullSymbol, JSONCPP_STRING const& endingLineFeedSymbol, bool useSpecialFloats, - unsigned int precision); + unsigned int precision, + PrecisionType precisionType); int write(Value const& root, JSONCPP_OSTREAM* sout) JSONCPP_OVERRIDE; private: void writeValue(Value const& value); @@ -885,6 +896,7 @@ private: bool indented_ : 1; bool useSpecialFloats_ : 1; unsigned int precision_; + PrecisionType precisionType_; }; BuiltStyledStreamWriter::BuiltStyledStreamWriter( JSONCPP_STRING const& indentation, @@ -893,7 +905,8 @@ BuiltStyledStreamWriter::BuiltStyledStreamWriter( JSONCPP_STRING const& nullSymbol, JSONCPP_STRING const& endingLineFeedSymbol, bool useSpecialFloats, - unsigned int precision) + unsigned int precision, + PrecisionType precisionType) : rightMargin_(74) , indentation_(indentation) , cs_(cs) @@ -904,6 +917,7 @@ BuiltStyledStreamWriter::BuiltStyledStreamWriter( , indented_(false) , useSpecialFloats_(useSpecialFloats) , precision_(precision) + , precisionType_(precisionType) { } int BuiltStyledStreamWriter::write(Value const& root, JSONCPP_OSTREAM* sout) @@ -933,7 +947,7 @@ void BuiltStyledStreamWriter::writeValue(Value const& value) { pushValue(valueToString(value.asLargestUInt())); break; case realValue: - pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_)); + pushValue(valueToString(value.asDouble(), useSpecialFloats_, precision_, precisionType_)); break; case stringValue: { @@ -1145,6 +1159,7 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const { JSONCPP_STRING indentation = settings_["indentation"].asString(); JSONCPP_STRING cs_str = settings_["commentStyle"].asString(); + JSONCPP_STRING pt_str = settings_["precisionType"].asString(); bool eyc = settings_["enableYAMLCompatibility"].asBool(); bool dnp = settings_["dropNullPlaceholders"].asBool(); bool usf = settings_["useSpecialFloats"].asBool(); @@ -1157,6 +1172,14 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const } else { throwRuntimeError("commentStyle must be 'All' or 'None'"); } + PrecisionType precisionType(significantDigits); + if (pt_str == "significant") { + precisionType = PrecisionType::significantDigits; + } else if (pt_str == "decimal") { + precisionType = PrecisionType::decimalPlaces; + } else { + throwRuntimeError("precisionType must be 'significant' or 'decimal'"); + } JSONCPP_STRING colonSymbol = " : "; if (eyc) { colonSymbol = ": "; @@ -1171,7 +1194,7 @@ StreamWriter* StreamWriterBuilder::newStreamWriter() const JSONCPP_STRING endingLineFeedSymbol; return new BuiltStyledStreamWriter( indentation, cs, - colonSymbol, nullSymbol, endingLineFeedSymbol, usf, pre); + colonSymbol, nullSymbol, endingLineFeedSymbol, usf, pre, precisionType); } static void getValidWriterKeys(std::set* valid_keys) { @@ -1182,6 +1205,7 @@ static void getValidWriterKeys(std::set* valid_keys) valid_keys->insert("dropNullPlaceholders"); valid_keys->insert("useSpecialFloats"); valid_keys->insert("precision"); + valid_keys->insert("precisionType"); } bool StreamWriterBuilder::validate(Json::Value* invalid) const { @@ -1214,6 +1238,7 @@ void StreamWriterBuilder::setDefaults(Json::Value* settings) (*settings)["dropNullPlaceholders"] = false; (*settings)["useSpecialFloats"] = false; (*settings)["precision"] = 17; + (*settings)["precisionType"] = "significant"; //! [StreamWriterBuilderDefaults] } diff --git a/src/test_lib_json/main.cpp b/src/test_lib_json/main.cpp index ee81790..cbf4823 100644 --- a/src/test_lib_json/main.cpp +++ b/src/test_lib_json/main.cpp @@ -1757,6 +1757,27 @@ JSONTEST_FIXTURE(ValueTest, precision) { expected = "0.25634569487374054"; result = Json::writeString(b, v); JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + b.settings_["precision"] = 5; + b.settings_["precisionType"] = "decimal"; + v = 0.256345694873740545068; + expected = "0.25635"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + b.settings_["precision"] = 1; + b.settings_["precisionType"] = "decimal"; + v = 0.256345694873740545068; + expected = "0.3"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); + + b.settings_["precision"] = 10; + b.settings_["precisionType"] = "decimal"; + v = 0.23300000; + expected = "0.233"; + result = Json::writeString(b, v); + JSONTEST_ASSERT_STRING_EQUAL(expected, result); } struct WriterTest : JsonTest::TestCase {};