~ chicken-core (chicken-5) 15fd900137c5007d60e6300973df7e73b2934110
commit 15fd900137c5007d60e6300973df7e73b2934110 Author: Peter Bex <peter@more-magic.net> AuthorDate: Sun Mar 15 17:56:56 2015 +0100 Commit: Peter Bex <peter@more-magic.net> CommitDate: Sun May 31 14:55:23 2015 +0200 Convert exact->inexact to a horribly long and ugly C function, making it inlineable (needed for converting + and - to be inlineable) diff --git a/chicken.h b/chicken.h index f0387662..2970689e 100644 --- a/chicken.h +++ b/chicken.h @@ -2183,7 +2183,6 @@ C_fctexport double C_fcall C_bignum_to_double(C_word bignum) C_regparm; C_fctexport C_word C_fcall C_a_i_cpu_time(C_word **a, int c, C_word buf) C_regparm; /* XXX TODO OBSOLETE: This can be removed after recompiling c-platform.scm */ C_fctexport C_word C_fcall C_a_i_string_to_number(C_word **a, int c, C_word str, C_word radix) C_regparm; -/* XXX TODO OBSOLETE: This can be removed after recompiling c-platform.scm */ C_fctexport C_word C_fcall C_a_i_exact_to_inexact(C_word **a, int c, C_word n) C_regparm; C_fctexport C_word C_fcall C_i_file_exists_p(C_word name, C_word file, C_word dir) C_regparm; diff --git a/library.scm b/library.scm index 3c6f756f..e9b0190c 100644 --- a/library.scm +++ b/library.scm @@ -36,7 +36,7 @@ ##sys#format-here-doc-warning exit-in-progress maximal-string-length find-ratio-between find-ratio - make-complex flonum->ratnum ratnum rat+/- minimum-denorm-flonum-expt + make-complex flonum->ratnum ratnum rat+/- +maximum-allowed-exponent+ mantexp->dbl ldexp round-quotient ##sys#string->compnum ##sys#bignum-extract-digits ##sys#internal-gcd) (not inline ##sys#user-read-hook ##sys#error-hook ##sys#signal-hook ##sys#schedule @@ -1130,58 +1130,8 @@ EOF (inexact->exact (%cplxnum-imag x)))) (else (##sys#error-bad-number x 'inexact->exact)))) -;; Exponent of the lowest allowed flonum; if we get any lower we get zero. -;; In other words, this is the first (smallest) flonum after 0. -;; Equal to (expt 2.0 (- flonum-minimum-exponent flonum-precision)) -(define minimum-denorm-flonum-expt (fx- flonum-minimum-exponent flonum-precision)) - (define (exact->inexact x) - (cond ((##core#inline "C_fixnump" x) - (##core#inline_allocate ("C_a_i_fix_to_flo" 4) x)) - ((##core#inline "C_i_flonump" x) x) - ((##core#inline "C_i_bignump" x) - (##core#inline_allocate ("C_a_u_i_big_to_flo" 4) x)) - ((ratnum? x) - ;; This tries to keep the numbers within representable ranges - ;; and tries to drop as few significant digits as possible by - ;; bringing the two numbers to within the same powers of two. - ;; See algorithms M & N in Knuth, 4.2.1 - (let* ((n1 (%ratnum-numerator x)) - (an (##sys#integer-abs n1)) - (d1 (%ratnum-denominator x)) - ;; Approximate distance between the numbers in powers - ;; of 2 ie, 2^e-1 < n/d < 2^e+1 (e is the *un*biased - ;; value of e_w in M2) - ;; XXX: What if b != 2 (ie, flonum-radix is not 2)? - (e (fx- (integer-length an) (integer-length d1))) - (rnd (lambda (n d e) ; Here, 1 <= n/d < 2 (normalized) [N5] - ;; Cannot shift above the available precision, - ;; and can't have an exponent that's below the - ;; minimum flonum exponent. - (let* ((s (min (fx- flonum-precision 1) - (fx- e minimum-denorm-flonum-expt))) - (norm (##sys#/-2 (##sys#integer-shift n s) d)) - (r (round norm)) - (fraction (exact->inexact r)) - (exp (fx- e s))) - (let ((res (fp* fraction (expt 2.0 exp)))) - (if (negative? n1) (##sys#--2 0 res) res))))) - (scale (lambda (n d) ; Here, 1/2 <= n/d < 2 [N3] - (if (##sys#<-2 n d) ; n/d < 1? - ;; Scale left [N3]; only needed once (see note in M3) - (rnd (##sys#integer-shift n 1) d (fx- e 1)) - ;; Already normalized - (rnd n d e))))) - ;; After this step, which shifts the smaller number to - ;; align with the larger, "f" in algorithm N is represented - ;; in the procedures above by n/d. - (if (negative? e) - (scale (##sys#integer-shift an (##sys#--2 0 e)) d1) - (scale an (##sys#integer-shift d1 e))))) - ((cplxnum? x) - (make-complex (exact->inexact (%cplxnum-real x)) - (exact->inexact (%cplxnum-imag x)))) - (else (##sys#error-bad-number x 'exact->inexact)))) + (##core#inline_allocate ("C_a_i_exact_to_inexact" 12) x)) (define ##sys#exact->inexact exact->inexact) (define ##sys#inexact->exact inexact->exact) diff --git a/runtime.c b/runtime.c index 5de43743..b6db3da1 100644 --- a/runtime.c +++ b/runtime.c @@ -9777,17 +9777,171 @@ void C_ccall C_string_to_symbol(C_word c, C_word closure, C_word k, C_word strin C_kontinue(k, s); } +C_inline int integer_length_abs(C_word x) +{ + if (x & C_FIXNUM_BIT) { + return C_ilen(labs(C_unfix(x))); + } else { + C_uword result = (C_bignum_size(x) - 1) * C_BIGNUM_DIGIT_LENGTH, + *last_digit = C_bignum_digits(x) + C_bignum_size(x) - 1, + last_digit_length = C_ilen(*last_digit); + return result + last_digit_length; + } +} -/* XXX TODO OBSOLETE: This can be removed after recompiling c-platform.scm */ +/* This will usually return a flonum, but it may also return a cplxnum + * consisting of two flonums, making for a total of 12 words. + */ C_regparm C_word C_fcall -C_a_i_exact_to_inexact(C_word **a, int c, C_word n) -{ - if(n & C_FIXNUM_BIT) - return C_flonum(a, (double)C_unfix(n)); - else if(C_immediatep(n) || C_block_header(n) != C_FLONUM_TAG) - barf(C_BAD_ARGUMENT_TYPE_ERROR, "exact->inexact", n); +C_a_i_exact_to_inexact(C_word **ptr, int c, C_word n) +{ + if (n & C_FIXNUM_BIT) { + return C_flonum(ptr, (double)C_unfix(n)); + } else if (C_immediatep(n)) { + barf(C_BAD_ARGUMENT_TYPE_NO_NUMBER_ERROR, "exact->inexact", n); + } else if (C_block_header(n) == C_FLONUM_TAG) { + return n; + } else if (C_truep(C_bignump(n))) { + return C_a_u_i_big_to_flo(ptr, c, n); + } else if (C_block_header(n) == C_STRUCTURE3_TAG && + (C_block_item(n, 0) == C_cplxnum_type_tag)) { + return C_cplxnum(ptr, C_a_i_exact_to_inexact(ptr, 1, C_block_item(n, 1)), + C_a_i_exact_to_inexact(ptr, 1, C_block_item(n, 2))); + /* The horribly painful case: ratnums */ + } else if (C_block_header(n) == C_STRUCTURE3_TAG && + (C_block_item(n, 0) == C_ratnum_type_tag)) { + /* This tries to keep the numbers within representable ranges and + * tries to drop as few significant digits as possible by bringing + * the two numbers to within the same powers of two. See + * algorithms M & N in Knuth, 4.2.1. + */ + C_word num = C_block_item(n, 1), denom = C_block_item(n, 2), + /* e = approx. distance between the numbers in powers of 2. + * ie, 2^e-1 < n/d < 2^e+1 (e is the *un*biased value of + * e_w in M2. TODO: What if b!=2 (ie, flonum-radix isn't 2)? + */ + e = integer_length_abs(num) - integer_length_abs(denom), + ab[C_SIZEOF_FIX_BIGNUM*4], *a = ab, tmp1 = 0, tmp2 = 0, tmp3 = 0, + shift_amount, negp = C_i_integer_negativep(num), q, r, len; + C_uword *d; + double res, fraction; + + /* Simplify logic by ensuring bignums */ + if (num & C_FIXNUM_BIT) num = C_a_u_i_fix_to_big(&a, num); + if (denom & C_FIXNUM_BIT) denom = C_a_u_i_fix_to_big(&a, denom); + + /* Align numbers by shifting the smaller to the same size of the + * larger. After this, "f" in alg. N is represented by num/denom. + */ + if (e < 0) { + tmp1 = allocate_tmp_bignum(C_fix(C_bignum_size(denom)), + C_SCHEME_FALSE, C_SCHEME_TRUE); + d = C_bignum_digits(tmp1) - e / C_BIGNUM_DIGIT_LENGTH; + C_memcpy(d, C_bignum_digits(num), C_wordstobytes(C_bignum_size(num))); + shift_amount = -e % C_BIGNUM_DIGIT_LENGTH; + if(shift_amount > 0) { + bignum_digits_destructive_shift_left( + d, C_bignum_digits(tmp1) + C_bignum_size(tmp1), shift_amount); + } + num = tmp1; + } else if (e > 0) { + tmp1 = allocate_tmp_bignum(C_fix(C_bignum_size(num)), + C_SCHEME_FALSE, C_SCHEME_TRUE); + d = C_bignum_digits(tmp1) + e / C_BIGNUM_DIGIT_LENGTH; + C_memcpy(d, C_bignum_digits(denom), C_wordstobytes(C_bignum_size(denom))); + shift_amount = e % C_BIGNUM_DIGIT_LENGTH; + if(shift_amount > 0) { + bignum_digits_destructive_shift_left( + d, C_bignum_digits(tmp1) + C_bignum_size(tmp1), shift_amount); + } + denom = tmp1; + } + /* From here on, 1/2 <= n/d < 2 [N3] */ + if (C_truep(C_i_integer_lessp(num, denom))) { /* n/d < 1? */ + len = C_bignum_size(num) + 1; + tmp2 = allocate_tmp_bignum(C_fix(len), C_SCHEME_FALSE, C_SCHEME_FALSE); + bignum_digits_destructive_copy(tmp2, num); + d = C_bignum_digits(tmp2); + d[len-1] = 0; /* Init most significant digit */ + bignum_digits_destructive_shift_left(d, d + len, 1); + num = tmp2; + e -= 1; + } - return n; + /* Here, 1 <= n/d < 2 (normalized) [N5] */ + shift_amount = nmin(DBL_MANT_DIG-1, e - (DBL_MIN_EXP - DBL_MANT_DIG)); + + len = C_bignum_size(num) + shift_amount / C_BIGNUM_DIGIT_LENGTH + 1; + tmp3 = allocate_tmp_bignum(C_fix(len), C_SCHEME_FALSE, C_SCHEME_TRUE); + d = C_bignum_digits(tmp3) + shift_amount / C_BIGNUM_DIGIT_LENGTH; + C_memcpy(d, C_bignum_digits(num), C_wordstobytes(C_bignum_size(num))); + shift_amount = shift_amount % C_BIGNUM_DIGIT_LENGTH; + if (shift_amount > 0) { + bignum_digits_destructive_shift_left( + d, C_bignum_digits(tmp3) + len, shift_amount); + } + num = tmp3; + + /* Now, calculate round(num/denom). We start with a quotient&remainder */ + switch(bignum_cmp_unsigned(num, denom)) { + case 0: /* q = 1, r = 0 */ + q = allocate_tmp_bignum(C_fix(1), C_SCHEME_FALSE, C_SCHEME_FALSE); + *(C_bignum_digits(q)) = 1; + r = allocate_tmp_bignum(C_fix(0), C_SCHEME_FALSE, C_SCHEME_FALSE); + break; + + case -1: /* q = 0, r = num */ + q = allocate_tmp_bignum(C_fix(0), C_SCHEME_FALSE, C_SCHEME_FALSE); + len = C_bignum_size(num) + 1; /* Ensure we can shift left by one */ + r = allocate_tmp_bignum(C_fix(len), C_SCHEME_FALSE, C_SCHEME_FALSE); + bignum_digits_destructive_copy(r, num); + d = C_bignum_digits(r); + d[len-1] = 0; /* Initialize most significant digit */ + bignum_digits_destructive_shift_left(d, d + len, 1); + break; + + case 1: + default: + len = C_bignum_size(num) + 1 - C_bignum_size(denom); + q = allocate_tmp_bignum(C_fix(len), C_SCHEME_FALSE, C_SCHEME_FALSE); + len = C_bignum_size(num) + 1; /* LEN */ + r = allocate_tmp_bignum(C_fix(len), C_SCHEME_FALSE, C_SCHEME_FALSE); + bignum_destructive_divide_full(num, denom, q, r, C_SCHEME_TRUE); + d = C_bignum_digits(r); + /* There should always be room to shift left by 1 because of LEN */ + assert(C_ilen(d[len-1]) < C_BIGNUM_DIGIT_LENGTH); + bignum_digits_destructive_shift_left(d, d + len, 1); + break; + } + + /* Now q is the quotient, but to "round" result we need to + * adjust. This follows the semantics of the "round" procedure: + * Round away from zero on positive numbers (this is never + * negative). In case of exactly halfway, we round up if odd. + */ + fraction = C_bignum_to_double(q); + switch (basic_cmp(C_bignum_simplify(r), C_bignum_simplify(denom), "", 0)) { + case C_fix(0): + if (*(C_bignum_digits(q)) & 1) fraction += 1.0; + break; + case C_fix(1): + fraction += 1.0; + break; + default: /* if r <= denom, we're done */ break; + } + + free_tmp_bignum(q); + free_tmp_bignum(r); + if (tmp1) free_tmp_bignum(tmp1); + if (tmp2) free_tmp_bignum(tmp2); + if (tmp3) free_tmp_bignum(tmp3); + + shift_amount = nmin(DBL_MANT_DIG-1, e - (DBL_MIN_EXP - DBL_MANT_DIG)); + res = ldexp(fraction, e - shift_amount); + return C_flonum(ptr, C_truep(negp) ? -res : res); + } else { + barf(C_BAD_ARGUMENT_TYPE_NO_NUMBER_ERROR, "exact->inexact", n); + } } diff --git a/types.db b/types.db index dbe39e66..58e938bd 100644 --- a/types.db +++ b/types.db @@ -532,7 +532,8 @@ (exact->inexact (#(procedure #:clean #:enforce #:foldable) exact->inexact (number) (or float cplxnum)) ((float) (float) #(1)) - ((fixnum) (float) (##core#inline_allocate ("C_a_i_fix_to_flo" 4) #(1)))) + ((fixnum) (float) (##core#inline_allocate ("C_a_i_fix_to_flo" 4) #(1))) + ((number) (##core#inline_allocate ("C_a_i_exact_to_inexact" 12) #(1)))) (inexact->exact (#(procedure #:clean #:enforce #:foldable) inexact->exact (number) (or integer ratnum)) ((fixnum) (fixnum) #(1)) @@ -811,8 +812,10 @@ (##core#inline_allocate ("C_a_i_fix_to_flo" 4) #(1)))) ((cplxnum) (##core#inline_allocate ("C_a_i_flonum_atan2" 4) - (##sys#exact->inexact (##sys#slot #(1) '2)) - (##sys#exact->inexact (##sys#slot #(1) '1))))) + (##core#inline_allocate ("C_a_i_exact_to_inexact" 12) + (##sys#slot #(1) '2)) + (##core#inline_allocate ("C_a_i_exact_to_inexact" 12) + (##sys#slot #(1) '1))))) (numerator (#(procedure #:clean #:enforce #:foldable) numerator ((or float integer ratnum)) (or float integer)) ((fixnum) (fixnum) #(1))Trap