1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-06-30 06:50:31 +02:00

fix string->number for bases > 16

* libguile/numbers.c (scm_iuint2str): Add an assertion on the domain of
  the radix. Use the number_chars table to write the string, instead of
  doing strange math. Same effect, though.
  (mem2uinteger, char_decimal_value): Change logic to allow all ascii
  alphabetic chars as decimals, not just a-f. Thanks to Nils Gey for the
  report.

* test-suite/tests/numbers.test ("number->string"): Add some tests.
This commit is contained in:
Andy Wingo 2010-11-15 23:43:30 +01:00
parent 8b755a759e
commit a6f3af1618
3 changed files with 46 additions and 19 deletions

1
THANKS
View file

@ -44,6 +44,7 @@ For fixes or providing information which led to a fix:
Barry Fishman
Charles Gagnon
Peter Gavin
Nils Gey
Eric Gillespie, Jr
Didier Godefroy
Panicz Maciej Godek

View file

@ -2402,6 +2402,9 @@ scm_iuint2str (scm_t_uintmax num, int rad, char *p)
size_t i;
scm_t_uintmax n = num;
if (rad < 2 || rad > 36)
scm_out_of_range ("scm_iuint2str", scm_from_int (rad));
for (n /= rad; n > 0; n /= rad)
j++;
@ -2412,7 +2415,7 @@ scm_iuint2str (scm_t_uintmax num, int rad, char *p)
int d = n % rad;
n /= rad;
p[i] = d + ((d < 10) ? '0' : 'a' - 10);
p[i] = number_chars[d];
}
return j;
}
@ -2545,11 +2548,26 @@ enum t_exactness {NO_EXACTNESS, INEXACT, EXACT};
/* R5RS, section 7.1.1, lexical structure of numbers: <uinteger R>. */
/* In non ASCII-style encodings the following macro might not work. */
#define XDIGIT2UINT(d) \
(uc_is_property_decimal_digit ((int) (unsigned char) d) \
? (d) - '0' \
: uc_tolower ((int) (unsigned char) d) - 'a' + 10)
/* Caller is responsible for checking that the return value is in range
for the given radix, which should be <= 36. */
static unsigned int
char_decimal_value (scm_t_uint32 c)
{
/* uc_decimal_value returns -1 on error. When cast to an unsigned int,
that's certainly above any valid decimal, so we take advantage of
that to elide some tests. */
unsigned int d = (unsigned int) uc_decimal_value (c);
/* If that failed, try extended hexadecimals, then. Only accept ascii
hexadecimals. */
if (d >= 10U)
{
c = uc_tolower (c);
if (c >= (scm_t_uint32) 'a')
d = c - (scm_t_uint32)'a' + 10U;
}
return d;
}
static SCM
mem2uinteger (SCM mem, unsigned int *p_idx,
@ -2568,9 +2586,7 @@ mem2uinteger (SCM mem, unsigned int *p_idx,
return SCM_BOOL_F;
c = scm_i_string_ref (mem, idx);
if (!uc_is_property_ascii_hex_digit ((scm_t_uint32) c))
return SCM_BOOL_F;
digit_value = XDIGIT2UINT (c);
digit_value = char_decimal_value (c);
if (digit_value >= radix)
return SCM_BOOL_F;
@ -2579,21 +2595,21 @@ mem2uinteger (SCM mem, unsigned int *p_idx,
while (idx != len)
{
scm_t_wchar c = scm_i_string_ref (mem, idx);
if (uc_is_property_ascii_hex_digit ((scm_t_uint32) c))
{
if (hash_seen)
break;
digit_value = XDIGIT2UINT (c);
if (digit_value >= radix)
break;
}
else if (c == '#')
if (c == '#')
{
hash_seen = 1;
digit_value = 0;
}
else
else if (hash_seen)
break;
else
{
digit_value = char_decimal_value (c);
/* This check catches non-decimals in addition to out-of-range
decimals. */
if (digit_value >= radix)
break;
}
idx++;
if (SCM_MOST_POSITIVE_FIXNUM / radix < shift)

View file

@ -1288,6 +1288,16 @@
(pass-if (string=? (number->string 255.0625 16) "FF.1"))
(pass-if (string=? (number->string (/ 1 3) 3) "1/10"))
(pass-if (string=? (number->string 10) "10"))
(pass-if (string=? (number->string 10 11) "A"))
(pass-if (string=? (number->string 36 36) "10"))
(pass-if (= (num->str->num 36 36) 36))
(pass-if (= (string->number "z" 36) 35))
(pass-if (= (string->number "Z" 36) 35))
(pass-if (not (string->number "Z" 35)))
(pass-if (string=? (number->string 35 36) "Z"))
(pass-if (= (num->str->num 35 36) 35))
;; Numeric conversion from decimal is not precise, in its current
;; implementation, so 11.333... and 1.324... can't be expected to
;; reliably come out to precise values. These tests did actually work