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)
This commit is contained in:
@ -1,5 +1,5 @@
|
||||
/*
|
||||
** Copyright (C) 2017-2022 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
** Copyright (C) 2017-2026 Dirk-Jan C. Binnema <djcb@djcbsoftware.nl>
|
||||
**
|
||||
** 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<uint64_t>(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<int64_t>(val);
|
||||
}
|
||||
|
||||
bool
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
*/
|
||||
|
||||
#include <vector>
|
||||
#include <limits>
|
||||
#include <glib.h>
|
||||
|
||||
#include <iostream>
|
||||
@ -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<int64_t>::min();
|
||||
constexpr auto int64_max = std::numeric_limits<int64_t>::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
|
||||
|
||||
Reference in New Issue
Block a user