From: Lennart Poettering Date: Wed, 17 Feb 2021 15:53:11 +0000 (+0100) Subject: util: add some helpers for converting percent/permille/permyriad to parts of 2^32-1 X-Git-Tag: v248-rc1~46^2~4 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=3b6e71ad0351c5ed2dd8382a63ab3581fa2d6174;p=thirdparty%2Fsystemd.git util: add some helpers for converting percent/permille/permyriad to parts of 2^32-1 At various places we accept values scaled to the range 0…2^32-1 which are exposed to the user as percentages/permille/permyriad. Let's add some helper macros (actually: typesafe macro-like functions) that help with converting our internal encoding to the external encodings. benefits: some of the previous code rounded up, some down. let's always round to nearest, to ensure that our conversions are reversible. Also, check for overflows correctly. This also adds a test that makes sure that for the full percent/permille/permyriad ranges we can convert forth and back without loss of accuracy. --- diff --git a/src/basic/percent-util.h b/src/basic/percent-util.h index 26707e8703a..d806ec007a7 100644 --- a/src/basic/percent-util.h +++ b/src/basic/percent-util.h @@ -1,6 +1,11 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ #pragma once +#include +#include + +#include "macro.h" + int parse_percent_unbounded(const char *p); int parse_percent(const char *p); @@ -9,3 +14,48 @@ int parse_permille(const char *p); int parse_permyriad_unbounded(const char *p); int parse_permyriad(const char *p); + +/* Some macro-like helpers that convert a percent/permille/permyriad value (as parsed by parse_percent()) to + * a value relative to 100% == 2^32-1. Rounds to closest. */ +static inline uint32_t UINT32_SCALE_FROM_PERCENT(int percent) { + assert_cc(INT_MAX <= UINT32_MAX); + return (uint32_t) (((uint64_t) percent * UINT32_MAX + 50) / 100U); +} + +static inline uint32_t UINT32_SCALE_FROM_PERMILLE(int permille) { + return (uint32_t) (((uint64_t) permille * UINT32_MAX + 500) / 1000U); +} + +static inline uint32_t UINT32_SCALE_FROM_PERMYRIAD(int permyriad) { + return (uint32_t) (((uint64_t) permyriad * UINT32_MAX + 5000) / 10000U); +} + +static inline int UINT32_SCALE_TO_PERCENT(uint32_t scale) { + uint32_t u; + + u = (uint32_t) ((((uint64_t) scale) * 100U + UINT32_MAX/2) / UINT32_MAX); + if (u > INT_MAX) + return -ERANGE; + + return (int) u; +} + +static inline int UINT32_SCALE_TO_PERMILLE(uint32_t scale) { + uint32_t u; + + u = (uint32_t) ((((uint64_t) scale) * 1000U + UINT32_MAX/2) / UINT32_MAX); + if (u > INT_MAX) + return -ERANGE; + + return (int) u; +} + +static inline int UINT32_SCALE_TO_PERMYRIAD(uint32_t scale) { + uint32_t u; + + u = (uint32_t) ((((uint64_t) scale) * 10000U + UINT32_MAX/2) / UINT32_MAX); + if (u > INT_MAX) + return -ERANGE; + + return (int) u; +} diff --git a/src/test/test-percent-util.c b/src/test/test-percent-util.c index 75f8a6c83d8..b8801438a75 100644 --- a/src/test/test-percent-util.c +++ b/src/test/test-percent-util.c @@ -2,6 +2,7 @@ #include "percent-util.h" #include "tests.h" +#include "time-util.h" static void test_parse_percent(void) { assert_se(parse_percent("") == -EINVAL); @@ -150,6 +151,51 @@ static void test_parse_permyriad_unbounded(void) { assert_se(parse_permyriad_unbounded("42949672.96%") == -ERANGE); } +static void test_scale(void) { + /* Check some fixed values */ + assert_se(UINT32_SCALE_FROM_PERCENT(0) == 0); + assert_se(UINT32_SCALE_FROM_PERCENT(50) == UINT32_MAX/2+1); + assert_se(UINT32_SCALE_FROM_PERCENT(100) == UINT32_MAX); + + assert_se(UINT32_SCALE_FROM_PERMILLE(0) == 0); + assert_se(UINT32_SCALE_FROM_PERMILLE(500) == UINT32_MAX/2+1); + assert_se(UINT32_SCALE_FROM_PERMILLE(1000) == UINT32_MAX); + + assert_se(UINT32_SCALE_FROM_PERMYRIAD(0) == 0); + assert_se(UINT32_SCALE_FROM_PERMYRIAD(5000) == UINT32_MAX/2+1); + assert_se(UINT32_SCALE_FROM_PERMYRIAD(10000) == UINT32_MAX); + + /* Make sure there's no numeric noise on the 0%…100% scale when converting from percent and back. */ + for (int percent = 0; percent <= 100; percent++) { + log_debug("%i%% → %" PRIu32 " → %i%%", + percent, + UINT32_SCALE_FROM_PERCENT(percent), + UINT32_SCALE_TO_PERCENT(UINT32_SCALE_FROM_PERCENT(percent))); + + assert_se(UINT32_SCALE_TO_PERCENT(UINT32_SCALE_FROM_PERCENT(percent)) == percent); + } + + /* Make sure there's no numeric noise on the 0‰…1000‰ scale when converting from permille and back. */ + for (int permille = 0; permille <= 1000; permille++) { + log_debug("%i‰ → %" PRIu32 " → %i‰", + permille, + UINT32_SCALE_FROM_PERMILLE(permille), + UINT32_SCALE_TO_PERMILLE(UINT32_SCALE_FROM_PERMILLE(permille))); + + assert_se(UINT32_SCALE_TO_PERMILLE(UINT32_SCALE_FROM_PERMILLE(permille)) == permille); + } + + /* Make sure there's no numeric noise on the 0‱…10000‱ scale when converting from permyriad and back. */ + for (int permyriad = 0; permyriad <= 10000; permyriad++) { + log_debug("%i‱ → %" PRIu32 " → %i‱", + permyriad, + UINT32_SCALE_FROM_PERMYRIAD(permyriad), + UINT32_SCALE_TO_PERMYRIAD(UINT32_SCALE_FROM_PERMYRIAD(permyriad))); + + assert_se(UINT32_SCALE_TO_PERMYRIAD(UINT32_SCALE_FROM_PERMYRIAD(permyriad)) == permyriad); + } +} + int main(int argc, char *argv[]) { test_setup_logging(LOG_DEBUG); @@ -159,6 +205,7 @@ int main(int argc, char *argv[]) { test_parse_permille_unbounded(); test_parse_permyriad(); test_parse_permyriad_unbounded(); + test_scale(); return 0; }