1

在我的项目中,我从一个 json 文件中读取QJsonDocument::fromJson(). 这很好用,但是当我尝试QJsonDocument用一​​些双打写回文件时toJson(),精度已经搞砸了。

例如,调用具有 double 值的toJson()a 的文档将另存为. 我不想要这个。QJsonValue0.150.14999999999999999

这是因为 Qt 源文件qjsonwriter.cpp at line 126 (Qt 5.6.2)读取:

json += QByteArray::number(d, 'g', std::numeric_limits<double>::digits10 + 2); // ::digits10 is 15

最后那个+2把我弄乱了。如果这个相同的调用QByteArray::number()的精度为 15(而不是 17),则结果完全符合我的需要0.15...。

我了解浮点精度的格式如何导致双精度限制它可以表示的内容。但是,如果我将精度限制为 15 而不是 17,这将具有匹配输入双精度的效果,这是我想要的。

我怎样才能解决这个问题?

显然......我可以编写自己的 Json 解析器,但这是最后的手段。显然我可以编辑 Qt 源代码,但是我的软件已经部署了 Qt5Core.dll,包含在每个人的安装目录中,而且我的更新程序并非旨在更新任何 dll。所以我无法编辑 Qt 源代码。

手指交叉有人对此有一个神奇的解决方法:)

4

2 回答 2

1

这具有匹配我想要的输入双精度的效果。

这个要求没有多大意义。double 不携带任何关于其精度的信息——它只携带一个值。0.15、0.1500 和 0.14999999999999999 是完全相同的双精度值,JSON 编写器无法知道它是如何从文件中读取的(如果它是从文件中读取的)。

一般来说,您不能按照您的建议要求最多 15 位精度,因为根据特定值,精确的 double->text->double 往返最多需要 17 位,因此您会编写不正确的舍入值。然而,一些 JSON 编写器所做的是写入具有读取相同 double back 所需的最小小数位数的数字。除非您这样做 - 就像许多人一样 - 从 15 到 17 循环,以这样的精度写入数字,将其解析回来并查看它是否以完全相同的双精度值返回,否则这远非微不足道。虽然这会产生“更好”(和更小)的输出,但它需要更多的工作并减慢 JSON 写入速度,所以这就是 Qt 可能不这样做的原因。

不过,您可以编写自己的 JSON 编写代码并拥有此功能,对于一个简单的递归实现,我预计大约 15 行代码。

话虽如此,如果您想精确匹配您的输入,这不会拯救您——因为这根本不可能。

于 2018-07-10T11:13:48.587 回答
0

我也刚遇到这个。然而,我没有用第三方库(或者我自己的库!)替换整个 Qt JSON 实现,而是拼凑了一个解决方案......

我与此相关的完整代码库过于广泛和详尽,无法在此处发布和解释。但是解决这一点的要点很简单。

首先,我使用 QVariantMap(或 QVariantHash)来收集我的数据,然后通过内置QJsonObject::fromVariantMapQJsonDocument::fromVariant函数将其转换为 json。为了控制序列化,我定义了一个名为的类DataFormatOptions,它有一个decimalPrecision成员(并设置了对其他此类格式化选项的简单扩展..),然后我调用了一个函数toMagicVar来为我的数据结构创建“魔术变体”以转换为 json字节。为了控制数字格式/精度toMagicVar,将双精度和浮点数转换为所需格式的字符串,并用一些“魔术字节”包围字符串值。我的实际代码的编写方式,可以轻松地在我正在构建的地图/哈希的任何“级别”上执行此操作/通过递归处理格式化,但我省略了这些细节......

const QString NO_QUOTE( "__NO_QUOT__" );

QVariant toMagicVar( const QVariant &var, const DataFormatOptions &opt )
{
...
    const QVariant::Type type( var.type() );
    const QMetaType::Type metaType( (QMetaType::Type)type );
... 
    if( opt.decimalPrecision != DataFormatOptions::DEFAULT_PRECISION
        && (type == QVariant::Type::Double || metaType == QMetaType::Float) )
    {
            static const char FORMAT( 'f' );
            static const QRegExp trailingPointAndZeros( "\\.?0+$" );
            QString formatted( QString::number(
                var.toDouble(), FORMAT, opt.decimalPrecision ) );
            formatted.remove( trailingPointAndZeros );
            return QVariant( QString( NO_QUOTE + formatted + NO_QUOTE ) );
    }
... 
}

请注意,我通过formatted.remove. 如果您希望数据始终包含小数点后的 X 位,您可以选择跳过该步骤。(或者你可能想通过控制它DataFormatOptions?)

一旦我有了要通过网络发送的 json 字节QByteArray,我将删除魔术字节,因此我表示为带引号的字符串的数字再次成为 json 中的数字。

// This is where any "magic residue" is removed, or otherwise manipulated,
// to produce the desired final json bytes...
void scrubMagicBytes( QByteArray &bytes )
{
    static const QByteArray EMPTY, QUOTE( "\"" ),
        NO_QUOTE_PREFIX( QUOTE + NO_QUOTE.toLocal8Bit() ),
        NO_QUOTE_SUFFIX( NO_QUOTE.toLocal8Bit() + QUOTE );
    bytes.replace( NO_QUOTE_PREFIX, EMPTY );
    bytes.replace( NO_QUOTE_SUFFIX, EMPTY );
}
于 2022-01-13T16:13:36.877 回答