Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 513 parsing of rationals #516

Merged
merged 9 commits into from
Nov 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
68 changes: 64 additions & 4 deletions runtime.c
Original file line number Diff line number Diff line change
Expand Up @@ -2557,7 +2557,8 @@ typedef enum {
STR2INT_SUCCESS,
STR2INT_OVERFLOW,
STR2INT_UNDERFLOW,
STR2INT_INCONVERTIBLE
STR2INT_INCONVERTIBLE,
STR2INT_RATIONAL
} str2int_errno;

/*
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -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)) {
Expand Down
5 changes: 5 additions & 0 deletions tests/base.scm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down