From a03fd4855164b2b10e3c5517927eba60aef21fa5 Mon Sep 17 00:00:00 2001 From: "Dirk-Jan C. Binnema" Date: Thu, 23 Jul 2026 11:05:08 +0300 Subject: [PATCH] utils: support negative lexnums We use "lexnums" to efficiently/sortably store numbers >= 0; let's support negative numbers as well (albeit not very optimal in size yet) --- lib/utils/mu-utils.cc | 23 ++++++++++++++++++----- lib/utils/mu-utils.hh | 19 +++++++++++++++---- lib/utils/tests/test-utils.cc | 19 +++++++++++++++++++ 3 files changed, 52 insertions(+), 9 deletions(-) diff --git a/lib/utils/mu-utils.cc b/lib/utils/mu-utils.cc index ccc1197a..bb1c2b2a 100644 --- a/lib/utils/mu-utils.cc +++ b/lib/utils/mu-utils.cc @@ -1,5 +1,5 @@ /* -** Copyright (C) 2017-2022 Dirk-Jan C. Binnema +** Copyright (C) 2017-2026 Dirk-Jan C. Binnema ** ** This library is free software; you can redistribute it and/or ** modify it under the terms of the GNU Lesser General Public License @@ -581,17 +581,30 @@ std::string Mu::to_lexnum(int64_t val) { char buf[18]; /* 1 byte prefix + hex + \0 */ - buf[0] = 'f' + ::snprintf(buf + 1, sizeof(buf) - 1, "%" PRIx64, val); + const auto len = ::snprintf(buf + 1, sizeof(buf) - 1, "%" PRIx64, + static_cast(val)); + /* an uppercase prefix marks a negative value (see mu-utils.hh); + * uppercase sorts before lowercase, so negative values sort before + * the positive ones. */ + buf[0] = (val < 0 ? 'F' : 'f') + len; return buf; } int64_t Mu::from_lexnum(const std::string& str) { - int64_t val{}; - std::from_chars(str.c_str() + 1, str.c_str() + str.size(), val, 16); + uint64_t val{}; + if (str.empty()) + return 0; - return val; + const auto res = std::from_chars(str.c_str() + 1, + str.c_str() + str.size(), val, 16); + if (res.ec != std::errc{}) + return 0; + + /* negative values are the two's-complement representation, so + * simply cast back. */ + return static_cast(val); } bool diff --git a/lib/utils/mu-utils.hh b/lib/utils/mu-utils.hh index b276cf0b..e84ca272 100644 --- a/lib/utils/mu-utils.hh +++ b/lib/utils/mu-utils.hh @@ -677,16 +677,27 @@ inline std::string shell_quote(const std::string& str) { /* - * Lexnums are lexicographically sortable string representations of non-negative - * integers. Start with 'f' + length of hex-representation number, followed by - * the hex representation itself. So, + * Lexnums are lexicographically sortable string representations of integers. + * + * Non-negative values start with 'f' + length of the hex-representation, + * followed by the hex representation itself. So, * * 0 -> 'g0' * 1 -> 'g1' * 10 -> 'ga' * 16 -> 'h10' * - * etc. + * etc. Negative values use the corresponding uppercase prefix, followed by the + * hex of their two's-complement representation (always 16 digits, hence always + * 'V'). Since uppercase sorts before lowercase in ASCII, and the + * two's-complement hex of negative values increases with the value, all lexnums + * sort in numerical order. So, + * + * -1 -> 'Vffffffffffffffff' + * -2 -> 'Vfffffffffffffffe' + * + * So, this is not quite optimimal (length-wise) for negative values; but at least + * they are supported now (they are rare). */ std::string to_lexnum(int64_t val); int64_t from_lexnum(const std::string& str); diff --git a/lib/utils/tests/test-utils.cc b/lib/utils/tests/test-utils.cc index c966a4e1..1ff4b9ff 100644 --- a/lib/utils/tests/test-utils.cc +++ b/lib/utils/tests/test-utils.cc @@ -18,6 +18,7 @@ */ #include +#include #include #include @@ -302,6 +303,24 @@ test_to_from_lexnum() g_assert_cmpuint(from_lexnum(to_lexnum(0)), ==, 0); g_assert_cmpuint(from_lexnum(to_lexnum(7777)), ==, 7777); g_assert_cmpuint(from_lexnum(to_lexnum(9876543)), ==, 9876543); + + /* negative values have an uppercase prefix and thus sort before all + * positive values, in numerical order */ + assert_equal(to_lexnum(-1), "Vffffffffffffffff"); + g_assert_cmpint(from_lexnum(to_lexnum(-1)), ==, -1); + g_assert_cmpint(from_lexnum(to_lexnum(-12345)), ==, -12345); + g_assert_true(to_lexnum(-2) < to_lexnum(-1)); + g_assert_true(to_lexnum(-1) < to_lexnum(0)); + g_assert_true(to_lexnum(-12345) < to_lexnum(12345)); + + constexpr auto int64_min = std::numeric_limits::min(); + constexpr auto int64_max = std::numeric_limits::max(); + g_assert_cmpint(from_lexnum(to_lexnum(int64_min)), ==, int64_min); + g_assert_cmpint(from_lexnum(to_lexnum(int64_max)), ==, int64_max); + g_assert_true(to_lexnum(int64_min) < to_lexnum(-1)); + g_assert_true(to_lexnum(0) < to_lexnum(int64_max)); + + g_assert_cmpint(from_lexnum(""), ==, 0); } static void