jsoncpp序列化double类型时精度损失问题的解决办法
json
适用于现代 C++ 的 JSON。
项目地址:https://gitcode.com/gh_mirrors/js/json
免费下载资源
·
目录
注意上述方法都需要升级jsoncpp1.9.5,我是基于这个版本改的
问题:
之前用的1.7.7版本的jsoncpp,序列化double数据时会有精度损失,例如下面这个
#include <iostream>
#include "json/json.h"
int main()
{
Json::Value obj;
Json::FastWriter write;
obj["d"] = 2.1;
obj["d2"] = 9.111;
auto rtn = write.write(obj);
std::cout << rtn << std::endl;
std::cout << obj.toStyledString() <<std::endl;
}
输出如下:
解决办法1:此法不需要改源码,使用StreamWriterBuilder进行序列化
注意:需升级jsoncpp到最新版本1.9.5版本
使用代码如下:
#include <iostream>
#include "json/json.h"
int main()
{
Json::Value obj;
std::ostringstream os;
Json::StreamWriterBuilder write2;
obj["d"] = 2.1;
obj["d2"] = 9.111;
obj["d3"] = 9.123456789;
//设置精度 注意这个默认设置的是数字总长度
//如果想设置小数点后的位数需设置precisionType为decimal
write2.settings_["precision"] = 7;
//设置精度类型 只可设置2种字符串 significant精度位数为数字总长度(jsoncpp默认为此类型) decimal精度位数为小数点后的长度
write2.settings_["precisionType"] = "decimal";
//注意 要先设置完settings_ 否则设置不会生效
auto p = write2.newStreamWriter();
p->write(obj, &os);
std::string rtn = os.str();
std::cout << rtn << std::endl;
}
- 特别注意:精度设置一定要大于你需求的精度位数,比如需要三位可以设置4位或5位,因为最后一位可能会不准
- 比如上面代码d3这个项是9.123456789,我设置精度为小数点后7位,然后看下面打印的输出可以发现第7位不对了,只有前6位是正确的
修改后的输出如下:
不足之处:
StreamWriterBuilder序列化的字符串是可读形式的,就像上面的输出,是有换行和缩进的(转换效率会比FastWrite低),我的服务端代码里其实不需要转换json为可读的,更需要的是效率,所以还有下面一种方法改FasetWrite源码
解决办法2:此法需要改源码,使用FastWriter进行序列化
注意:需升级jsoncpp到最新版本1.9.5版本
修改源码(writer.h):
FastWriter类新增2个成员变量(precision_和precisionType_)和成员函数(set_precision和set_precisionType)
#if defined(_MSC_VER)
#pragma warning(push)
#pragma warning(disable : 4996) // Deriving from deprecated class
#endif
class JSON_API FastWriter
: public Writer {
public:
FastWriter();
~FastWriter() override = default;
void enableYAMLCompatibility();
/** \brief Drop the "null" string from the writer's output for nullValues.
* Strictly speaking, this is not valid JSON. But when the output is being
* fed to a browser's JavaScript, it makes for smaller output and the
* browser can handle the output just fine.
*/
void dropNullPlaceholders();
void omitEndingLineFeed();
public: // overridden from Writer
String write(const Value& root) override;
//设置精度位数
void set_precision(int precision) { precision_ = precision; };
//设置精度类型 默认为数字总长
//入参:isDecimal true表示类型为小数点后长度 false表示类型为数字总长
void set_precisionType(bool isDecimal) { isDecimal ? (precisionType_ = PrecisionType::decimalPlaces) : (precisionType_ = PrecisionType::significantDigits); };
private:
void writeValue(const Value& value);
String document_;
bool yamlCompatibilityEnabled_{false};
bool dropNullPlaceholders_{false};
bool omitEndingLineFeed_{false};
int precision_{ 17 };//精度位数 默认17位
PrecisionType precisionType_{ PrecisionType::significantDigits };//精度类型 默认为数字总长
};
#if defined(_MSC_VER)
#pragma warning(pop)
#endif
修改源码(json_writer.cpp):
只修改了1行代码,FastWriter::writeValue函数中case realValue的处理中调用的valueToString新增了2个参数传递(源码中没有传递用的函数默认值,现在传了并且可以通过新增的2个成员函数进行设置)
void FastWriter::writeValue(const Value& value) {
switch (value.type()) {
case nullValue:
if (!dropNullPlaceholders_)
document_ += "null";
break;
case intValue:
document_ += valueToString(value.asLargestInt());
break;
case uintValue:
document_ += valueToString(value.asLargestUInt());
break;
case realValue:
//这里原先是document_ += valueToString(value.asDouble());
//因为后2个参数没传,所以用的函数默认值即精度位数=17,精度类型=PrecisionType::significantDigits
//修改后 现在会传这2个参数,具体值可以通过新增加的2个成员函数设置
document_ += valueToString(value.asDouble(), precision_, precisionType_);
break;
case stringValue: {
// Is NULL possible for value.string_? No.
char const* str;
char const* end;
bool ok = value.getString(&str, &end);
if (ok)
document_ += valueToQuotedStringN(str, static_cast<size_t>(end - str));
break;
}
case booleanValue:
document_ += valueToString(value.asBool());
break;
case arrayValue: {
document_ += '[';
ArrayIndex size = value.size();
for (ArrayIndex index = 0; index < size; ++index) {
if (index > 0)
document_ += ',';
writeValue(value[index]);
}
document_ += ']';
} break;
case objectValue: {
Value::Members members(value.getMemberNames());
document_ += '{';
for (auto it = members.begin(); it != members.end(); ++it) {
const String& name = *it;
if (it != members.begin())
document_ += ',';
document_ += valueToQuotedStringN(name.data(), name.length());
document_ += yamlCompatibilityEnabled_ ? ": " : ":";
writeValue(value[name]);
}
document_ += '}';
} break;
}
}
使用的代码:
#include <iostream>
#include "json/json.h"
int main()
{
Json::Value obj;
Json::FastWriter write;
write.set_precision(7);
write.set_precisionType(true);
obj["d"] = 2.1;
obj["d2"] = 9.111;
obj["d3"] = 9.123456789;
auto rtn = write.write(obj);
std::cout << rtn << std::endl;
}
- 特别注意:精度设置一定要大于你需求的精度位数,比如需要三位可以设置4位或5位,因为最后一位可能会不准
- 比如上面代码d3这个项是9.123456789,我设置精度为小数点后7位,然后看下面打印的输出可以发现第7位不对了,只有前6位是正确的
输出如下:
不足之处:
序列化后的字符串可读性比较差,但是序列化效率高,我的代码追求效率不需要可读性,所以用的这个
总结:
两种方法各有好坏,一个不需要改源码但是效率可能低一点,一个需改源码但是效率高点,大家自行选择
GitHub 加速计划 / js / json
41.72 K
6.61 K
下载
适用于现代 C++ 的 JSON。
最近提交(Master分支:20 天前 )
960b763e
3 个月前
8c391e04
6 个月前
更多推荐
已为社区贡献2条内容
所有评论(0)