[英]QJsonDocument::toJson() incorrect double precision
在我的项目中,我使用QJsonDocument::fromJson()
从 json 文件中读取。 这很好用,但是当我尝试使用toJson()
将QJsonDocument
写回文件时,一些双精度数已经搞砸了。
例如,对具有QJsonValue
且 double 值为0.15
的文档调用toJson()
将保存到文件中为0.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 源代码。
手指交叉有人对此有一个神奇的解决方法:)
这具有匹配我想要的输入双精度的效果。
这个请求没有多大意义。 双精度型不包含有关精度的任何信息-它仅带有值。 0.15、0.1500和0.14999999999999999是完全相同的双精度值,并且JSON编写器无法知道首先如何从文件中读取它( 如果完全从文件中读取的话 )。
通常,您不能像建议的那样要求最大15位精度,因为取决于特定值,精确的double-> text-> double往返最多需要17位,因此您将编写不正确的舍入值。 但是,某些JSON编写器所做的是用最小的小数位数编写数字, 以读取相同的double back 。 除非您-像许多人一样-进行从15到17的循环,否则很难正确地进行数字运算,以这种精度编写数字,将其解析回去,然后看是否返回为完全相同的double值。 尽管这会生成“更小(且更小)”的输出,但它的工作量更大,并且会降低JSON的写入速度,因此这就是Qt可能不这样做的原因。
不过,您仍然可以编写自己的JSON编写代码并具有此功能,对于简单的递归实现,我希望有15行代码。
话又说回来,如果您想精确地匹配您的输入,那将无法节省您-因为这是根本不可能的。
我也刚遇到这个。 我需要生成相对复杂的 json 对象以发送到一个 api,该 api 需要根据字段的不同精度/有效数字。 我没有用第三方库(或者我自己的库!)替换整个 Qt JSON 实现,而是拼凑了一个解决方案......
我与此相关的完整代码库过于广泛和详尽,无法在此处发布和解释。 但它的要点很简单。
我使用 QVariantMap(或 QVariantHash)来收集我的数据,然后通过内置的QJsonObject::fromVariantMap
或QJsonDocument::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' );
const QString formatted( QString::number(
var.toDouble(), FORMAT, opt.decimalPrecision ) );
return QVariant( QString( NO_QUOTE + formatted + NO_QUOTE ) );
}
...
}
一旦我有了要作为QByteArray
通过网络发送的 json 字节,我就会删除魔术字节,这样我用带引号的字符串表示的数字就会再次成为 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
QUOTE( "\"" )
, NO_QUOTE_PREFIX( QUOTE + NO_QUOTE.toLocal8Bit() )
, NO_QUOTE_SUFFIX( NO_QUOTE.toLocal8Bit() + QUOTE );
static const ushort
NO_QUOTE_PREFIX_LEN( NO_QUOTE_PREFIX.length() ),
NO_QUOTE_SUFFIX_LEN( NO_QUOTE_SUFFIX.length() );
for( int idx = bytes.indexOf( NO_QUOTE_PREFIX ); idx != -1;
idx = bytes.indexOf( NO_QUOTE_PREFIX ) )
bytes.remove( idx, NO_QUOTE_PREFIX_LEN );
for( int idx = bytes.indexOf( NO_QUOTE_SUFFIX ); idx != -1;
idx = bytes.indexOf( NO_QUOTE_SUFFIX ) )
bytes.remove( idx, NO_QUOTE_SUFFIX_LEN );
}
上面的擦洗函数可能写得更简洁,如果你想将 QByteArray 转换为 QString 然后返回,以执行“全部替换”,QByteArray 似乎出于某种原因没有?
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.