From f4d94066c81f56757477ef70eb607065cb9a1e90 Mon Sep 17 00:00:00 2001 From: Martin Pulec Date: Mon, 31 Mar 2025 09:14:34 +0200 Subject: [PATCH] unit_evaluate: accept binary suffixes + test --- src/utils/misc.cpp | 38 ++++++++++++++++++++++++++++---------- test/misc_test.cpp | 32 ++++++++++++++++++++++++++++++++ test/run_tests.c | 2 ++ test/unit_common.h | 18 ++++++++++++++++++ 4 files changed, 80 insertions(+), 10 deletions(-) diff --git a/src/utils/misc.cpp b/src/utils/misc.cpp index 303f7c5d8..c3a796087 100644 --- a/src/utils/misc.cpp +++ b/src/utils/misc.cpp @@ -90,7 +90,7 @@ unit_evaluate(const char *str, const char **endptr) } /** - * Converts units in format [.][kMG] to floating point representation. + * Converts units in format [.][kMG[i]] to floating point representation. * * @param str string to be parsed, suffix following SI suffix is ignored (as in 1ms or 100MB) * @param case_sensitive if true 'm' will be considered as milli, otherwise mega @@ -101,6 +101,11 @@ unit_evaluate(const char *str, const char **endptr) double unit_evaluate_dbl(const char *str, bool case_sensitive, const char **endptr) { + enum { + SI = 1000, + BIN = 1024, + }; + if (endptr != nullptr) { *endptr = str; } @@ -117,39 +122,52 @@ unit_evaluate_dbl(const char *str, bool case_sensitive, const char **endptr) return NAN; } char unit_prefix = case_sensitive ? *endptr_tmp : toupper(*endptr_tmp); + char *prefix_start = endptr_tmp; endptr_tmp += 1; + double unit = SI; + double mult = 1; + if (endptr_tmp[0] == 'i' || (!case_sensitive && endptr_tmp[0] == 'I')) { + endptr_tmp += 1; + unit = BIN; + } switch(unit_prefix) { case 'n': case 'N': - ret /= 1000'000'000; + mult = 1 / unit / unit / unit; break; case 'u': case 'U': - ret /= 1000'000; + mult = 1 / unit / unit; break; case 'm': - ret /= 1000; + mult = 1 / unit; break; case 'k': case 'K': - ret *= 1000; + mult = unit; break; case 'M': - ret *= 1000'000LL; + mult = unit * unit; break; case 'g': case 'G': - ret *= 1000'000'000LL; + mult = unit * unit * unit; break; default: - endptr_tmp -= 1; + endptr_tmp = prefix_start; + } + if (unit == BIN) { // sanity chwecks + if ((case_sensitive && isupper(unit_prefix)) // eg. GI + || mult < 1) { // binary prefixes <1 undefined + mult = 1; + endptr_tmp = prefix_start; + } } - if (endptr != nullptr) { *endptr = endptr_tmp; } - return ret; + return ret * mult; } /** diff --git a/test/misc_test.cpp b/test/misc_test.cpp index 2bebcfdb3..d21959a90 100644 --- a/test/misc_test.cpp +++ b/test/misc_test.cpp @@ -1,3 +1,4 @@ +#include #include // for strcmp #include // for abs #include @@ -6,6 +7,7 @@ #include "color.h" #include "types.h" +#include "utils/misc.h" #include "utils/net.h" #include "utils/string.h" #include "unit_common.h" @@ -18,6 +20,7 @@ int misc_test_net_getsockaddr(); int misc_test_net_sockaddr_compare_v4_mapped(); int misc_test_replace_all(); int misc_test_video_desc_io_op_symmetry(); +int misc_test_unit_evaluate(); } using namespace std; @@ -134,6 +137,35 @@ int misc_test_replace_all() return 0; } +int misc_test_unit_evaluate() +{ + struct { + const char *str; + long long exp_val; + const char *exp_endstr; // value, not ptr + } test_cases[] = { + { "1", 1, "" }, + { "1M", 1000 * 1000, "" }, + { "1.2M", 1200 * 1000, "" }, + { "0.5Gi", 512 * 1024 * 1024, "" }, + // partially parsed + { "1x", 1, "x" }, + // errors + { "x", LLONG_MIN, "x" }, + { "Gi", LLONG_MIN, "Gi" }, + }; + + for (unsigned i = 0; i < sizeof test_cases / sizeof test_cases[0]; + ++i) { + const char *endptr = nullptr; + long long val = unit_evaluate(test_cases[i].str, &endptr); + ASSERT_EQUAL(test_cases[i].exp_val, val); + ASSERT_EQUAL_STR(test_cases[i].exp_endstr, endptr); + } + + return 0; +} + int misc_test_video_desc_io_op_symmetry() { const std::list test_desc = { diff --git a/test/run_tests.c b/test/run_tests.c index 9fe64379a..fc8e0c996 100644 --- a/test/run_tests.c +++ b/test/run_tests.c @@ -90,6 +90,7 @@ DECLARE_TEST(misc_test_color_coeff_range); DECLARE_TEST(misc_test_net_getsockaddr); DECLARE_TEST(misc_test_net_sockaddr_compare_v4_mapped); DECLARE_TEST(misc_test_replace_all); +DECLARE_TEST(misc_test_unit_evaluate); DECLARE_TEST(misc_test_video_desc_io_op_symmetry); struct { @@ -126,6 +127,7 @@ struct { DEFINE_TEST(misc_test_net_getsockaddr), DEFINE_TEST(misc_test_net_sockaddr_compare_v4_mapped), DEFINE_TEST(misc_test_replace_all), + DEFINE_TEST(misc_test_unit_evaluate), DEFINE_TEST(misc_test_video_desc_io_op_symmetry), }; diff --git a/test/unit_common.h b/test/unit_common.h index 4a25ac7a1..6a771cd2b 100644 --- a/test/unit_common.h +++ b/test/unit_common.h @@ -5,9 +5,11 @@ #include #include #include +#include #else #include #include +#include #endif #define ASSERT(expr) \ @@ -38,6 +40,13 @@ << ", actual : " << (actual) << "\n"; \ return -1; \ } +/// compares cstr or std::string value +#define ASSERT_EQUAL_STR(expected, actual) \ + if (std::string(expected) != std::string(actual)) { \ + std::cerr << "Assertion failed - expected \"" << (expected) \ + << "\", actual : \"" << (actual) << "\"\n"; \ + return -1; \ + } #else #define ASSERT_EQUAL(expected, actual) \ if ((expected) != (actual)) { \ @@ -47,6 +56,15 @@ (intmax_t) (expected), (intmax_t) (actulal)); \ return -1; \ } +/// compares cstr value +#define ASSERT_EQUAL_STR(expected, actual) \ + if (strcmp((expected), (actual)) != 0) { \ + fprintf(stderr, \ + "Assertion failed - expected \"%s\"" \ + ", actual \"%s\"\n", \ + (expected), (actulal)); \ + return -1; \ + } #endif #ifdef __cplusplus