/* if ever in need of formatting functions like in this file, but signed, they * can be found wasted in the version history, commit * f975594e55bdc05ee436bc7bdcd6e09aec5357b1. */ #include #include #include #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 ull_floored_exponent_notation_base ( unsigned long long x, unsigned int precision, unsigned int base ) { int i = 0; while (x >= int_pown(base, precision)) { x /= base; i++; } exp_notated res = { .mantissa = x, .exponent = i, .base = base }; return res; } exp_notated ull_floored_exponent_notation( unsigned long long x, unsigned int precision ) { return ull_floored_exponent_notation_base(x, precision, 10); } exp_notated ull_ceiled_exponent_notation_base( unsigned long long x, unsigned int precision, unsigned int base ) { unsigned long long original = x; int i = 0; while (x >= int_pown(base, precision)) { x /= base; i++; } if (x * ull_pown(base, i) != original) { x += 1; } exp_notated res = { .mantissa = x, .exponent = i, .base = base }; return res; } exp_notated ull_ceiled_exponent_notation( unsigned long long x, unsigned int precision ) { return ull_ceiled_exponent_notation_base(x, precision, 10); } unsigned long long exp_notated_to_ull(exp_notated x) { return x.mantissa * (unsigned long long) 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 `unsigned long long 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 ull_floored_with_binary_prefix( char** res, size_t* res_bufsize, unsigned long long x ) { exp_notated x_exp = ull_floored_exponent_notation_base(x, 1, 1024); 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, "%llu %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 ull_floored_with_prefix( char** res, size_t* res_bufsize, long long unsigned x, unsigned int precision ) { exp_notated x_exp = ull_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 = ull_floored_exponent_notation(9489345, 3); snprintf(msg, bufsize, "ull_floored_exponent_notation(9489345, 3): expected 948*10^4, got %llu*%d^%d", res.mantissa, res.base, res.exponent ); mt_assert( res.mantissa == 948 && res.base == 10 && res.exponent == 4 , msg); res = ull_floored_exponent_notation_base(3145733, 1, 1024); snprintf(msg, bufsize, "ull_floored_exponent_notation_base(3145733, 1, 1024): expected 3*1024^2, got %llu*%d^%d", res.mantissa, res.base, res.exponent ); mt_assert( res.mantissa == 3 && res.base == 1024 && res.exponent == 2 , msg); free(msg); return 0; } char* test_ceiled_exponent_notation() { const size_t bufsize = 128; char* msg = malloc(bufsize); exp_notated res = ull_ceiled_exponent_notation(9489345, 3); snprintf(msg, bufsize, "ull_ceiled_exponent_notation(9489345, 3): expected 949*10^4, got %llu*%d^%d", res.mantissa, res.base, res.exponent ); mt_assert( res.mantissa == 949 && res.base == 10 && res.exponent == 4 , msg); res = ull_ceiled_exponent_notation_base(3145733, 1, 1024); snprintf(msg, bufsize, "ull_ceiled_exponent_notation_base(3145733, 1, 1024): expected 4*1024^2, got %llu*%d^%d", res.mantissa, res.base, res.exponent ); mt_assert( res.mantissa == 4 && res.base == 1024 && res.exponent == 2 , msg); free(msg); return 0; } char* test_exponent_notated_to_ull() { exp_notated x = { .mantissa = 3, .exponent = 2, .base = 10 }; mt_assert_eq(300, exp_notated_to_ull(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); ull_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); ull_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); ull_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); ull_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); ull_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); ull_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); ull_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_ull); mt_run_test(test_floored_with_binary_prefix); mt_run_test(test_floored_with_prefix); }