xemu/tests/check-qnum.c
Markus Armbruster f917eed306 qobject: Fix qnum_to_string() to use sufficient precision
We should serialize numbers to JSON so that they deserialize back to
the same number.  We fail to do so.

The culprit is qnum_to_string(): it uses format %f with trailing '0'
trimmed.  Results in pretty output for "nice" numbers, but is prone to
nasty rounding errors.  For instance, numbers between 0 and 0.0000005
get flushed to zero.

Where exactly the incorrect rounding can bite is tiresome to gauge.
Here's my take.

* In QMP output, type 'number':

  - query-blockstats value avg_rd_queue_depth

  - QMP query-migrate values mbps, cache-miss-rate, encoding-rate,
    busy-rate, compression-rate.

  Relatively harmless, I guess.

* In tracing QMP input.  Harmless.

* In qemu-ga output, type 'number': guest-get-users value login-time.
  Harmless.

* In output of HMP qom-get.  Harmless.

Not affected, because double values don't actually occur there (I
think):

* QMP output, type 'any':

  * qom-get value

  * qom-list, qom-list-properties value default-value

  * query-cpu-model-comparison, query-cpu-model-baseline,
    query-cpu-model-expansion value props.

* qemu-img --output json output.

* "json:" pseudo-filenames generated by bdrv_refresh_filename().

* The rbd block driver's "=keyvalue-pairs" hack.

* In -object help on property default values.  Aside: use of JSON
  feels inappropriate here.

* Output of HMP qom-get.

* Argument conversion to QemuOpts for qdev_device_add() and HMP with
  qemu_opts_from_qdict()

  QMP and HMP device_add, virtio-net failover primary creation,
  xen-usb "usb-host" creation, HMP netdev_add, object_add.

* The uses of qobject_input_visitor_new_flat_confused()

  As far as I can tell, none of the visited types contain double
  values.

* Dumping ImageInfoSpecific with dump_qobject()

Fix by formatting with %.17g.  17 decimal digits always suffice for
IEEE double.

The change to expected test output illustrates the effect: the
rounding errors are gone, but some seemingly "nice" numbers now get
converted to not so nice strings, e.g. 0.42 to "0.41999999999999998".
This is because 0.42 is not representable exactly in double.  It's
more accurate in this example than strictly necessary, though.

If ugly accuracy bothers us, we can we can try using the least number
of digits that still converts back to the same double.  In this
example, "0.42" would do.

Signed-off-by: Markus Armbruster <armbru@redhat.com>
Message-Id: <20201210161452.2813491-7-armbru@redhat.com>
2020-12-19 10:37:16 +01:00

176 lines
4.0 KiB
C

