]> git.ipfire.org Git - thirdparty/bird.git/commitdiff
Formatting numbers with order prefixes
authorMaria Matejka <mq@ucw.cz>
Thu, 28 Nov 2024 22:08:50 +0000 (23:08 +0100)
committerOndrej Zajicek <santiago@crfreenet.org>
Fri, 29 Nov 2024 15:06:07 +0000 (16:06 +0100)
Unit tests by Ondrej Zajicek.

lib/printf.c
lib/printf_test.c
lib/string.h

index 68d3bb743c2b7dc0917310a0bfb734e17520cc48..318e683c616562c2abd7dd5a2a71f451ee1bf043 100644 (file)
@@ -8,7 +8,8 @@
  */
 
 #include "nest/bird.h"
-#include "string.h"
+#include "lib/macro.h"
+#include "lib/string.h"
 
 #include <errno.h>
 
@@ -631,3 +632,121 @@ char *lp_sprintf(linpool *p, const char *fmt, ...)
   va_end(args);
   return out;
 }
+
+static const u64 decadic_multiplier[] = {
+                 1,                 10,                 100,                 1000,
+             10000,             100000,             1000000,             10000000,
+#if 0
+         100000000,         1000000000,         10000000000,         100000000000,
+      1000000000000,    10000000000000,     100000000000000,     1000000000000000,
+  10000000000000000, 100000000000000000, 1000000000000000000,
+#endif
+};
+
+static const u64 decmul_limit[] = {
+  ~0ULL /                  1, ~0ULL /                 10,
+  ~0ULL /                100, ~0ULL /               1000,
+  ~0ULL /              10000, ~0ULL /             100000,
+  ~0ULL /            1000000, ~0ULL /           10000000,
+#if 0
+  ~0ULL /          100000000, ~0ULL /         1000000000,
+  ~0ULL /        10000000000, ~0ULL /       100000000000,
+  ~0ULL /      1000000000000, ~0ULL /     10000000000000,
+  ~0ULL /     100000000000000, ~0ULL /   1000000000000000,
+  ~0ULL /   10000000000000000, ~0ULL / 100000000000000000,
+  ~0ULL / 1000000000000000000,
+#endif
+};
+
+STATIC_ASSERT(sizeof decadic_multiplier == sizeof decmul_limit);
+
+char *fmt_order(u64 value, uint decimals, u64 kb_threshold)
+{
+  bool too_big = (value + 512 < 512ULL);
+
+  u64 mv = value;
+  uint magnitude = 0;
+  while (mv > kb_threshold)
+  {
+    magnitude++;
+    mv = (mv + (too_big ? 0 : 512)) / 1024;
+  }
+
+  uint shift = magnitude * 10;
+
+  /* The trivial case */
+  if (magnitude == 0)
+    return tmp_sprintf("%lu  ", value);
+
+  /* Now we can find the suffix and the main divisor */
+  ASSERT_DIE(magnitude < 7);
+  char suffix = " kMGTPE"[magnitude];
+
+  /* The value before the dot is available just by dividing */
+  u64 before_dot = value >> shift;
+
+  /* Remainder is more tricky. First we need to know it. */
+  u64 remainder = value - (before_dot << shift);
+
+  /* We would like to compute (remainder * decmul) / divisor
+   * in integers but it's tricky because of u64 limits. */
+  ASSERT_DIE(decimals < ARRAY_SIZE(decadic_multiplier));
+  u64 decmul = decadic_multiplier[decimals];
+  u64 product;
+
+  if (remainder < decmul_limit[decimals])
+  {
+    /* The easier version: Everything fits into u64 */
+    product = remainder * decmul;
+    product >>= shift - 1;
+    product++;
+    product >>= 1;
+  }
+  else
+  {
+    /* Harder version: We have to multiply by parts.
+     * Fortunately, decmul always fits into 32 bits. */
+    /* After this, product = lower + (upper << 32). */
+    u64 lower = (remainder & ((1ULL << 32) - 1)) * decmul;
+    u64 upper = (remainder >> 32) * decmul;
+
+    if (shift < 33)
+    {
+      /* Divide lower */
+      lower >>= shift - 1;
+
+      /* Add the full upper part, not shifted enough to lose any bits */
+      lower += upper << (33 - shift);
+    }
+    else
+    {
+      /* First move the shifted-out bits from upper to lower */
+      lower += (upper & ((1ULL << (shift - 32)) - 1)) << 32;
+
+      /* Then we can divide */
+      lower >>= shift - 1;
+
+      /* And add the shifted upper part */
+      lower += upper >> (shift - 33);
+    }
+
+    /* Now we finish the division by rounding */
+    product = (lower + 1) >> 1;
+  }
+
+  if (product == decmul)
+  {
+    product = 0;
+    before_dot++;
+  }
+
+  ASSERT_DIE(product < decmul);
+
+  /* And now we finally have all the numbers to print! */
+  if (decimals)
+    return tmp_sprintf("%lu.%0*lu %c",
+       before_dot, decimals, product, suffix
+       );
+  else
+    return tmp_sprintf("%lu %c", before_dot, suffix);
+}
index 996f34ae8ab8d3681be183979e0a71a19e3d0bd4..b3d4d5605a761d9ecec94f1691b4713b603f76d9 100644 (file)
@@ -117,6 +117,68 @@ t_bstrcmp(void)
   return 1;
 }
 
