Move the formatting of <chrono> durations to QDebug & QtTest

[ChangeLog][QtCore][QDebug] Added pretty formatting of C++ <chrono>
durations.

[ChangeLog][QtTest] Added pretty formatting of C++ <chrono> durations
for QCOMPARE expressions.

Change-Id: I3b169860d8bd41e9be6bfffd1757cc087ba957fa
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Thiago Macieira 2023-04-20 17:35:22 -07:00
parent 59f8da17e6
commit a2551c45d4
9 changed files with 388 additions and 46 deletions

View File

@ -15,6 +15,8 @@
#include <private/qtextstream_p.h> #include <private/qtextstream_p.h>
#include <private/qtools_p.h> #include <private/qtools_p.h>
#include <q20chrono.h>
QT_BEGIN_NAMESPACE QT_BEGIN_NAMESPACE
using namespace QtMiscUtils; using namespace QtMiscUtils;
@ -345,6 +347,90 @@ void QDebug::putByteArray(const char *begin, size_t length, Latin1Content conten
} }
} }
/*!
\since 6.6
\internal
Helper to the std::chrono::duration debug streaming output.
*/
QByteArray QDebug::timeUnit(qint64 num, qint64 den)
{
using namespace std::chrono;
using namespace q20::chrono;
if (num == 1 && den > 1) {
// sub-multiple of seconds
char prefix = '\0';
auto tryprefix = [&](auto d, char c) {
static_assert(decltype(d)::num == 1, "not an SI prefix");
if (den == decltype(d)::den)
prefix = c;
};
// "u" should be "µ", but debugging output is not always UTF-8-safe
tryprefix(std::milli{}, 'm');
tryprefix(std::micro{}, 'u');
tryprefix(std::nano{}, 'n');
tryprefix(std::pico{}, 'p');
tryprefix(std::femto{}, 'f');
tryprefix(std::atto{}, 'a');
// uncommon ones later
tryprefix(std::centi{}, 'c');
tryprefix(std::deci{}, 'd');
if (prefix) {
char unit[3] = { prefix, 's' };
return QByteArray(unit, sizeof(unit) - 1);
}
}
const char *unit = nullptr;
if (num > 1 && den == 1) {
// multiple of seconds - but we don't use SI prefixes
auto tryunit = [&](auto d, const char *name) {
static_assert(decltype(d)::period::den == 1, "not a multiple of a second");
if (unit || num % decltype(d)::period::num)
return;
unit = name;
num /= decltype(d)::period::num;
};
tryunit(years{}, "yr");
tryunit(weeks{}, "wk");
tryunit(days{}, "d");
tryunit(hours{}, "h");
tryunit(minutes{}, "min");
}
if (!unit)
unit = "s";
if (num == 1 && den == 1)
return unit;
if (Q_UNLIKELY(num < 1 || den < 1))
return QString::asprintf("<invalid time unit %lld/%lld>", num, den).toLatin1();
// uncommon units: will return something like "[2/3]s"
// strlen("[/]min") = 6
char buf[2 * (std::numeric_limits<qint64>::digits10 + 2) + 10];
size_t len = 0;
auto appendChar = [&](char c) {
Q_ASSERT(len < sizeof(buf));
buf[len++] = c;
};
auto appendNumber = [&](qint64 value) {
if (value >= 10'000 && (value % 1000) == 0)
len += qsnprintf(buf + len, sizeof(buf) - len, "%.6g", double(value)); // "1e+06"
else
len += qsnprintf(buf + len, sizeof(buf) - len, "%lld", value);
};
appendChar('[');
appendNumber(num);
if (den != 1) {
appendChar('/');
appendNumber(den);
}
appendChar(']');
memcpy(buf + len, unit, strlen(unit));
return QByteArray(buf, len + strlen(unit));
}
/*! /*!
\fn QDebug::swap(QDebug &other) \fn QDebug::swap(QDebug &other)
\since 5.0 \since 5.0
@ -776,6 +862,18 @@ QDebug &QDebug::resetFormat()
\endlist \endlist
*/ */
/*!
\since 6.6
\fn template <typename Rep, typename Period> QDebug &QDebug::operator<<(std::chrono::duration<Rep, Period> duration)
Prints the time duration \a duration to the stream and returns a reference
to the stream. The printed string is the numeric representation of the
period followed by the time unit, similar to what the C++ Standard Library
would produce with \c{std::ostream}.
The unit is not localized.
*/
/*! /*!
\fn template <class T> QString QDebug::toString(T &&object) \fn template <class T> QString QDebug::toString(T &&object)
\since 6.0 \since 6.0

View File

@ -16,6 +16,7 @@
#include <QtCore/qsharedpointer.h> #include <QtCore/qsharedpointer.h>
// all these have already been included by various headers above, but don't rely on indirect includes: // all these have already been included by various headers above, but don't rely on indirect includes:
#include <chrono>
#include <list> #include <list>
#include <map> #include <map>
#include <string> #include <string>
@ -67,6 +68,7 @@ class QT6_ONLY(Q_CORE_EXPORT) QDebug : public QIODeviceBase
QT7_ONLY(Q_CORE_EXPORT) void putUcs4(uint ucs4); QT7_ONLY(Q_CORE_EXPORT) void putUcs4(uint ucs4);
QT7_ONLY(Q_CORE_EXPORT) void putString(const QChar *begin, size_t length); QT7_ONLY(Q_CORE_EXPORT) void putString(const QChar *begin, size_t length);
QT7_ONLY(Q_CORE_EXPORT) void putByteArray(const char *begin, size_t length, Latin1Content content); QT7_ONLY(Q_CORE_EXPORT) void putByteArray(const char *begin, size_t length, Latin1Content content);
QT7_ONLY(Q_CORE_EXPORT) static QByteArray timeUnit(qint64 num, qint64 den);
public: public:
explicit QDebug(QIODevice *device) : stream(new Stream(device)) {} explicit QDebug(QIODevice *device) : stream(new Stream(device)) {}
explicit QDebug(QString *string) : stream(new Stream(string)) {} explicit QDebug(QString *string) : stream(new Stream(string)) {}
@ -189,6 +191,13 @@ public:
{ return *this << QString::fromUcs4(s.data(), s.size()); } { return *this << QString::fromUcs4(s.data(), s.size()); }
#endif // !Q_QDOC #endif // !Q_QDOC
template <typename Rep, typename Period>
QDebug &operator<<(std::chrono::duration<Rep, Period> duration)
{
stream->ts << duration.count() << timeUnit(Period::num, Period::den);
return maybeSpace();
}
template <typename T> template <typename T>
static QString toString(T &&object) static QString toString(T &&object)
{ {

View File

@ -356,6 +356,19 @@ template<> inline char *toString(const QCborMap &m)
return Internal::QCborValueFormatter::format(m); return Internal::QCborValueFormatter::format(m);
} }
template <typename Rep, typename Period> char *toString(std::chrono::duration<Rep, Period> dur)
{
QString r;
QDebug d(&r);
d.nospace() << qSetRealNumberPrecision(9) << dur;
if constexpr (Period::num != 1 || Period::den != 1) {
// include the equivalent value in seconds, in parentheses
using namespace std::chrono;
d << " (" << duration_cast<duration<qreal>>(dur).count() << "s)";
}
return qstrdup(std::move(r).toUtf8().constData());
}
template <typename T1, typename T2> template <typename T1, typename T2>
inline char *toString(const std::pair<T1, T2> &pair) inline char *toString(const std::pair<T1, T2> &pair)
{ {

View File

@ -360,6 +360,9 @@ namespace QTest
template <class... Types> template <class... Types>
inline char *toString(const std::tuple<Types...> &tuple); inline char *toString(const std::tuple<Types...> &tuple);
template <typename Rep, typename Period>
inline char *toString(std::chrono::duration<Rep, Period> duration);
Q_TESTLIB_EXPORT char *toHexRepresentation(const char *ba, qsizetype length); Q_TESTLIB_EXPORT char *toHexRepresentation(const char *ba, qsizetype length);
Q_TESTLIB_EXPORT char *toPrettyCString(const char *unicode, qsizetype length); Q_TESTLIB_EXPORT char *toPrettyCString(const char *unicode, qsizetype length);
Q_TESTLIB_EXPORT char *toPrettyUnicode(QStringView string); Q_TESTLIB_EXPORT char *toPrettyUnicode(QStringView string);

View File

@ -16,6 +16,8 @@
#include <QMimeDatabase> #include <QMimeDatabase>
#include <QMetaType> #include <QMetaType>
#include <q20chrono.h>
#ifdef __cpp_lib_memory_resource #ifdef __cpp_lib_memory_resource
# include <memory_resource> # include <memory_resource>
namespace pmr = std::pmr; namespace pmr = std::pmr;
@ -23,6 +25,8 @@ namespace pmr = std::pmr;
namespace pmr = std; namespace pmr = std;
#endif #endif
using namespace std::chrono;
using namespace q20::chrono;
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
static_assert(QTypeTraits::has_ostream_operator_v<QDebug, int>); static_assert(QTypeTraits::has_ostream_operator_v<QDebug, int>);
@ -83,6 +87,8 @@ private slots:
void qDebugQByteArray() const; void qDebugQByteArray() const;
void qDebugQByteArrayView() const; void qDebugQByteArrayView() const;
void qDebugQFlags() const; void qDebugQFlags() const;
void qDebugStdChrono_data() const;
void qDebugStdChrono() const;
void textStreamModifiers() const; void textStreamModifiers() const;
void resetFormat() const; void resetFormat() const;
void defaultMessagehandler() const; void defaultMessagehandler() const;
@ -1102,6 +1108,107 @@ void tst_QDebug::qDebugQFlags() const
QCOMPARE(s_msg, QString::fromLatin1("QFlags<tst_QDebug::FlagType>(EnumFlag1)")); QCOMPARE(s_msg, QString::fromLatin1("QFlags<tst_QDebug::FlagType>(EnumFlag1)"));
} }
using ToStringFunction = std::function<QString()>;
void tst_QDebug::qDebugStdChrono_data() const
{
using attoseconds = duration<int64_t, std::atto>;
using femtoseconds = duration<int64_t, std::femto>;
using picoseconds = duration<int64_t, std::pico>;
using centiseconds = duration<int64_t, std::centi>;
using deciseconds = duration<int64_t, std::deci>;
using quadriennia = duration<int, std::ratio_multiply<std::ratio<4>, years::period>>;
using decades = duration<int, std::ratio_multiply<years::period, std::deca>>; // decayears
using centuries = duration<int16_t, std::ratio_multiply<years::period, std::hecto>>; // hectoyears
using millennia = duration<int16_t, std::ratio_multiply<years::period, std::kilo>>; // kiloyears
using gigayears = duration<int8_t, std::ratio_multiply<years::period, std::giga>>;
using fortnights = duration<int, std::ratio_multiply<days::period, std::ratio<14>>>;
using microfortnights = duration<int64_t, std::ratio_multiply<fortnights::period, std::micro>>;
using telecom = duration<int64_t, std::ratio<1, 8000>>; // 8 kHz
using kiloseconds = duration<int64_t, std::kilo>;
using exaseconds = duration<int8_t, std::exa>;
using meter_per_light = duration<int64_t, std::ratio<1, 299'792'458>>;
using kilometer_per_light = duration<int64_t, std::ratio<1000, 299'792'458>>;
QTest::addColumn<ToStringFunction>("fn");
QTest::addColumn<QString>("expected");
auto addRow = [](const char *name, auto duration, const char *expected) {
auto toString = [duration]() { return QDebug::toString(duration); };
QTest::newRow(name) << ToStringFunction(toString) << expected;
};
addRow("1as", attoseconds{1}, "1as");
addRow("1fs", femtoseconds{1}, "1fs");
addRow("1ps", picoseconds{1}, "1ps");
addRow("0ns", 0ns, "0ns");
addRow("1000ns", 1000ns, "1000ns");
addRow("0us", 0us, "0us");
addRow("0ms", 0ms, "0ms");
addRow("1cs", centiseconds{1}, "1cs");
addRow("2ds", deciseconds{2}, "2ds");
addRow("-1s", -1s, "-1s");
addRow("0s", 0s, "0s");
addRow("1s", 1s, "1s");
addRow("60s", 60s, "60s");
addRow("1min", 1min, "1min");
addRow("1h", 1h, "1h");
addRow("1days", days{1}, "1d");
addRow("365days", days{365}, "365d");
addRow("1weeks", weeks{1}, "1wk");
addRow("1years", years{1}, "1yr"); // 365.2425 days
addRow("42years", years{42}, "42yr");
addRow("1ks", kiloseconds{1}, "1[1000]s");
addRow("2fortnights", fortnights{2}, "2[2]wk");
addRow("1quadriennia", quadriennia{1}, "1[4]yr");
addRow("1decades", decades{1}, "1[10]yr");
addRow("1centuries", centuries{1}, "1[100]yr");
addRow("1millennia", millennia{1}, "1[1000]yr");
#if defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
// some OSes print the exponent differently
addRow("1Es", exaseconds{1}, "1[1e+18]s");
addRow("13gigayears", gigayears{13}, "13[1e+09]yr");
#endif
// months are one twelfth of a Gregorian year, not 30 days
addRow("1months", months{1}, "1[2629746]s");
// weird units
addRow("2microfortnights", microfortnights{2}, "2[756/625]s");
addRow("1telecom", telecom{1}, "1[1/8000]s");
addRow("10m/c", meter_per_light{10}, "10[1/299792458]s");
addRow("10km/c", kilometer_per_light{10}, "10[500/149896229]s");
// real floting point
using fpsec = duration<double>;
using fpmsec = duration<double, std::milli>;
using fpnsec = duration<double, std::nano>;
addRow("1.0s", fpsec{1}, "1s");
addRow("1.5s", fpsec{1.5}, "1.5s");
addRow("1.0ms", fpmsec{1}, "1ms");
addRow("1.5ms", fpmsec{1.5}, "1.5ms");
addRow("1.0ns", fpnsec{1}, "1ns");
addRow("1.5ns", fpnsec{1.5}, "1.5ns");
// and some precision setting too
QTest::newRow("1.00000ns")
<< ToStringFunction([]() {
QString buffer;
QDebug d(&buffer);
d.nospace() << qSetRealNumberPrecision(5) << Qt::fixed << fpnsec{1};
return buffer;
}) << "1.00000ns";
}
void tst_QDebug::qDebugStdChrono() const
{
QFETCH(ToStringFunction, fn);
QFETCH(QString, expected);
QCOMPARE(fn(), expected);
}
void tst_QDebug::textStreamModifiers() const void tst_QDebug::textStreamModifiers() const
{ {
QString file, function; QString file, function;

View File

@ -27,52 +27,6 @@ template<> char *toString(const QDeadlineTimer &dt)
dt.hasExpired() ? " (expired)" : ""); dt.hasExpired() ? " (expired)" : "");
return buf; return buf;
} }
template <typename Rep, typename Period> char *toString(std::chrono::duration<Rep, Period> dur)
{
using namespace std::chrono;
static_assert(sizeof(double) == sizeof(qlonglong));
if constexpr (Period::num == 1 && sizeof(Rep) <= sizeof(qlonglong)) {
// typical case: second or sub-multiple of second, in a representation
// we can directly use
char *buf = new char[128];
if constexpr (std::is_integral_v<Rep>) {
char unit[] = "ss";
if constexpr (std::is_same_v<Period, std::atto>) { // from Norwegian "atten", 18
unit[0] = 'a';
} else if constexpr (std::is_same_v<Period, std::femto>) { // Norwegian "femten", 15
unit[0] = 'f';
} else if constexpr (std::is_same_v<Period, std::pico>) {
unit[0] = 'p';
} else if constexpr (std::is_same_v<Period, std::nano>) {
unit[0] = 'n';
} else if constexpr (std::is_same_v<Period, std::micro>) {
unit[0] = 'u'; // µ, really, but the output may not be UTF-8-safe
} else if constexpr (std::is_same_v<Period, std::milli>) {
unit[0] = 'm';
} else {
// deci, centi, cycles of something (60 Hz, 8000 Hz, etc.)
static_assert(Period::den == 1,
"Unsupported std::chrono::duration of a sub-multiple of second");
unit[1] = '\0';
}
// cast to qlonglong in case Rep is not int64_t
qsnprintf(buf, 128, "%lld %s", qlonglong(dur.count()), unit);
} else {
auto secs = duration_cast<duration<double>>(dur);
qsnprintf(buf, 128, "%g s", secs.count());
}
return buf;
} else if constexpr (std::is_integral_v<Rep> && Period::den == 1) {
// multiple of second, so just print it in seconds
return toString(std::chrono::seconds(dur));
} else {
// something else, use floating-point seconds
return toString(std::chrono::duration_cast<double>(dur));
}
}
} }
QT_END_NAMESPACE QT_END_NAMESPACE

View File

@ -2,6 +2,7 @@
# SPDX-License-Identifier: BSD-3-Clause # SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(qsignalspy) add_subdirectory(qsignalspy)
add_subdirectory(tostring)
# QTBUG-88507 # QTBUG-88507
if(QT_FEATURE_process AND NOT ANDROID) if(QT_FEATURE_process AND NOT ANDROID)

View File

@ -0,0 +1,7 @@
# Copyright (C) 2023 Intel Corporation.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_test(tst_tostring
SOURCES
tst_tostring.cpp
)

View File

@ -0,0 +1,150 @@
// Copyright (C) 2023 Intel Corporation.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QTest>
#include <memory>
#include <q20chrono.h>
using ToStringFunction = std::function<char *()>;
class tst_toString : public QObject
{
Q_OBJECT
private:
void addColumns();
void testRows();
private slots:
void chrono_duration_data();
void chrono_duration() { testRows(); }
};
void tst_toString::addColumns()
{
QTest::addColumn<ToStringFunction>("fn");
QTest::addColumn<QByteArrayView>("expected");
QTest::addColumn<QByteArrayView>("expr");
QTest::addColumn<QByteArrayView>("file");
QTest::addColumn<int>("line");
}
void tst_toString::testRows()
{
QFETCH(ToStringFunction, fn);
QFETCH(QByteArrayView, expected);
QFETCH(QByteArrayView, expr);
QFETCH(QByteArrayView, file);
QFETCH(int, line);
std::unique_ptr<char []> ptr{fn()};
QTest::qCompare(ptr.get(), expected, expr.data(), expected.data(), file.data(), line);
}
template <typename T> void addRow(QByteArrayView name, T &&value, QByteArrayView expression,
QByteArrayView expected, QByteArrayView file, int line)
{
ToStringFunction fn = [v = std::move(value)]() { return QTest::toString(v); };
QTest::newRow(name.data()) << fn << expected << expression << file << line;
}
#define ADD_ROW(name, expr, expected) \
::addRow(name, expr, #expr, expected, __FILE__, __LINE__)
void tst_toString::chrono_duration_data()
{
addColumns();
using namespace std::chrono;
using namespace q20::chrono;
using attoseconds = duration<int64_t, std::atto>;
using femtoseconds = duration<int64_t, std::femto>;
using picoseconds = duration<int64_t, std::pico>;
using centiseconds = duration<int64_t, std::centi>;
using deciseconds = duration<int64_t, std::deci>;
using kiloseconds = duration<int64_t, std::kilo>;
using decades = duration<int, std::ratio_multiply<years::period, std::deca>>; // decayears
using centuries = duration<int16_t, std::ratio_multiply<years::period, std::hecto>>; // hectoyears
using millennia = duration<int16_t, std::ratio_multiply<years::period, std::kilo>>; // kiloyears
using gigayears = duration<int8_t, std::ratio_multiply<years::period, std::giga>>;
using fortnights = duration<int, std::ratio_multiply<days::period, std::ratio<14>>>;
using microfortnights = duration<int64_t, std::ratio_multiply<fortnights::period, std::micro>>;
using meter_per_light = duration<int64_t, std::ratio<1, 299'792'458>>;
using kilometer_per_light = duration<int64_t, std::ratio<1000, 299'792'458>>;
using AU_per_light = duration<int64_t, std::ratio<149'597'871'800, 299'792'458>>;
using pstn_rate = duration<int64_t, std::ratio<1, 8000>>; // PSTN sampling rate (8 kHz)
using hyperfine = duration<int64_t, std::ratio<1, 9'192'631'770>>; // definition of second
ADD_ROW("1as", attoseconds{1}, "1as (1e-18s)"); // from Norwegian "atten" (18)
ADD_ROW("1fs", femtoseconds{1}, "1fs (1e-15s)"); // from Norwegian "femten" (15)
ADD_ROW("1ps", picoseconds{1}, "1ps (1e-12s)"); // from Italian piccolo?
ADD_ROW("0ns", 0ns, "0ns (0s)");
ADD_ROW("1000ns", 1000ns, "1000ns (1e-06s)");
ADD_ROW("1us", 1us, "1us (1e-06s)");
ADD_ROW("125us", 125us, "125us (0.000125s)");
ADD_ROW("0ms", 0ms, "0ms (0s)");
ADD_ROW("-1s", -1s, "-1s");
ADD_ROW("0s", 0s, "0s");
ADD_ROW("1cs", centiseconds{1}, "1cs (0.01s)");
ADD_ROW("2ds", deciseconds{2}, "2ds (0.2s)");
ADD_ROW("1s", 1s, "1s");
ADD_ROW("60s", 60s, "60s");
ADD_ROW("1min", 1min, "1min (60s)");
ADD_ROW("1h", 1h, "1h (3600s)");
ADD_ROW("1days", days{1}, "1d (86400s)");
ADD_ROW("7days", days{7}, "7d (604800s)");
ADD_ROW("1weeks", weeks{1}, "1wk (604800s)");
ADD_ROW("365days", days{365}, "365d (31536000s)");
ADD_ROW("1years", years{1}, "1yr (31556952s)"); // 365.2425 days
ADD_ROW("2ks", kiloseconds{2}, "2[1000]s (2000s)");
ADD_ROW("1fortnights", fortnights{1}, "1[2]wk (1209600s)");
ADD_ROW("1decades", decades{1}, "1[10]yr (315569520s)");
ADD_ROW("1centuries", centuries{1}, "1[100]yr (3.1556952e+09s)");
ADD_ROW("1millennia", millennia{1}, "1[1000]yr (3.1556952e+10s)");
#if defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
// some OSes print the exponent differently
ADD_ROW("13gigayears", gigayears{13}, "13[1e+09]yr (4.10240376e+17s)");
#endif
// months are one twelfth of a Gregorian year, not 30 days
ADD_ROW("1months", months{1}, "1[2629746]s (2629746s)");
ADD_ROW("12months", months{12}, "12[2629746]s (31556952s)");
// weird units
ADD_ROW("2microfortnights", microfortnights{2}, "2[756/625]s (2.4192s)");
ADD_ROW("1pstn_rate", pstn_rate{1}, "1[1/8000]s (0.000125s)"); // 125µs
ADD_ROW("10m/c", meter_per_light{10}, "10[1/299792458]s (3.33564095e-08s)");
ADD_ROW("10km/c", kilometer_per_light{10}, "10[500/149896229]s (3.33564095e-05s)");
ADD_ROW("1AU/c", AU_per_light{1}, "1[74798935900/149896229]s (499.004788s)");
ADD_ROW("Cs133-hyperfine", hyperfine{1}, "1[1/9192631770]s (1.08782776e-10s)");
ADD_ROW("1sec-definition", hyperfine{9'192'631'770}, "9192631770[1/9192631770]s (1s)");
ADD_ROW("8000pstn_rate", pstn_rate{8000}, "8000[1/8000]s (1s)");
// real floting point
// current (2023) best estimate is 13.813 ± 0.038 billion years (Plank Collaboration)
using universe = duration<double, std::ratio_multiply<std::ratio<13'813'000'000>, years::period>>;
using fpksec = duration<double, std::kilo>;
using fpsec = duration<double>;
using fpmsec = duration<double, std::milli>;
using fpnsec = duration<double, std::nano>;
using fpGyr = duration<double, std::ratio_multiply<years::period, std::giga>>;
ADD_ROW("1.0s", fpsec{1}, "1s");
ADD_ROW("1.5s", fpsec{1.5}, "1.5s");
ADD_ROW("-1.0ms", fpmsec{-1}, "-1ms (-0.001s)");
ADD_ROW("1.5ms", fpmsec{1.5}, "1.5ms (0.0015s)");
ADD_ROW("1.0ns", fpnsec{1}, "1ns (1e-09s)");
ADD_ROW("-1.5ns", fpnsec{-1.5}, "-1.5ns (-1.5e-09s)");
ADD_ROW("1.0ks", fpksec{1}, "1[1000]s (1000s)");
ADD_ROW("-1.5ks", fpksec{-1.5}, "-1.5[1000]s (-1500s)");
ADD_ROW("1.0zs", fpsec{1e-21}, "1e-21s"); // zeptosecond
ADD_ROW("1.0ys", fpsec{1e-24}, "1e-24s"); // yoctosecond
ADD_ROW("planck-time", fpsec(5.39124760e-44), "5.3912476e-44s");
#if defined(Q_OS_LINUX) || defined(Q_OS_DARWIN)
// some OSes print the exponent differently
ADD_ROW("13.813Gyr", fpGyr(13.813), "13.813[1e+09]yr (4.35896178e+17s)");
ADD_ROW("1universe", universe{1}, "1[1.3813e+10]yr (4.35896178e+17s)");
#endif
}
QTEST_APPLESS_MAIN(tst_toString)
#include "tst_tostring.moc"