From 87f0114dd8d2cf355c4e631ab089ddd75abdcc0c Mon Sep 17 00:00:00 2001 From: Richard Qin Date: Wed, 24 Sep 2025 13:50:55 +0800 Subject: Initial commit: Add starter code for Datalab --- btest.c | 636 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 636 insertions(+) create mode 100644 btest.c (limited to 'btest.c') diff --git a/btest.c b/btest.c new file mode 100644 index 0000000..fe8130e --- /dev/null +++ b/btest.c @@ -0,0 +1,636 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include "btest.h" + +/* Not declared in some stdlib.h files, so define here */ +float strtof(const char* nptr, char** endptr); + +/************************* + * Configuration Constants + *************************/ + +/* Handle infinite loops by setting upper limit on execution time, in + seconds */ +#define TIMEOUT_LIMIT 10 + +/* For functions with a single argument, generate TEST_RANGE values + above and below the min and max test values, and above and below + zero. Functions with two or three args will use square and cube + roots of this value, respectively, to avoid combinatorial + explosion */ +#define TEST_RANGE 500000 + +/* This defines the maximum size of any test value array. The + gen_vals() routine creates k test values for each value of + TEST_RANGE, thus MAX_TEST_VALS must be at least k*TEST_RANGE */ +#define MAX_TEST_VALS 13 * TEST_RANGE + +/********************************** + * Globals defined in other modules + **********************************/ +/* This characterizes the set of puzzles to test. + Defined in decl.c and generated from templates in ./puzzles dir */ +extern test_rec test_set[]; + +/************************************************ + * Write-once globals defined by command line args + ************************************************/ + +/* Emit results in a format for autograding, without showing + and counter-examples */ +static int grade = 0; + +/* Time out after this number of seconds */ +static int timeout_limit = TIMEOUT_LIMIT; /* -T */ + +/* If non-NULL, test only one function (-f) */ +static char* test_fname = NULL; + +/* Special case when only use fixed argument(s) (-1, -2, or -3) */ +static int has_arg[3] = {0, 0, 0}; +static unsigned argval[3] = {0, 0, 0}; + +/* Use fixed weight for rating, and if so, what should it be? (-r) */ +static int global_rating = 0; + +/****************** + * Helper functions + ******************/ + +/* + * Signal - installs a signal handler + */ +typedef void handler_t(int); + +handler_t* Signal(int signum, handler_t* handler) { + struct sigaction action, old_action; + + action.sa_handler = handler; + sigemptyset(&action.sa_mask); /* block sigs of type being handled */ + action.sa_flags = SA_RESTART; /* restart syscalls if possible */ + + if (sigaction(signum, &action, &old_action) < 0) + perror("Signal error"); + return (old_action.sa_handler); +} + +/* + * timeout_handler - SIGALARM hander + */ +sigjmp_buf envbuf; +void timeout_handler(int sig) { + siglongjmp(envbuf, 1); +} + +/* + * random_val - Return random integer value between min and max + */ +static int random_val(int min, int max) { + double weight = rand() / (double)RAND_MAX; + int result = min * (1 - weight) + max * weight; + return result; +} + +void pack_store(int test_vals_low[], int test_vals_high[], int* test_count, int64_t value) { + test_vals_low[*test_count] = value & 0xFFFFFFFF; + test_vals_high[*test_count] = (value >> 32) & 0xFFFFFFFF; + (*test_count)++; +} + +/* + * gen_float64_vals - 生成 64 位 float 的测试数据,分别填充到 test_vals_low 和 test_vals_high 中 + */ +static int gen_float64_vals(int test_vals_low[], int test_vals_high[], int test_range, int arg_low, int arg_high) { + int64_t i; + int test_count = 0; + if (has_arg[arg_low] && has_arg[arg_high]) { + test_vals_low[0] = argval[arg_low]; + test_vals_high[0] = argval[arg_high]; + return 1; + } else if (has_arg[arg_low] || has_arg[arg_high]) { + printf("Configuration error: You can't set only one of the two float arguments, as they are a pair.\n"); + exit(1); + } + + int64_t smallest_norm = 0x0010000000000000; + int64_t one = 0x3ff0000000000000; + int64_t largest_norm = 0x7fefffffffffffff; + + int64_t inf = 0x7ff0000000000000; + int64_t nan = 0x7ff8000000000000; + int64_t sign = 0x8000000000000000; + + for (i = 0; i < test_range; i++) { + /* Denorms around zero */ + pack_store(test_vals_low, test_vals_high, &test_count, i); + pack_store(test_vals_low, test_vals_high, &test_count, sign | i); + + /* Region around norm to denorm transition */ + pack_store(test_vals_low, test_vals_high, &test_count, smallest_norm + i); + pack_store(test_vals_low, test_vals_high, &test_count, smallest_norm - i); + pack_store(test_vals_low, test_vals_high, &test_count, sign | (smallest_norm + i)); + pack_store(test_vals_low, test_vals_high, &test_count, sign | (smallest_norm - i)); + + /* Region around one */ + pack_store(test_vals_low, test_vals_high, &test_count, one + i); + pack_store(test_vals_low, test_vals_high, &test_count, one - i); + pack_store(test_vals_low, test_vals_high, &test_count, sign | (one + i)); + pack_store(test_vals_low, test_vals_high, &test_count, sign | (one - i)); + + /* Region below largest norm */ + pack_store(test_vals_low, test_vals_high, &test_count, largest_norm - i); + pack_store(test_vals_low, test_vals_high, &test_count, sign | (largest_norm - i)); + } + + /* special vals */ + pack_store(test_vals_low, test_vals_high, &test_count, inf); /* inf */ + pack_store(test_vals_low, test_vals_high, &test_count, sign | inf); /* -inf */ + pack_store(test_vals_low, test_vals_high, &test_count, nan); /* nan */ + pack_store(test_vals_low, test_vals_high, &test_count, sign | nan); /* -nan */ + return test_count; +} + +/* + * gen_vals - Generate the integer values we'll use to test a function + */ +static int gen_vals(int test_vals[], int min, int max, int test_range, int arg) { + int i; + int test_count = 0; + + /* Special case: If the user has specified a specific function + argument using the -1, -2, or -3 flags, then simply use this + argument and return */ + if (has_arg[arg]) { + test_vals[0] = argval[arg]; + return 1; + } + + /* + * Special case: Generate test vals for floating point functions + * where the input argument is an unsigned bit-level + * representation of a float. For this case we want to test the + * regions around zero, the smallest normalized and largest + * denormalized numbers, one, and the largest normalized number, + * as well as inf and nan. + */ + if ((min == 1 && max == 1)) { + unsigned smallest_norm = 0x00800000; + unsigned one = 0x3f800000; + unsigned largest_norm = 0x7f000000; + + unsigned inf = 0x7f800000; + unsigned nan = 0x7fc00000; + unsigned sign = 0x80000000; + + /* Test range should be at most 1/2 the range of one exponent + value */ + if (test_range > (1 << 23)) { + test_range = 1 << 23; + } + + /* Functions where the input argument is an unsigned bit-level + representation of a float. The number of tests generated + inside this loop body is the value k referenced in the + comment for the global variable MAX_TEST_VALS. */ + + for (i = 0; i < test_range; i++) { + /* Denorms around zero */ + test_vals[test_count++] = i; + test_vals[test_count++] = sign | i; + + /* Region around norm to denorm transition */ + test_vals[test_count++] = smallest_norm + i; + test_vals[test_count++] = smallest_norm - i; + test_vals[test_count++] = sign | (smallest_norm + i); + test_vals[test_count++] = sign | (smallest_norm - i); + + /* Region around one */ + test_vals[test_count++] = one + i; + test_vals[test_count++] = one - i; + test_vals[test_count++] = sign | (one + i); + test_vals[test_count++] = sign | (one - i); + + /* Region below largest norm */ + test_vals[test_count++] = largest_norm - i; + test_vals[test_count++] = sign | (largest_norm - i); + } + + /* special vals */ + test_vals[test_count++] = inf; /* inf */ + test_vals[test_count++] = sign | inf; /* -inf */ + test_vals[test_count++] = nan; /* nan */ + test_vals[test_count++] = sign | nan; /* -nan */ + + return test_count; + } + + /* + * Normal case: Generate test vals for integer functions + */ + + /* If the range is small enough, then do exhaustively */ + if (max - MAX_TEST_VALS <= min) { + for (i = min; i <= max; i++) + test_vals[test_count++] = i; + return test_count; + } + + /* Otherwise, need to sample. Do so near the boundaries, around + zero, and for some random cases. */ + for (i = 0; i < test_range; i++) { + /* Test around the boundaries */ + test_vals[test_count++] = min + i; + test_vals[test_count++] = max - i; + + /* If zero falls between min and max, then also test around zero */ + if (i >= min && i <= max) + test_vals[test_count++] = i; + if (-i >= min && -i <= max) + test_vals[test_count++] = -i; + + /* Random case between min and max */ + test_vals[test_count++] = random_val(min, max); + } + return test_count; +} + +/* + * test_0_arg - Test a function with zero arguments + */ +static int test_0_arg(funct_t f, funct_t ft, char* name) { + int r = f(); + int rt = ft(); + int error = (r != rt); + + if (error && !grade) + printf("ERROR: Test %s() failed...\n...Gives %d[0x%x]. Should be %d[0x%x]\n", name, r, r, rt, rt); + + return error; +} + +/* + * test_1_arg - Test a function with one argument + */ +static int test_1_arg(funct_t f, funct_t ft, int arg1, char* name) { + funct1_t f1 = (funct1_t)f; + funct1_t f1t = (funct1_t)ft; + int r, rt, error; + + r = f1(arg1); + rt = f1t(arg1); + error = (r != rt); + if (error && !grade) + printf("ERROR: Test %s(%d[0x%x]) failed...\n...Gives %d[0x%x]. Should be %d[0x%x]\n", name, arg1, arg1, r, r, rt, rt); + + return error; +} + +/* + * test_2_arg - Test a function with two arguments + */ +static int test_2_arg(funct_t f, funct_t ft, int arg1, int arg2, char* name) { + funct2_t f2 = (funct2_t)f; + funct2_t f2t = (funct2_t)ft; + int r = f2(arg1, arg2); + int rt = f2t(arg1, arg2); + int error = (r != rt); + + if (error && !grade) + printf("ERROR: Test %s(%d[0x%x],%d[0x%x]) failed...\n...Gives %d[0x%x]. Should be %d[0x%x]\n", name, arg1, arg1, arg2, arg2, r, r, rt, rt); + + return error; +} + +/* + * test_3_arg - Test a function with three arguments + */ +static int test_3_arg(funct_t f, funct_t ft, int arg1, int arg2, int arg3, char* name) { + funct3_t f3 = (funct3_t)f; + funct3_t f3t = (funct3_t)ft; + int r = f3(arg1, arg2, arg3); + int rt = f3t(arg1, arg2, arg3); + int error = (r != rt); + + if (error && !grade) + printf("ERROR: Test %s(%d[0x%x],%d[0x%x],%d[0x%x]) failed...\n...Gives %d[0x%x]. Should be %d[0x%x]\n", name, arg1, arg1, arg2, arg2, arg3, arg3, r, r, rt, rt); + + return error; +} + +/* + * test_function - Test a function. Return number of errors + */ +static int test_function(test_ptr t, int arg_low, int arg_high) { + int test_counts[3]; /* number of test values for each arg */ + int args = t->args; /* number of function arguments */ + int arg_test_range[3] = {0}; /* test range for each argument */ + int i, a1, a2, a3; + int errors __attribute__((unused)) = 0; + + /* These are the test values for each arg. Declared with the + static attribute so that the array will be allocated in bss + rather than the stack */ + static int arg_test_vals[3][MAX_TEST_VALS]; + + /* Sanity check on the number of args */ + if (args < 0 || args > 3) { + printf("Configuration error: invalid number of args (%d) for function %s\n", args, t->name); + exit(1); + } + + /* Assign range of argument test vals so as to conserve the total + number of tests, independent of the number of arguments */ + if (args == 1) { + arg_test_range[0] = TEST_RANGE; + } else if (args == 2) { + arg_test_range[0] = pow((double)TEST_RANGE, 0.5); /* sqrt */ + arg_test_range[1] = arg_test_range[0]; + } else { + arg_test_range[0] = pow((double)TEST_RANGE, 0.333); /* cbrt */ + arg_test_range[1] = arg_test_range[0]; + arg_test_range[2] = arg_test_range[0]; + } + + /* Sanity check on the ranges */ + if (arg_test_range[0] < 1) + arg_test_range[0] = 1; + if (arg_test_range[1] < 1) + arg_test_range[1] = 1; + if (arg_test_range[2] < 1) + arg_test_range[2] = 1; + + /* Create a test set for each argument */ + for (i = 0; i < args; i++) { + if (i == arg_low || i == arg_high) + continue; + test_counts[i] = gen_vals(arg_test_vals[i], + t->arg_ranges[i][0], /* min */ + t->arg_ranges[i][1], /* max */ + arg_test_range[i], + i); + } + + if (arg_low != -1 && arg_high != -1) { + if (args != 2) { + printf("Configuration error: invalid number of args (%d) for function %s. Support only two arguments for now.\n", args, t->name); + exit(1); + } + int test_count = gen_float64_vals(arg_test_vals[arg_low], arg_test_vals[arg_high], TEST_RANGE, arg_low, arg_high); + test_counts[arg_low] = test_count; + test_counts[arg_high] = test_count; + } + + /* Handle timeouts in the test code */ + if (timeout_limit > 0) { + int rc; + rc = sigsetjmp(envbuf, 1); + if (rc) { + /* control will reach here if there is a timeout */ + errors = 1; + printf("ERROR: Test %s failed.\n Timed out after %d secs (probably infinite loop)\n", t->name, timeout_limit); + return errors; + } + alarm(timeout_limit); + } + + /* Test function has no arguments */ + if (args == 0) { + errors += test_0_arg(t->solution_funct, t->test_funct, t->name); + return errors; + } + + /* + * Test function has at least one argument + */ + + /* Iterate over the values for first argument */ + + if (arg_low != -1 && arg_high != -1) { + for (a1 = 0; a1 < test_counts[0]; a1++) { + errors += test_2_arg(t->solution_funct, + t->test_funct, + arg_test_vals[0][a1], + arg_test_vals[1][a1], + t->name); + + /* Stop testing if there is an error */ + if (errors) + return errors; + } + return errors; + } + + for (a1 = 0; a1 < test_counts[0]; a1++) { + if (args == 1) { + errors += test_1_arg(t->solution_funct, + t->test_funct, + arg_test_vals[0][a1], + t->name); + + /* Stop testing if there is an error */ + if (errors) + return errors; + } else { + /* if necessary, iterate over values for second argument */ + for (a2 = 0; a2 < test_counts[1]; a2++) { + if (args == 2) { + errors += test_2_arg(t->solution_funct, + t->test_funct, + arg_test_vals[0][a1], + arg_test_vals[1][a2], + t->name); + + /* Stop testing if there is an error */ + if (errors) + return errors; + } else { + /* if necessary, iterate over vals for third arg */ + for (a3 = 0; a3 < test_counts[2]; a3++) { + errors += test_3_arg(t->solution_funct, + t->test_funct, + arg_test_vals[0][a1], + arg_test_vals[1][a2], + arg_test_vals[2][a3], + t->name); + + /* Stop testing if there is an error */ + if (errors) + return errors; + } /* a3 */ + } + } /* a2 */ + } + } /* a1 */ + + return errors; +} + +/* + * run_tests - Run series of tests. Return number of errors + */ +static int run_tests() { + int i; + int errors = 0; + double points = 0.0; + double max_points = 0.0; + + // printf("Score\tRating\tErrors\tFunction\n"); + + for (i = 0; test_set[i].solution_funct; i++) { + int terrors; + double tscore; + double tpoints; + if (!test_fname || strcmp(test_set[i].name, test_fname) == 0) { + int rating = global_rating ? global_rating : test_set[i].rating; + if (strcmp(test_set[i].name, "float64_f2i") == 0) { // special case for float64_f2i + terrors = test_function(&test_set[i], 0, 1); + } else { + terrors = test_function(&test_set[i], -1, -1); + } + errors += terrors; + tscore = terrors == 0 ? 1.0 : 0.0; + tpoints = rating * tscore; + points += tpoints; + max_points += rating; + + if (grade || terrors < 1) + printf(" %.0f\t%d\t%d\t%s\n", + tpoints, rating, terrors, test_set[i].name); + } + } + + // printf("Total points: %.0f/%.0f\n", points, max_points); + return errors; +} + +/* + * get_num_val - Extract hex/decimal/or float value from string + */ +static int get_num_val(char* sval, unsigned* valp) { + char* endp; + + /* See if it's an integer or floating point */ + int ishex = 0; + int isfloat = 0; + int i; + for (i = 0; sval[i]; i++) { + switch (sval[i]) { + case 'x': + case 'X': + ishex = 1; + break; + case 'e': + case 'E': + if (!ishex) + isfloat = 1; + break; + case '.': + isfloat = 1; + break; + default: + break; + } + } + if (isfloat) { + float fval = strtof(sval, &endp); + if (!*endp) { + *valp = *(unsigned*)&fval; + return 1; + } + return 0; + } else { + long long int llval = strtoll(sval, &endp, 0); + long long int upperbits = llval >> 31; + /* will give -1 for negative, 0 or 1 for positive */ + if (!*valp && (upperbits == 0 || upperbits == -1 || upperbits == 1)) { + *valp = (unsigned)llval; + return 1; + } + return 0; + } +} + +/* + * usage - Display usage info + */ +static void usage(char* cmd) { + printf("Usage: %s [-hg] [-r ] [-f [-1|-2|-3 ]*] [-T