diff options
Diffstat (limited to 'tests')
-rw-r--r-- | tests/decimal_test.c | 414 | ||||
-rw-r--r-- | tests/soft_float_test.c | 21 | ||||
-rw-r--r-- | tests/test_data.h | 26 |
3 files changed, 439 insertions, 22 deletions
diff --git a/tests/decimal_test.c b/tests/decimal_test.c index 674ce5cb..ae35a17c 100644 --- a/tests/decimal_test.c +++ b/tests/decimal_test.c @@ -17,14 +17,31 @@ #undef NDEBUG #include "../src/decimal.h" +#include "../src/string_utils.h" +#include "test_data.h" #include "serd/serd.h" +#ifdef _WIN32 +#include <process.h> +#else +#include <unistd.h> +#endif + #include <assert.h> +#include <float.h> #include <math.h> #include <stdbool.h> +#include <stdint.h> #include <stdio.h> +#include <stdlib.h> #include <string.h> +#include <time.h> + +#define DBL_INFINITY ((double)INFINITY) + +static size_t n_tests = 16384ul; +static uint32_t seed = 0; static void test_count_digits(void) @@ -55,6 +72,16 @@ test_count_digits(void) } static void +test_strtod(void) +{ + assert(serd_strtod("1E999", NULL) == (double)INFINITY); + assert(serd_strtod("-1E999", NULL) == (double)-INFINITY); + assert(serd_strtod("1E-999", NULL) == 0.0); + assert(serd_strtod("-1E-999", NULL) == -0.0); + assert(isnan(serd_strtod("ABCDEF", NULL))); +} + +static void check_precision(const double d, const unsigned precision, const unsigned frac_digits, @@ -94,10 +121,395 @@ test_precision(void) check_precision(12345.678900, 9, 1, "12345.6"); } +/// Check that `str` is a canonical xsd:float or xsd:double string +static void +test_canonical(const char* str, const size_t len) +{ + if (!strcmp(str, "NaN") || !strcmp(str, "-INF") || !strcmp(str, "INF")) { + return; + } + + assert(len > 4); // Shortest possible is something like 1.2E3 + assert(str[0] == '-' || is_digit(str[0])); + + const int first_digit = str[0] == '-' ? 1 : 0; + assert(is_digit(str[first_digit])); + assert(str[first_digit + 1] == '.'); + assert(is_digit(str[first_digit + 2])); + + const char* const e = strchr(str, 'E'); + assert(e); + assert(*e == 'E'); + assert(*(e + 1) == '-' || is_digit(*(e + 1))); +} + +/// Check that `f` round-trips, and serialises to `expected` if given +static void +test_float_value(const float f, const char* expected) +{ + SerdNode* const node = serd_new_float(f); + const char* str = serd_node_string(node); + size_t end = 0; + const float result = (float)serd_strtod(str, &end); + const bool match = result == f || (isnan(f) && isnan(result)); + + if (!match) { + fprintf(stderr, "error: value is %.9g\n", (double)result); + fprintf(stderr, "note: expected %.9g\n", (double)f); + fprintf(stderr, "note: string %s\n", str); + } + + assert(match); + assert(end == serd_node_length(node)); + assert((isnan(f) && isnan(result)) || result == f); + assert(!expected || !strcmp(str, expected)); + + test_canonical(str, serd_node_length(node)); + serd_node_free(node); +} + +static void +test_float(const bool exhaustive) +{ + test_float_value(NAN, "NaN"); + test_float_value(-INFINITY, "-INF"); + test_float_value(INFINITY, "INF"); + + test_float_value(-0.0f, "-0.0E0"); + test_float_value(+0.0f, "0.0E0"); + test_float_value(-1.0f, "-1.0E0"); + test_float_value(+1.0f, "1.0E0"); + + test_float_value(5.0f, "5.0E0"); + test_float_value(50.0f, "5.0E1"); + test_float_value(5000000000.0f, "5.0E9"); + test_float_value(-0.5f, "-5.0E-1"); + test_float_value(0.5f, "5.0E-1"); + test_float_value(0.0625f, "6.25E-2"); + test_float_value(0.0078125f, "7.8125E-3"); + + // Every digit of precision + test_float_value(134217728.0f, "1.34217728E8"); + + // Normal limits + test_float_value(FLT_MIN, NULL); + test_float_value(FLT_EPSILON, NULL); + test_float_value(FLT_MAX, NULL); + + // Subnormals + test_float_value(nextafterf(0.0f, 1.0f), NULL); + test_float_value(nextafterf(0.0f, -1.0f), NULL); + + // Past limits + assert((float)serd_strtod("1e39", NULL) == INFINITY); + assert((float)serd_strtod("1e-46", NULL) == 0.0f); + + // Powers of two (where the lower boundary is closer) + for (int i = -127; i <= 127; ++i) { + test_float_value(powf(2, (float)i), NULL); + } + + if (exhaustive) { + fprintf(stderr, "Testing xsd:float exhaustively\n"); + + for (uint64_t i = 0; i <= UINT32_MAX; ++i) { + const float f = float_from_rep((uint32_t)i); + test_float_value(f, NULL); + + if (i % 1000000 == 1) { + fprintf(stderr, + "%f%%\n", + ((double)i / (double)UINT32_MAX * 100.0)); + } + } + } else { + fprintf(stderr, "Testing xsd:float randomly\n"); + const size_t n_per_report = n_tests / 10ul; + uint64_t last_report = 0; + uint32_t rep = seed; + for (uint64_t i = 0; i < n_tests; ++i) { + rep = lcg32(rep); + + const float f = float_from_rep(rep); + + test_float_value(nextafterf(f, -INFINITY), NULL); + test_float_value(f, NULL); + test_float_value(nextafterf(f, INFINITY), NULL); + + if (i / n_per_report != last_report / n_per_report) { + fprintf(stderr, + "%u%%\n", + (unsigned)((double)i / (double)n_tests * 100.0)); + last_report = i; + } + } + } +} + +/// Check that `d` round-trips, and serialises to `expected` if given +static void +test_double_value(const double d, const char* expected) +{ + SerdNode* const node = serd_new_double(d); + const char* str = serd_node_string(node); + size_t end = 0; + const double result = serd_strtod(str, &end); + const bool match = ((isnan(d) && isnan(result)) || + result == d || + ulp_distance(result, d) == 0); + + if (!match) { + fprintf(stderr, "error: value is %.17g\n", result); + fprintf(stderr, "note: expected %.17g\n", d); + fprintf(stderr, "note: string %s\n", str); + } + + assert(match); + assert(end == serd_node_length(node)); + assert((isnan(d) && isnan(result)) || result == d); + assert(!expected || !strcmp(str, expected)); + + test_canonical(str, serd_node_length(node)); + serd_node_free(node); +} + +static void +test_double(void) +{ + test_double_value((double)NAN, "NaN"); + test_double_value(-DBL_INFINITY, "-INF"); + test_double_value(DBL_INFINITY, "INF"); + + test_double_value(-0.0, "-0.0E0"); + test_double_value(+0.0, "0.0E0"); + test_double_value(-1.0, "-1.0E0"); + test_double_value(+1.0, "1.0E0"); + + test_double_value(5.0, "5.0E0"); + test_double_value(50.0, "5.0E1"); + test_double_value(500000000000000000000.0, "5.0E20"); + test_double_value(-0.5, "-5.0E-1"); + test_double_value(0.5, "5.0E-1"); + test_double_value(0.05, "5.0E-2"); + test_double_value(0.005, "5.0E-3"); + test_double_value(0.00000000000000000005, "5.0E-20"); + + // Leading whitespace special cases + assert(isnan(serd_strtod(" NaN", NULL))); + assert(serd_strtod(" -INF", NULL) == -DBL_INFINITY); + assert(serd_strtod(" INF", NULL) == DBL_INFINITY); + assert(serd_strtod(" +INF", NULL) == DBL_INFINITY); + + // Every digit of precision + test_double_value(18014398509481984.0, "1.8014398509481984E16"); + + // Normal limits + test_double_value(DBL_MIN, NULL); + test_double_value(nextafter(DBL_MIN, DBL_INFINITY), NULL); + test_double_value(DBL_EPSILON, NULL); + test_double_value(DBL_MAX, NULL); + test_double_value(nextafter(DBL_MAX, -DBL_INFINITY), NULL); + + // Subnormals + test_double_value(nextafter(0.0, 1.0), NULL); + test_double_value(nextafter(nextafter(0.0, 1.0), 1.0), NULL); + test_double_value(nextafter(0.0, -1.0), NULL); + test_double_value(nextafter(nextafter(0.0, -1.0), -1.0), NULL); + + // Past limits + assert(serd_strtod("1e309", NULL) == DBL_INFINITY); + assert(ulp_distance(serd_strtod("12345678901234567123", NULL), + 12345678901234567000.0) == 0); + assert(serd_strtod("1e-325", NULL) == 0.0); + + // Various tricky cases + test_double_value(1e23, "1.0E23"); + test_double_value(6.02951420360127e-309, "6.02951420360127E-309"); + test_double_value(9.17857104364115e+288, "9.17857104364115E288"); + test_double_value(2.68248422823759e+22, "2.68248422823759E22"); + + // Powers of two (where the lower boundary is closer) + for (int i = -1023; i <= 1023; ++i) { + test_double_value(pow(2, i), NULL); + } + + fprintf(stderr, "Testing xsd:double randomly\n"); + + const size_t n_per_report = n_tests / 10ul; + uint64_t last_report = 0; + uint64_t rep = seed; + for (uint64_t i = 0; i < n_tests; ++i) { + rep = lcg64(rep); + + const double d = double_from_rep(rep); + + test_double_value(nextafter(d, -DBL_INFINITY), NULL); + test_double_value(d, NULL); + test_double_value(nextafter(d, DBL_INFINITY), NULL); + + if (i / n_per_report != last_report / n_per_report) { + fprintf(stderr, + "%u%%\n", + (unsigned)((double)i / (double)n_tests * 100.0)); + last_report = i; + } + } +} + +/// Check that `d` round-trips, and serialises to `expected` if given +static void +test_decimal_value(const double d, const char* expected) +{ + SerdNode* const node = serd_new_decimal(d, 17, 0, NULL); + const char* str = serd_node_string(node); + size_t end = 0; + const double result = serd_strtod(str, &end); + const bool match = result == d || (isnan(d) && isnan(result)); + + if (!match) { + fprintf(stderr, "error: value is %.17g\n", result); + fprintf(stderr, "note: expected %.17g\n", d); + fprintf(stderr, "note: string %s\n", str); + } + + assert(match); + assert(end == serd_node_length(node)); + + if (expected && strcmp(str, expected)) { + fprintf(stderr, "error: string is \"%s\"\n", str); + fprintf(stderr, "note: expected \"%s\"\n", expected); + } + assert(!expected || !strcmp(str, expected)); + + serd_node_free(node); +} + +static void +test_decimal(void) +{ + test_decimal_value(-0.0, "-0.0"); + test_decimal_value(+0.0, "0.0"); + test_decimal_value(-1.0, "-1.0"); + test_decimal_value(+1.0, "1.0"); + + test_decimal_value(5.0, "5.0"); + test_decimal_value(50.0, "50.0"); + test_decimal_value(500000000000000000000.0, "500000000000000000000.0"); + test_decimal_value(-0.5, "-0.5"); + test_decimal_value(0.5, "0.5"); + test_decimal_value(0.05, "0.05"); + test_decimal_value(0.005, "0.005"); + test_decimal_value(0.00000000000000000005, "0.00000000000000000005"); + + // Every digit of precision + test_decimal_value(18014398509481984.0, "18014398509481984.0"); + + // Normal limits + test_decimal_value(DBL_MIN, NULL); + test_decimal_value(nextafter(DBL_MIN, DBL_INFINITY), NULL); + test_decimal_value(DBL_EPSILON, NULL); + test_decimal_value(DBL_MAX, NULL); + test_decimal_value(nextafter(DBL_MAX, -DBL_INFINITY), NULL); + + // Subnormals + test_decimal_value(nextafter(0.0, 1.0), NULL); + test_decimal_value(nextafter(nextafter(0.0, 1.0), 1.0), NULL); + test_decimal_value(nextafter(0.0, -1.0), NULL); + test_decimal_value(nextafter(nextafter(0.0, -1.0), -1.0), NULL); + + // Past limits + assert(serd_strtod("1e309", NULL) == DBL_INFINITY); + assert(ulp_distance(serd_strtod("12345678901234567123", NULL), + 12345678901234567000.0) == 0); + assert(serd_strtod("1e-325", NULL) == 0.0); + + // Various tricky cases + test_decimal_value(1e23, NULL); + test_decimal_value(6.02951420360127e-309, NULL); + test_decimal_value(9.17857104364115e+288, NULL); + test_decimal_value(2.68248422823759e+22, NULL); + + // Powers of two (where the lower boundary is closer) + for (int i = -1023; i <= 1023; ++i) { + test_decimal_value(pow(2, i), NULL); + } + + fprintf(stderr, "Testing xsd:decimal randomly\n"); + + const size_t n_per_report = n_tests / 10ul; + uint64_t last_report = 0; + uint64_t rep = seed; + for (uint64_t i = 0; i < n_tests; ++i) { + rep = lcg64(rep); + + const double d = double_from_rep(rep); + if (!isfinite(d)) { + continue; + } + + test_decimal_value(nextafter(d, (double)-INFINITY), NULL); + test_decimal_value(d, NULL); + test_decimal_value(nextafter(d, (double)INFINITY), NULL); + + if (i / n_per_report != last_report / n_per_report) { + fprintf(stderr, + "%u%%\n", + (unsigned)((double)i / (double)n_tests * 100.0)); + last_report = i; + } + } +} + +static int +print_usage(const char* name) +{ + fprintf(stderr, "Usage: %s [OPTION]...\n", name); + fprintf(stderr, "Test floating point conversion.\n"); + fprintf(stderr, " -n NUM_TESTS Number of random tests to run.\n"); + fprintf(stderr, " -s SEED Use random seed.\n"); + fprintf(stderr, " -x Exhaustively test floats.\n"); + return 1; +} + int -main(void) +main(int argc, char** argv) { + // Parse command line arguments + int a = 1; + bool exhaustive = false; + for (; a < argc && argv[a][0] == '-'; ++a) { + if (argv[a][1] == '\0') { + break; + } else if (argv[a][1] == 'x') { + exhaustive = true; + } else if (argv[a][1] == 's') { + if (++a == argc) { + return print_usage(argv[0]); + } + + seed = (uint32_t)strtol(argv[a], NULL, 10); + } else if (argv[a][1] == 'n') { + if (++a == argc) { + return print_usage(argv[0]); + } + + n_tests = (uint32_t)strtol(argv[a], NULL, 10); + } + } + + if (!seed) { + seed = (uint32_t)time(NULL) + (uint32_t)getpid(); + } + + fprintf(stderr, "Using random seed %u\n", seed); + test_count_digits(); + test_strtod(); test_precision(); + test_float(exhaustive); + test_double(); + test_decimal(); + + fprintf(stderr, "All tests passed\n"); return 0; } diff --git a/tests/soft_float_test.c b/tests/soft_float_test.c index 85e9e4b6..d81f9308 100644 --- a/tests/soft_float_test.c +++ b/tests/soft_float_test.c @@ -18,7 +18,6 @@ #include "test_data.h" -#include "../src/ieee_float.h" #include "../src/soft_float.h" #include <assert.h> @@ -26,26 +25,6 @@ #include <stdbool.h> #include <stdint.h> -static uint64_t -ulp_distance(const double a, const double b) -{ - assert(a >= 0.0); - assert(b >= 0.0); - - if (a == b) { - return 0; - } else if (isnan(a) || isnan(b)) { - return UINT64_MAX; - } else if (isinf(a) || isinf(b)) { - return UINT64_MAX; - } - - const uint64_t ia = double_to_rep(a); - const uint64_t ib = double_to_rep(b); - - return ia > ib ? ia - ib : ib - ia; -} - static bool check_multiply(const double lhs, const double rhs) { diff --git a/tests/test_data.h b/tests/test_data.h index fa32f529..6ff7b644 100644 --- a/tests/test_data.h +++ b/tests/test_data.h @@ -14,6 +14,11 @@ OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include "../src/ieee_float.h" +#include "../src/soft_float.h" + +#include <assert.h> +#include <math.h> #include <stdint.h> #include <string.h> @@ -54,3 +59,24 @@ double_from_rep(const uint64_t rep) memcpy(&d, &rep, sizeof(d)); return d; } + +/// Return the distance between two doubles in ULPs +static uint64_t +ulp_distance(const double a, const double b) +{ + assert(a >= 0.0); + assert(b >= 0.0); + + if (a == b) { + return 0; + } else if (isnan(a) || isnan(b)) { + return UINT64_MAX; + } else if (isinf(a) || isinf(b)) { + return UINT64_MAX; + } + + const uint64_t ia = double_to_rep(a); + const uint64_t ib = double_to_rep(b); + + return ia > ib ? ia - ib : ib - ia; +} |