/*
* QNum unit-tests.
*
* Copyright (C) 2009 Red Hat Inc.
* Copyright IBM, Corp. 2009
*
* Authors:
* Luiz Capitulino <lcapitulino@redhat.com>
* Anthony Liguori <aliguori@us.ibm.com>
*
* This work is licensed under the terms of the GNU LGPL, version 2.1 or later.
* See the COPYING.LIB file in the top-level directory.
*/
#include "qemu/osdep.h"
#include "qapi/qmp/qnum.h"
#include "qemu-common.h"
/*
* Public Interface test-cases
*
* (with some violations to access 'private' data)
*/
static void qnum_from_int_test(void)
{
QNum *qn;
const int value = -42;
qn = qnum_from_int(value);
g_assert(qn != NULL);
g_assert_cmpint(qn->kind, ==, QNUM_I64);
g_assert_cmpint(qn->u.i64, ==, value);
g_assert_cmpint(qn->base.refcnt, ==, 1);
g_assert_cmpint(qobject_type(QOBJECT(qn)), ==, QTYPE_QNUM);
qobject_unref(qn);
}
static void qnum_from_uint_test(void)
{
QNum *qn;
const uint64_t value = UINT64_MAX;
qn = qnum_from_uint(value);
g_assert(qn != NULL);
g_assert_cmpint(qn->kind, ==, QNUM_U64);
g_assert(qn->u.u64 == value);
g_assert(qn->base.refcnt == 1);
g_assert(qobject_type(QOBJECT(qn)) == QTYPE_QNUM);
qobject_unref(qn);
}
static void qnum_from_double_test(void)
{
QNum *qn;
const double value = -42.23423;
qn = qnum_from_double(value);
g_assert(qn != NULL);
g_assert_cmpint(qn->kind, ==, QNUM_DOUBLE);
g_assert_cmpfloat(qn->u.dbl, ==, value);
g_assert_cmpint(qn->base.refcnt, ==, 1);
g_assert_cmpint(qobject_type(QOBJECT(qn)), ==, QTYPE_QNUM);
qobject_unref(qn);
}
static void qnum_from_int64_test(void)
{
QNum *qn;
const int64_t value = 0x1234567890abcdefLL;
qn = qnum_from_int(value);
g_assert_cmpint((int64_t) qn->u.i64, ==, value);
qobject_unref(qn);
}
static void qnum_get_int_test(void)
{
QNum *qn;
const int value = 123456;
qn = qnum_from_int(value);
g_assert_cmpint(qnum_get_int(qn), ==, value);
qobject_unref(qn);
}
static void qnum_get_uint_test(void)
{
QNum *qn;
const int value = 123456;
uint64_t val;
int64_t ival;
qn = qnum_from_uint(value);
g_assert(qnum_get_try_uint(qn, &val));
g_assert_cmpuint(val, ==, value);
qobject_unref(qn);
qn = qnum_from_int(value);
g_assert(qnum_get_try_uint(qn, &val));
g_assert_cmpuint(val, ==, value);
qobject_unref(qn);
/* invalid cases */
qn = qnum_from_int(-1);
g_assert(!qnum_get_try_uint(qn, &val));
qobject_unref(qn);
qn = qnum_from_uint(-1ULL);
g_assert(!qnum_get_try_int(qn, &ival));
qobject_unref(qn);
qn = qnum_from_double(0.42);
g_assert(!qnum_get_try_uint(qn, &val));
qobject_unref(qn);
}
static void qobject_to_qnum_test(void)
{
QNum *qn;
qn = qnum_from_int(0);
g_assert(qobject_to(QNum, QOBJECT(qn)) == qn);
qobject_unref(qn);
qn = qnum_from_double(0);
g_assert(qobject_to(QNum, QOBJECT(qn)) == qn);
qobject_unref(qn);
}
static void qnum_to_string_test(void)
{
QNum *qn;
char *tmp;
qn = qnum_from_int(123456);
tmp = qnum_to_string(qn);
g_assert_cmpstr(tmp, ==, "123456");
g_free(tmp);
qobject_unref(qn);
qn = qnum_from_double(0.42);
tmp = qnum_to_string(qn);
g_assert_cmpstr(tmp, ==, "0.41999999999999998");
g_free(tmp);
qobject_unref(qn);
qn = qnum_from_double(2.718281828459045);
tmp = qnum_to_string(qn);
g_assert_cmpstr(tmp, ==, "2.7182818284590451");
g_free(tmp);
qobject_unref(qn);
}
int main(int argc, char **argv)
{
g_test_init(&argc, &argv, NULL);
g_test_add_func("/qnum/from_int", qnum_from_int_test);
g_test_add_func("/qnum/from_uint", qnum_from_uint_test);
g_test_add_func("/qnum/from_double", qnum_from_double_test);
g_test_add_func("/qnum/from_int64", qnum_from_int64_test);
g_test_add_func("/qnum/get_int", qnum_get_int_test);
g_test_add_func("/qnum/get_uint", qnum_get_uint_test);
g_test_add_func("/qnum/to_qnum", qobject_to_qnum_test);
g_test_add_func("/qnum/to_string", qnum_to_string_test);
return g_test_run();
}