diff --git a/CHANGELOG.md b/CHANGELOG.md index 64fc54c0..7f270f22 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## 0.36.0 - TBD +Features + +- Enhanced the reader to parse rationals and store them as inexact numbers. + Bug Fixes - Fix `exact` to properly handle complex numbers, including raising an error when passed `nan` or `inf` double values. diff --git a/runtime.c b/runtime.c index 1c20a0f9..9532a263 100644 --- a/runtime.c +++ b/runtime.c @@ -2557,7 +2557,8 @@ typedef enum { STR2INT_SUCCESS, STR2INT_OVERFLOW, STR2INT_UNDERFLOW, - STR2INT_INCONVERTIBLE + STR2INT_INCONVERTIBLE, + STR2INT_RATIONAL } str2int_errno; /* @@ -2588,12 +2589,18 @@ static str2int_errno str2int(int *out, char *s, int base) errno = 0; long l = strtol(s, &end, base); /* Both checks are needed because INT_MAX == LONG_MAX is possible. */ - if (l > CYC_FIXNUM_MAX /*INT_MAX*/ || (errno == ERANGE && l == LONG_MAX)) + if (l > CYC_FIXNUM_MAX /*INT_MAX*/ || (errno == ERANGE && l == LONG_MAX)) { return STR2INT_OVERFLOW; - if (l < CYC_FIXNUM_MIN /*INT_MIN*/ || (errno == ERANGE && l == LONG_MIN)) + } + if (l < CYC_FIXNUM_MIN /*INT_MIN*/ || (errno == ERANGE && l == LONG_MIN)) { return STR2INT_UNDERFLOW; - if (*end != '\0') + } + if (*end == '/') { + return STR2INT_RATIONAL; + } + if (*end != '\0') { return STR2INT_INCONVERTIBLE; + } *out = l; return STR2INT_SUCCESS; } @@ -2610,6 +2617,51 @@ int str_is_bignum(str2int_errno errnum, char *c) return 1; } +/** + * @brief Read a rational number from given string. + * @param data Thread data object for the caller. + * @param char* String to read + * @return double Return number as double, since cyclone does + * not support a rational number type at this time + */ +double string2rational(void *data, char *s) +{ + // Duplicate string so we can safely create separate strings + // for numerator and denominator + char *nom = _strdup(s); + if (nom == NULL) { + return 0.0; + } + + char *denom = strchr(nom, '/'); + if (denom == NULL) { + // Should never happen since we check for '/' elsewhere + return 0.0; + } + denom[0] = '\0'; + denom++; + + // Parse both rational components as bignums since + // that code handles any integer + alloc_bignum(data, bn_nom); + if (MP_OKAY != mp_read_radix(&(bignum_value(bn_nom)), nom, 10)) { + Cyc_rt_raise2(data, "Error converting string to bignum", nom); + } + + alloc_bignum(data, bn_denom); + if (MP_OKAY != mp_read_radix(&(bignum_value(bn_denom)), denom, 10)) { + Cyc_rt_raise2(data, "Error converting string to bignum", denom); + } + + // Prevent memory leak + free(nom); + + // Compute final result as double + double x = mp_get_double(&bignum_value(bn_nom)); + double y = mp_get_double(&bignum_value(bn_denom)); + return x / y; +} + object Cyc_string2number_(void *data, object cont, object str) { int result, rv; @@ -2622,6 +2674,14 @@ object Cyc_string2number_(void *data, object cont, object str) rv = str2int(&result, s, 10); if (rv == STR2INT_SUCCESS) { _return_closcall1(data, cont, obj_int2obj(result)); + } else if (rv == STR2INT_RATIONAL || + // Could still be a rational if numerator is + // bignum, so in that case do one more scan + ((rv == STR2INT_OVERFLOW || rv == STR2INT_UNDERFLOW) && + strchr(s, '/') != NULL)) { + double d = string2rational(data, s); + make_double(result, d); + _return_closcall1(data, cont, &result); } else if (str_is_bignum(rv, s)) { alloc_bignum(data, bn); if (MP_OKAY != mp_read_radix(&(bignum_value(bn)), s, 10)) { diff --git a/tests/base.scm b/tests/base.scm index 63e6dd91..506a46b8 100644 --- a/tests/base.scm +++ b/tests/base.scm @@ -48,6 +48,11 @@ "rationals" (test 3.0 (numerator (/ 6 4))) (test 2.0 (denominator (/ 6 4))) + (test 3.0 (expt 81 1/4)) + (test #t + (< 1.0e+40 + (/ 33333333333333333333333333333333333333333 3.0) + 1.2e+40)) ) (test-group