【问题标题】:Jsoncpp writing float values incorrectlyJsoncpp 错误地写入浮点值
【发布时间】:2015-07-02 04:50:26
【问题描述】:

我正在使用 jsoncpp 从 JSON 文件中读取数据。当我写回文件时,我的浮点值略有偏差。为了测试,我决定将文件解析为 Json::Value,然后将该值写回文件。我希望它看起来相同,但浮点值不同。

例子:

"Parameters":
{
"MinXValue": 0.1,
"MaxXValue": 0.15,
"MinYValue": 0.25,
"MaxYValue": 1.1,
"MinObjectSizeValue": 1
}

写成:

"Parameters":
{
"MinXValue": 0.10000000000000001,
"MaxXValue": 0.14999999999999999,
"MinYValue": 0.25,
"MaxYValue": 1.1000000238418579,
"MinObjectSizeValue": 1
}

您可能会注意到 0.25 没有变化,尽管所有其他浮点数都发生了变化。知道这里发生了什么吗?

【问题讨论】:

  • 有些浮点值可以用二进制精确表示,有些则不能。您所看到的是最接近您的价值观的代表。
  • 浮点数不精确。它们是有限内存中的最佳表示。 PS 0.25 是与二进制工作有关的四分之一总和;-)
  • 感谢您的澄清。有没有办法避免这种情况?
  • @SFBA26 不要将浮点数写回float。将它们写回格式化字符串。这就是为什么大多数语言都有格式化浮点数的功能,因为您不太可能希望显示或写入 18 位精度。

标签: c++ json floating-point jsoncpp


【解决方案1】:

其实是浮点数解析/打印实现的问题。虽然浮点数只能精确表示一些十进制数(0.25 是 ~2^64 之一),但有必要将字符串表示解析为最接近的二进制表示。打印浮点数时,还需要打印(最好是最短的)字符串表示,可以恢复为二进制表示。

我承认我没有调查 JsonCPP 来查看是否有解决方案。但由于我是RapidJSON 的作者,所以我尝试看看 RapidJSON 对此的表现如何:

const char json[] = 
    "{"
    "\"MinXValue\": 0.1,"
    "\"MaxXValue\": 0.15,"
    "\"MinYValue\": 0.25,"
    "\"MaxYValue\": 1.1,"
    "\"MinObjectSizeValue\": 1"
    "}";

using namespace rapidjson;

Document d;
d.Parse(json);

StringBuffer sb;
PrettyWriter<StringBuffer> writer(sb);
d.Accept(writer);

std::cout << sb.GetString();

结果:

{
    "MinXValue": 0.1,
    "MaxXValue": 0.15,
    "MinYValue": 0.25,
    "MaxYValue": 1.1,
    "MinObjectSizeValue": 1
}

RapidJSON 在内部实现了解析和打印算法。正常精度解析最多有 3 个 ULP 错误,但使用全精度解析标志 (kParseFullPrecisionFlag),它始终可以解析为最接近的表示。打印部分实现了 Grisu2 算法。它确实总是产生一个准确的结果,并且超过 99% 的时间是最短的(最优的)。

其实,使用strtod()sprintf(..., "%.17g", ...) 也可以解决这个问题。但是它们在当前的 C/C++ 标准库中要慢得多。例如,我做了一个benchmark 用于打印double。因此,在 RapidJSON 中,我们实现了自己优化的仅标头解决方案。

【讨论】:

  • 感谢您的回复。不幸的是,我没有编写原始代码,现在切换到 RapidJSON 为时已晚。在写入 JSON 之前,我尝试使用 to_string 转换浮点数,但是在使用 .AsFloat() 读取保存的值时出现错误。所以我认为我唯一的选择是在作为字符串写入和读取时转换为字符串,然后转换为浮点数。
【解决方案2】:

一种解决方案是对 jsoncpp 源文件进行少量更改。

将下一行中的 17 替换为 15,使其显示为(我的副本中的第 4135 行):

std::string valueToString(double value) { return valueToString(value, false, 15); }

基本上,它会将最大打印位数从 17 位减少到 15 位,但如果您对此表示满意,它似乎可以修复您提到的所有不受欢迎的打印伪影。我认为有人也可能会争辩说你不应该使用 json 来传递 >15 个有效数字(接近双精度的极限),但这是另一回事......

例如以前为我打印的内容是:

"yo" : 1726.6969999999999,

现在打印为:

"yo" : 1726.697,

【讨论】:

  • 数字太多是一个外观问题。太少会给出错误的结果。对于 IEEE754 双倍来说,17 总是足够的。
【解决方案3】:

这个功能已经被支持了,对于那些还在研究这个问题的人:https://github.com/open-source-parsers/jsoncpp/commit/772f634548f0cec058a0f16a2e641d9f7b78343d

std::ofstream ofs("example.json");
Json::Value root;
// ... Build your json object....
Json::StreamWriterBuilder wbuilder;
wbuilder["indentation"] = "";
wbuilder.settings_["precision"] = 6;
std::unique_ptr<Json::StreamWriter> writer(wbuilder.newStreamWriter());
// Write to file.
writer->write(root, &ofs);
ofs.close();

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-05-17
    • 2012-12-09
    • 2012-11-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多