diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | Makefile | 16 | ||||
-rw-r--r-- | intmath.c | 447 | ||||
-rw-r--r-- | intmath.h | 33 | ||||
-rw-r--r-- | minitest.h | 31 | ||||
-rw-r--r-- | stdu.c | 237 | ||||
-rw-r--r-- | tests.c | 18 |
7 files changed, 786 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..26e8cf8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +stdu +stdu.o +intmath.o +test diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..685f42a --- /dev/null +++ b/Makefile @@ -0,0 +1,16 @@ + +stdu : stdu.o intmath.o intmath.h + cc -o stdu stdu.o intmath.o + +stdu.o : stdu.c + cc -c stdu.c + +intmath.o : intmath.c minitest.h intmath.h + cc -c intmath.c + +test : tests.c minitest.h intmath.c + cc -o test tests.c + ./test + +clean : + rm stdu stdu.o intmath.o test diff --git a/intmath.c b/intmath.c new file mode 100644 index 0000000..cba66a9 --- /dev/null +++ b/intmath.c @@ -0,0 +1,447 @@ + +#include <stdlib.h> +#include <string.h> + +#include "minitest.h" +#include "intmath.h" + +int int_pown(unsigned int base, unsigned int exp) { + int res = 1; + while (exp > 0) { + res *= base; + exp--; + } + return res; +} + +int int_pow10(unsigned int exp) { + return int_pown(10, exp); +} + +int int_floor(int x, int precision) { + int sign = (x < 0) * -2 + 1; + x *= sign; + + int iterations = 0; + while (x > int_pow10(precision)) { + x /= 10; + iterations++; + } + + return (x + (sign == -1)) + * int_pow10(iterations) + * sign; +} + +int int_ceil(int x, int precision) { + return -int_floor(-x, precision); +} + +int int_min(int a, int b) { + return (a < b) * a + (b <= a) * b; +} + +int int_max(int a, int b) { + return (a > b) * a + (b >= a) * b; +} + +/* 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, it should not be greater than `1023*1024^10` and it + * should be floored/ceiled (ie. the mantissa should not be further away from + * zero than `1023`). Failing to meet these conditions results in the function + * returning `NULL`. + */ +const char* binary_prefix(exp_notated x) { + if ( + (x.base != 1024) || + (x.exponent > 10) || + (x.mantissa > 1023) || + (x.mantissa < -1023) + ) { + 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"; + default: + return "Qi"; + } +} + +/* Returns the a string describing the argument `int x` floored to the + * appropriate multiple of 1024 followed with a binary prefix (Ki, Mi, Gi...). + * The string is dynamically allocated and should be free'd. + * + * Returns NULL on malloc error. + */ +char* int_floored_with_binary_prefix(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_pow10(x_exp.exponent - 10); + + const char* prefix = binary_prefix(x_exp); + + size_t bufsize = + 5 + /* len("-1024") */ + int_max(0, x_exp.exponent - 10) + /* length of possible zeros */ + 4; /* len(" Gi\0") */ + char* res = malloc(bufsize); + if (res == NULL) return NULL; + sprintf(res, "%d", x_exp.mantissa); + if (strcmp(prefix, "") != 0) + strcat(res, " "); + strcat(res, prefix); + + return res; +} + +char* test_pown() { + mt_assert_eq(int_pown(2, 5), 32); + mt_assert_eq(int_pown(3, 3), 27); + mt_assert_eq(int_pown(9, 0), 1); + return 0; +} + +char* test_pow10() { + mt_assert_eq(int_pow10(3), 1000); + mt_assert_eq(int_pow10(0), 1); + return 0; +} + +char* test_floor() { + mt_assert_eq(int_floor(34128, 2), 34000); + mt_assert_eq(int_floor(-34128, 3), -34200); + mt_assert_eq(int_floor(-9999, 3), -10000); + mt_assert_eq(int_floor(9999, 3), 9990); + mt_assert_eq(int_floor(0, 1), 0); + return 0; +} + +char* test_ceil() { + mt_assert_eq(int_ceil(44212, 3), 44300); + mt_assert_eq(int_ceil(-44212, 3), -44200); + mt_assert_eq(int_ceil(-9999, 3), -9990); + mt_assert_eq(int_ceil(9999, 3), 10000); + mt_assert_eq(int_ceil(0, 1), 0); + return 0; +} + +char* test_min() { + mt_assert_eq(int_min(2, 6), 2); + mt_assert_eq(int_min(4, 1), 1); + mt_assert_eq(int_min(10, 10), 1); + return 0; +} + +char* test_max() { + mt_assert_eq(int_max(3, 7), 7); + mt_assert_eq(int_min(4, 1), 4); + mt_assert_eq(int_min(10, 10), 10); + return 0; +} + +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); + + char* res = int_floored_with_binary_prefix(1026); + snprintf(msg, bufsize, "1026 yielded `%s` instead of `1 Ki`", res); + mt_assert(strcmp( + res, + "1 Ki" + ) == 0, msg); + free(res); + + res = int_floored_with_binary_prefix(-1023); + snprintf(msg, bufsize, "-1023 yielded `%s` instead of `-1023`", res); + mt_assert(strcmp( + res, + "-1023" + ) == 0, msg); + free(res); + + res = int_floored_with_binary_prefix(-1026); + snprintf(msg, bufsize, "-1026 yielded `%s` instead of `-2 Ki`", res); + mt_assert(strcmp( + res, + "-2 Ki" + ) == 0, msg); + free(res); + + res = int_floored_with_binary_prefix(1049088); + snprintf(msg, bufsize, "1049088 yielded `%s` instead of `1 Mi`", res); + mt_assert(strcmp( + res, + "1 Mi" + ) == 0, msg); + free(res); + + res = int_floored_with_binary_prefix(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; +} + +void intmath_tests() { + mt_run_test(test_pown); + mt_run_test(test_pow10); + mt_run_test(test_floor); + mt_run_test(test_ceil); + mt_run_test(test_min); + mt_run_test(test_max); + 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); +} diff --git a/intmath.h b/intmath.h new file mode 100644 index 0000000..b71334c --- /dev/null +++ b/intmath.h @@ -0,0 +1,33 @@ + +#ifndef INTMATH_IS_IMPORTED +#define INTMATH_IS_IMPORTED + +struct exp_val { + int mantissa; + int exponent; + int base; +}; +typedef struct exp_val exp_notated; + +int int_pow10(unsigned int exp); +int int_floor(int x, int precision); +int int_ceil(int x, int precision); +int int_max(int a, int b); +int int_min(int a, int b); +exp_notated int_floored_exponent_notation_base( + int x, + unsigned int precision, + unsigned int base +); +exp_notated int_ceiled_exponent_notation_base( + int x, + unsigned int precision, + unsigned int base +); +exp_notated int_floored_exponent_notation(int x, unsigned int precision); +exp_notated int_ceiled_exponent_notation(int x, unsigned int precision); +int exp_notated_to_int(exp_notated x); +const char* binary_prefix(exp_notated x); +char* int_floored_with_binary_prefix(int x); + +#endif diff --git a/minitest.h b/minitest.h new file mode 100644 index 0000000..c566e48 --- /dev/null +++ b/minitest.h @@ -0,0 +1,31 @@ + +/// Minimal testing library tweaked with +/// reference from `minunit.h` from +/// Jera Design LLC: +/// https://jera.com/techinfo/jtns/jtn002 + +#include <stdio.h> +#define mt_assert(test, message) do { if (!(test)) return message; } while (0) +#define mt_assert_eq(left, right) do { \ + if (left != right) \ + return "`" #left "` didn't match `" #right "`"; \ + return 0; \ +} while (0) +#define mt_run_test(test) do { \ + char *message = test(); \ + if (message) { \ + fprintf(stderr, "[FAIL] " #test ": %s\n", message); \ + tests_failed++; \ + } else { \ + fprintf(stderr, "[PASS] " #test "\n"); \ + } \ + tests_run++; \ +} while (0) +#define mt_test_report() do { \ + if (tests_failed == 0) \ + fprintf(stderr, "[REPORT] All %d tests passed.\n", tests_run); \ + else \ + fprintf(stderr, "[REPORT] Failure. %d/%d tests failed.\n", tests_failed, tests_run); \ +} while (0) +extern int tests_run; +extern int tests_failed; @@ -0,0 +1,237 @@ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdbool.h> + +#include "intmath.h" + +int tests_run = 0; +int tests_failed = 0; + +struct Config { + int precision; + bool human_readable; +}; +typedef struct Config Config; +struct Result { + bool success; + void* result; +}; +typedef struct Result Result; + +Result parse_config(int argc, char* argv[]) { + Result res; + res.success = false; + Config tmp; + tmp.precision = 0; + tmp.human_readable = false; + + size_t i = 1; + char* argument; + char* precision = NULL; + while (i < argc) { + argument = argv[i]; + int comp1 = strcmp(argument, "--precision") == 0; + if ( + comp1 || strncmp(argument, "-p", 2) == 0 + ) { + if (precision != NULL) { + char* error_msg = malloc(44); + snprintf( + error_msg, + 44, + "precision can't be specified multiple times" + ); + res.result = error_msg; + return res; + } + i++; + if (strlen(argument) != 2 && !comp1) { + precision = argument + 2; + continue; + } + if (i == argc) { + char* error_msg = malloc(57); + snprintf( + error_msg, + 57, + "`%s` should be followed by an integer argument", + argument + ); + res.result = error_msg; + return res; + } + precision = argv[i]; + } else if ( + strcmp(argument, "--human-readable") == 0 + || strcmp(argument, "-h") == 0 + ) { + if (tmp.human_readable != false) { + char* error_msg = malloc(52); + snprintf( + error_msg, + 52, + "human readability can't be specified multiple times" + ); + res.result = error_msg; + return res; + } + tmp.human_readable = true; + } else { + size_t msg_size = 21 + strlen(argument); + char* error_msg = malloc(msg_size); + snprintf( + error_msg, + msg_size, + "unknown argument: `%s`", + argument + ); + res.result = error_msg; + return res; + } + + i++; + } + + if (precision != NULL) { + char* endptr; + errno = 0; + tmp.precision = (int) strtol(precision, &endptr, 10); + if (errno != 0) { + char* error_msg = malloc(64); + char* errno_str = strerror(errno); + snprintf( + error_msg, + 64, + "invalid precision: %s", + errno_str + ); + free(errno_str); + res.result = error_msg; + return res; + } + if (*endptr != '\0') { + char* error_msg = malloc(53); + snprintf( + error_msg, + 53, + "the precision may not contain non-numeric characters", + precision + ); + res.result = error_msg; + return res; + } + if (tmp.precision <= 0) { + char* error_msg = malloc(50); + snprintf( + error_msg, + 50, + "the precision can't be less than or equal to zero" + ); + res.result = error_msg; + return res; + } + } + + Config* conf = malloc(sizeof(int) + sizeof(bool)); + conf->precision = tmp.precision; + conf->human_readable = tmp.human_readable; + + res.success = true; + res.result = conf; + return res; +} + +int main (int argc, char* argv[]) { + Result res = parse_config(argc, argv); + if (res.success == false) { + char* err_msg = (char*) res.result; + fprintf( + stderr, + "Error parsing command line: %s\n", + err_msg + ); + return 1; + } + + Config* conf = (Config*) res.result; + int precision = conf->precision; + bool human_readable = conf->human_readable; + printf("precision: %d\n", precision); + if (human_readable) printf("human readable\n"); + + unsigned int base = 10; + if (human_readable && precision == 0) { + precision = 1; + base = 1024; + } + + int blocksize = 1; + size_t bufsize = 1; + unsigned int bytes_read = 0; + size_t new_read_bytes = 0; + if (precision != 0) { + blocksize = exp_notated_to_int(int_ceiled_exponent_notation_base( + bytes_read + 1, + precision, + base)) + - bytes_read; + bufsize = 1024; + } + size_t max_bufsize = 1048576; + char* buf = malloc(bufsize); + char* tmpbuf; + + while (1) { + /* output */ + printf("\r%u", bytes_read); + if (fflush(stdin) == EOF) { + printf("\n"); + perror("error during fflush"); + return 1; + } + + /* reading */ + new_read_bytes = fread( + buf, + 1, + int_min(blocksize, bufsize)/* + (blocksize == 0)*/, + stdin + ); + if (new_read_bytes == 0) { + int err = ferror(stdin); + if (err != 0) { + printf("\n"); + fprintf(stderr, "error reading stdin"); + return err; + } + break; + } + bytes_read += new_read_bytes; + + /* resizing buffer and blocksize to read as much as possible + * at once + */ + if (precision == 0) continue; + blocksize = exp_notated_to_int(int_ceiled_exponent_notation_base( + bytes_read + 1, + precision, + base)) + - bytes_read; + if (blocksize > bufsize) { + tmpbuf = malloc(bufsize * 2); + if (tmpbuf == NULL) { + free(tmpbuf); + } else { + free(buf); + buf = tmpbuf; + bufsize *= 2; + } + } + } + printf("\n"); + + return 0; +} @@ -0,0 +1,18 @@ + +#include "minitest.h" +#include "intmath.c" + +int tests_run = 0; +int tests_failed = 0; + +int main() { + printf("[INFO] Running tests...\n\n"); + + printf("[INFO] Running intmath-tests...\n"); + intmath_tests(); + printf("\n"); + + mt_test_report(); + + return tests_failed != 0; +} |