diff options
-rw-r--r-- | THANKS | 1 | ||||
-rw-r--r-- | libguile/numbers.c | 54 | ||||
-rw-r--r-- | test-suite/tests/numbers.test | 10 |
3 files changed, 46 insertions, 19 deletions
@@ -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 diff --git a/libguile/numbers.c b/libguile/numbers.c index fbc6cc8ab..c9fd891b9 100644 --- a/libguile/numbers.c +++ b/libguile/numbers.c @@ -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 if (hash_seen) + break; else - break; + { + 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) diff --git a/test-suite/tests/numbers.test b/test-suite/tests/numbers.test index 3c3e14fed..3432d7a06 100644 --- a/test-suite/tests/numbers.test +++ b/test-suite/tests/numbers.test @@ -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 |