+static int
+t_fmt_order(void)
+{
+  struct fmt_order_tv {
+    u64 value;
+    int decimals;
+    u64 threshold;
+    const char *expect;
+  } test_vectors [] = {
+    { 9999, 1, 10000, "9999  " },
+    { 10001, 1, 10000, "9.8 k" },
+    { 10001, 2, 10000, "9.77 k" },
+    { 10001, 3, 10000, "9.767 k" },
+    { 1048575, 0, 10000, "1024 k" },
+    { 1048575, 1, 10000, "1024.0 k" },
+    { 1048575, 2, 10000, "1024.00 k" },
+    { 1048575, 3, 10000, "1023.999 k" },
+    { 1048575, 4, 10000, "1023.9990 k" },
+    { 1048575, 5, 10000, "1023.99902 k" },
+    { 1048575, 6, 10000, "1023.999023 k" },
+    { 1048575, 0, 1000, "1 M" },
+    { 1048575, 1, 1000, "1.0 M" },
+    { 1048575, 2, 1000, "1.00 M" },
+    { 1048575, 3, 1000, "1.000 M" },
+    { 1048575, 4, 1000, "1.0000 M" },
+    { 1048575, 5, 1000, "1.00000 M" },
+    { 1048575, 6, 1000, "0.999999 M" },
+    { 1048577, 6, 10000, "1024.000977 k" },
+    { 1048577, 6, 1000, "1.000001 M" },
+    { 1048577, 6, 100, "1.000001 M" },
+    { 1048577, 6, 10, "1.000001 M" },
+    { 1048577, 6, 1, "1.000001 M" },
+    { 10000000000000, 6, 10000, "9313.225746 G" },
+    { 10000000000000, 6, 1000, "9.094947 T" },
+    { 123456789123456789, 0, 1000, "110 P" },
+    { 123456789123456789, 4, 1000, "109.6517 P" },
+    { 123456789123456789, 7, 1000, "109.6516559 P" },
+    { 1234567890123456789, 0, 1000, "1 E" },
+    { 1234567890123456789, 1, 1000, "1.1 E" },
+    { 1234567890123456789, 2, 1000, "1.07 E" },
+    { 1234567890123456789, 3, 1000, "1.071 E" },
+    { 1234567890123456789, 4, 1000, "1.0708 E" },
+    { 1234567890123456789, 5, 1000, "1.07082 E" },
+    { 1234567890123456789, 6, 1000, "1.070817 E" },
+    { 1234567890123456789, 7, 1000, "1.0708170 E" },
+    { 9444732965739290427U, 3, 1000, "8.192 E" },
+    { 9444732965739290427U, 6, 1000, "8.192000 E" },
+    { 18446744073709551615U, 2, 1000, "16.00 E" },
+  };
+
+  for (int i = 0; i < (int)ARRAY_SIZE(test_vectors); i++)
+  {
+    const char *result = fmt_order(test_vectors[i].value, test_vectors[i].decimals, test_vectors[i].threshold);
+    const char *expect = test_vectors[i].expect;
+
+    bt_assert_msg(strncmp(result, expect, strlen(expect)) == 0,
+                 "case %d, result \"%s\", expect \"%s\"", i, result, expect);
+  }
+
+  return 1;
+}
+
 int
 main(int argc, char *argv[])
 {
@@ -126,6 +188,7 @@ main(int argc, char *argv[])
   bt_test_suite(t_router_id, "print router id");
   bt_test_suite(t_time, "print time");
   bt_test_suite(t_bstrcmp, "bstrcmp");
+  bt_test_suite(t_fmt_order, "fmt_order");
 
   return bt_exit_value();
 }
index 0c21f51332cd192f4962a27ee38366655a2a810b..8831666c7d73bc158dca08b161e1a72da30cf47f 100644 (file)
@@ -24,6 +24,8 @@ char *mb_sprintf(pool *p, const char *fmt, ...);
 char *mb_vsprintf(pool *p, const char *fmt, va_list args);
 char *lp_sprintf(linpool *p, const char *fmt, ...);
 char *lp_vsprintf(linpool *p, const char *fmt, va_list args);
+#define tmp_sprintf(...)    lp_sprintf(tmp_linpool, __VA_ARGS__)
+#define tmp_vsprintf(...)   lp_vsprintf(tmp_linpool, __VA_ARGS__)
 
 int buffer_vprint(buffer *buf, const char *fmt, va_list args);
 int buffer_print(buffer *buf, const char *fmt, ...);
@@ -33,6 +35,8 @@ u64 bstrtoul10(const char *str, char **end);
 u64 bstrtoul16(const char *str, char **end);
 byte bstrtobyte16(const char *str);
 
+char *fmt_order(u64 value, uint decimals, u64 kb_threshold);
+
 int bstrhextobin(const char *s, byte *b);
 int bstrbintohex(const byte *b, size_t len, char *buf, size_t size, char delim) ACCESS_READ(1, 2) ACCESS_WRITE(3, 4);