aboutsummaryrefslogtreecommitdiff
path: root/formatting.c
diff options
context:
space:
mode:
Diffstat (limited to 'formatting.c')
-rw-r--r--formatting.c593
1 files changed, 593 insertions, 0 deletions
diff --git a/formatting.c b/formatting.c
new file mode 100644
index 0000000..c806f4d
--- /dev/null
+++ b/formatting.c
@@ -0,0 +1,593 @@
+
+#include <math.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "intmath.h"
+#include "formatting.h"
+#include "minitest.h"
+
+size_t int_charcount(int x) {
+ size_t n = x < 0;
+ x *= -(x < 0) * 2 + 1;
+ while (x >= 10) {
+ x /= 10;
+ n += 1;
+ }
+ return n + 1;
+}
+
+/* Returns `int x` floored to `unsigned int precision` as an `exp_notated`
+ * value with given `unsigned int base`.
+ *
+ * `unsigned int base` affects the precision.
+ */
+exp_notated int_floored_exponent_notation_base
+(
+ int x,
+ unsigned int precision,
+ unsigned int base
+) {
+ int original = x;
+ int sign = (x < 0) * -2 + 1;
+ x *= sign;
+
+ int i = 0;
+ while (x >= int_pown(base, precision)) {
+ x /= base;
+ i++;
+ }
+ x += (sign == -1) && (original != -x * int_pown(base, i));
+ if (x >= int_pown(base, precision)) {
+ x /= base;
+ i++;
+ }
+ x *= sign;
+
+ exp_notated res = {
+ .mantissa = x,
+ .exponent = i,
+ .base = base
+ };
+ return res;
+}
+
+exp_notated int_floored_exponent_notation(int x, unsigned int precision) {
+ return int_floored_exponent_notation_base(x, precision, 10);
+}
+
+exp_notated int_ceiled_exponent_notation_base(
+ int x,
+ unsigned int precision,
+ unsigned int base
+) {
+ exp_notated res = int_floored_exponent_notation_base(-x, precision, base);
+ res.mantissa = -res.mantissa;
+ return res;
+}
+
+exp_notated int_ceiled_exponent_notation(int x, unsigned int precision) {
+ return int_ceiled_exponent_notation_base(x, precision, 10);
+}
+
+int exp_notated_to_int(exp_notated x) {
+ return x.mantissa * int_pown(x.base, x.exponent);
+}
+
+/* Returns the appropriate binary prefix for `exp_notated x`. `x` must have
+ * `1024` as its base and its exponent should not be greater than `10` Failing
+ * to meet these conditions results in the function returning `NULL`.
+ */
+const char* binary_prefix(exp_notated x) {
+ if (x.base != 1024) return NULL;
+
+ switch (x.exponent) {
+ case 0:
+ return "";
+ case 1:
+ return "Ki";
+ case 2:
+ return "Mi";
+ case 3:
+ return "Gi";
+ case 4:
+ return "Ti";
+ case 5:
+ return "Pi";
+ case 6:
+ return "Ei";
+ case 7:
+ return "Zi";
+ case 8:
+ return "Yi";
+ case 9:
+ return "Ri";
+ case 10:
+ return "Qi";
+ }
+
+ return NULL;
+}
+
+/* Modifies the string pointed to by `char** res` to a string describing the
+ * argument `int x` floored to the appropriate multiple of 1024 followed with a
+ * binary prefix (Ki, Mi, Gi...).
+ *
+ * `size_t* res_bufsize` should point to a value describing the size of the
+ * string buffer pointed to by `char** res`. Returns a negative value upon
+ * failure in memory allocation or `snprintf`, otherwise returns the length of
+ * `char* res`.
+ */
+int int_floored_with_binary_prefix(char** res, size_t* res_bufsize, int x) {
+ exp_notated x_exp = int_floored_exponent_notation_base(x, 1, 1024);
+ if (x_exp.exponent > 10) { /* quebi-=1024^10 greatest defined prefix */
+ x_exp.mantissa *= int_pown(1024, x_exp.exponent - 10);
+ x_exp.exponent = 10;
+ }
+
+ const char* prefix = binary_prefix(x_exp);
+
+ size_t bufsize =
+ 5 + /* len("-1024") */
+ /* length of possible zeroes */
+ int_max(0, (x_exp.exponent - 10) * 3) +
+ 4; /* len(" Gi\0") */
+ if (bufsize > *res_bufsize) {
+ free(*res);
+ *res = malloc(bufsize);
+ *res_bufsize = bufsize;
+ }
+ if (*res == NULL) return -1;
+ return snprintf(*res, *res_bufsize, "%d %s", x_exp.mantissa, prefix);
+}
+
+/* Returns the unit prefix resembling the exponent `int x`. If there is no
+ * prefix resembling `x`, NULL is returned, though the special case `x = 0`
+ * returns an empty string.
+ */
+const char* unit_prefix(int exponent) {
+ switch (exponent) {
+ case -30:
+ return "q";
+ case -27:
+ return "r";
+ case -24:
+ return "y";
+ case -21:
+ return "z";
+ case -18:
+ return "a";
+ case -15:
+ return "f";
+ case -12:
+ return "p";
+ case -9:
+ return "n";
+ case -6:
+ return "μ";
+ case -3:
+ return "m";
+ case -2:
+ return "c";
+ case -1:
+ return "d";
+ case 0:
+ return "";
+ case 1:
+ return "da";
+ case 2:
+ return "h";
+ case 3:
+ return "k";
+ case 6:
+ return "M";
+ case 9:
+ return "G";
+ case 12:
+ return "T";
+ case 15:
+ return "P";
+ case 18:
+ return "E";
+ case 21:
+ return "Z";
+ case 24:
+ return "Y";
+ case 27:
+ return "R";
+ case 30:
+ return "Q";
+ }
+
+ return NULL;
+}
+
+/* Modifies the string pointed to by `char** res` to a string describing the
+ * argument `int x` floored to the appropriate precision followed with a unit
+ * prefix (k, M, G...).
+ *
+ * `size_t* res_bufsize` should point to a value describing the size of the
+ * string buffer pointed to by `char** res`. Returns a negative value upon
+ * failure and the length of `char* res` on success. Failure can be caused by
+ * problems in memory allocation or failure in `snprintf`.
+ */
+int int_floored_with_prefix(
+ char** res,
+ size_t* res_bufsize,
+ int x,
+ unsigned int precision
+) {
+ exp_notated x_exp
+ = int_floored_exponent_notation_base(x, precision, 10);
+ double mantissa = (double) x_exp.mantissa * int_pow10(x_exp.exponent % 3);
+ unsigned int exponent = x_exp.exponent - x_exp.exponent % 3;
+
+ while (fabs(mantissa) >= 1000) {
+ mantissa /= 1000;
+ exponent += 3;
+ }
+
+ const char* prefix = unit_prefix(exponent);
+ size_t prefix_len = strlen(prefix);
+
+ size_t bufsize =
+ int_max(precision, 3) /* mantissa */
+ + 4 /* minus, space, decimal dot and NULL byte */
+ + prefix_len;
+ if (bufsize > *res_bufsize) {
+ free(*res);
+ *res_bufsize = bufsize;
+ *res = malloc(*res_bufsize);
+ }
+ if (*res == NULL) return -1;
+
+ return snprintf(
+ *res,
+ *res_bufsize,
+ "%.*f %s",
+ int_max(precision - int_charcount((int) fabs(mantissa)), 0),
+ mantissa,
+ prefix
+ );
+}
+
+char* test_floored_exponent_notation() {
+ const size_t bufsize = 128;
+ char* msg = malloc(bufsize);
+
+ exp_notated res = int_floored_exponent_notation(9489345, 3);
+ snprintf(msg, bufsize,
+ "int_floored_exponent_notation(9489345, 3): expected 948*10^4, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == 948 &&
+ res.base == 10 &&
+ res.exponent == 4
+ , msg);
+
+ res = int_floored_exponent_notation(-88, 1);
+ snprintf(msg, bufsize,
+ "int_floored_exponent_notation(-88, 1): expected -9*10^1, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == -9 &&
+ res.base == 10 &&
+ res.exponent == 1
+ , msg);
+
+ res = int_floored_exponent_notation(-99, 1);
+ snprintf(msg, bufsize,
+ "int_floored_exponent_notation(-99, 1): expected -1*10^2, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == -1 &&
+ res.base == 10 &&
+ res.exponent == 2
+ , msg);
+
+ res = int_floored_exponent_notation_base(3145733, 1, 1024);
+ snprintf(msg, bufsize,
+ "int_floored_exponent_notation_base(3145733, 1, 1024): expected 3*1024^2, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == 3 &&
+ res.base == 1024 &&
+ res.exponent == 2
+ , msg);
+
+ res = int_floored_exponent_notation_base(-1022, 1, 1024);
+ snprintf(msg, bufsize,
+ "int_floored_exponent_notation(-1022, 1, 1024): expected -1022*1024^0, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == -1022 &&
+ res.base == 1024 &&
+ res.exponent == 0
+ , msg);
+
+ res = int_floored_exponent_notation_base(-1023, 1, 1024);
+ snprintf(msg, bufsize,
+ "int_floored_exponent_notation(-1023, 1, 1024): expected -1023*1024^0, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == -1023 &&
+ res.base == 1024 &&
+ res.exponent == 0
+ , msg);
+
+ res = int_floored_exponent_notation_base(-1024, 1, 1024);
+ snprintf(msg, bufsize,
+ "int_floored_exponent_notation(-1024, 1, 1024): expected -1*1024^1, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == -1 &&
+ res.base == 1024 &&
+ res.exponent == 1
+ , msg);
+
+ free(msg);
+ return 0;
+}
+
+char* test_ceiled_exponent_notation() {
+ const size_t bufsize = 128;
+ char* msg = malloc(bufsize);
+
+ exp_notated res = int_ceiled_exponent_notation(9489345, 3);
+ snprintf(msg, bufsize,
+ "int_ceiled_exponent_notation(9489345, 3): expected 949*10^4, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == 949 &&
+ res.base == 10 &&
+ res.exponent == 4
+ , msg);
+
+ res = int_ceiled_exponent_notation(-99, 1);
+ snprintf(msg, bufsize,
+ "int_ceiled_exponent_notation(-99, 1): expected -9*10^1, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == -9 &&
+ res.base == 10 &&
+ res.exponent == 1
+ , msg);
+
+ res = int_ceiled_exponent_notation(-100, 1);
+ snprintf(msg, bufsize,
+ "int_ceiled_exponent_notation(-100, 1): expected -1*10^2, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == -1 &&
+ res.base == 10 &&
+ res.exponent == 2
+ , msg);
+
+ res = int_ceiled_exponent_notation_base(3145733, 1, 1024);
+ snprintf(msg, bufsize,
+ "int_ceiled_exponent_notation_base(3145733, 1, 1024): expected 4*1024^2, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == 4 &&
+ res.base == 1024 &&
+ res.exponent == 2
+ , msg);
+
+ res = int_ceiled_exponent_notation_base(-1023, 1, 1024);
+ snprintf(msg, bufsize,
+ "int_ceiled_exponent_notation(-1023, 1, 1024): expected -1023*1024^0, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == -1023 &&
+ res.base == 1024 &&
+ res.exponent == 0
+ , msg);
+
+ res = int_ceiled_exponent_notation_base(-1024, 1, 1024);
+ snprintf(msg, bufsize,
+ "int_ceiled_exponent_notation(-1024, 1, 1024): expected -1*1024^1, got %d*%d^%d",
+ res.mantissa, res.base, res.exponent
+ );
+ mt_assert(
+ res.mantissa == -1 &&
+ res.base == 1024 &&
+ res.exponent == 1
+ , msg);
+
+ free(msg);
+ return 0;
+}
+
+char* test_exponent_notated_to_int() {
+ exp_notated x = {
+ .mantissa = 3,
+ .exponent = 2,
+ .base = 10
+ };
+ mt_assert_eq(300, exp_notated_to_int(x));
+ return 0;
+}
+
+char* test_floored_with_binary_prefix() {
+ const size_t bufsize = 64;
+ char* msg = malloc(bufsize);
+ size_t res_bufsize = 2;
+ char* res = malloc(res_bufsize);
+
+ int_floored_with_binary_prefix(
+ &res,
+ &res_bufsize,
+ 1026
+ );
+ snprintf(msg, bufsize, "1026 yielded `%s` instead of `1 Ki`", res);
+ mt_assert(strcmp(
+ res,
+ "1 Ki"
+ ) == 0, msg);
+
+ int_floored_with_binary_prefix(
+ &res,
+ &res_bufsize,
+ -1023
+ );
+ snprintf(msg, bufsize, "-1023 yielded `%s` instead of `-1023`", res);
+ mt_assert(strcmp(
+ res,
+ "-1023 "
+ ) == 0, msg);
+
+ int_floored_with_binary_prefix(
+ &res,
+ &res_bufsize,
+ -1026
+ );
+ snprintf(msg, bufsize, "-1026 yielded `%s` instead of `-2 Ki`", res);
+ mt_assert(strcmp(
+ res,
+ "-2 Ki"
+ ) == 0, msg);
+
+ int_floored_with_binary_prefix(
+ &res,
+ &res_bufsize,
+ 1049088
+ );
+ snprintf(msg, bufsize, "1049088 yielded `%s` instead of `1 Mi`", res);
+ mt_assert(strcmp(
+ res,
+ "1 Mi"
+ ) == 0, msg);
+
+ int_floored_with_binary_prefix(
+ &res,
+ &res_bufsize,
+ 5243392
+ );
+ snprintf(msg, bufsize, "5243392 yielded `%s` instead of `5 Mi`", res);
+ mt_assert(strcmp(
+ res,
+ "5 Mi"
+ ) == 0, msg);
+
+ free(res);
+ free(msg);
+ return 0;
+}
+
+
+char* test_floored_with_prefix() {
+ const size_t bufsize = 64;
+ char* msg = malloc(bufsize);
+ size_t res_bufsize = 2;
+ char* res = malloc(res_bufsize);
+
+ int_floored_with_prefix(
+ &res,
+ &res_bufsize,
+ 1001,
+ 3
+ );
+ snprintf(msg, bufsize, "1001 yielded `%s` instead of `1.00 k`", res);
+ mt_assert(strcmp(
+ res,
+ "1.00 k"
+ ) == 0, msg);
+
+ int_floored_with_prefix(
+ &res,
+ &res_bufsize,
+ 300,
+ 1
+ );
+ snprintf(msg, bufsize, "300 (-p1) yielded `%s` instead of `300 `", res);
+ mt_assert(strcmp(
+ res,
+ "300 "
+ ) == 0, msg);
+
+ int_floored_with_prefix(
+ &res,
+ &res_bufsize,
+ -999,
+ 3
+ );
+ snprintf(msg, bufsize, "-999 yielded `%s` instead of `-999 `", res);
+ mt_assert(strcmp(
+ res,
+ "-999 "
+ ) == 0, msg);
+
+ int_floored_with_prefix(
+ &res,
+ &res_bufsize,
+ -1026,
+ 3
+ );
+ snprintf(msg, bufsize, "-1021 yielded `%s` instead of `-1.03 k`", res);
+ mt_assert(strcmp(
+ res,
+ "-1.03 k"
+ ) == 0, msg);
+
+ int_floored_with_prefix(
+ &res,
+ &res_bufsize,
+ 1000000,
+ 2
+ );
+ snprintf(msg, bufsize, "1 000 000 yielded `%s` instead of `1.0 M`", res);
+ mt_assert(strcmp(
+ res,
+ "1.0 M"
+ ) == 0, msg);
+
+ int_floored_with_prefix(
+ &res,
+ &res_bufsize,
+ 5243392,
+ 4
+ );
+ snprintf(msg, bufsize, "5243692 yielded `%s` instead of `5.243 M`", res);
+ mt_assert(strcmp(
+ res,
+ "5.243 M"
+ ) == 0, msg);
+
+ free(res);
+ free(msg);
+ return 0;
+}
+
+char* test_int_charcount() {
+ mt_assert_eq(int_charcount(5), 1);
+ mt_assert_eq(int_charcount(24), 2);
+ mt_assert_eq(int_charcount(10), 2);
+ mt_assert_eq(int_charcount(0), 1);
+ mt_assert_eq(int_charcount(1), 1);
+ mt_assert_eq(int_charcount(-1), 2);
+ mt_assert_eq(int_charcount(99), 2);
+ mt_assert_eq(int_charcount(100), 3);
+ return 0;
+}
+
+
+void formatting_tests() {
+ mt_run_test(test_int_charcount);
+ mt_run_test(test_floored_exponent_notation);
+ mt_run_test(test_ceiled_exponent_notation);
+ mt_run_test(test_exponent_notated_to_int);
+ mt_run_test(test_floored_with_binary_prefix);
+ mt_run_test(test_floored_with_prefix);
+}