mirror of
https://git.savannah.gnu.org/git/guile.git
synced 2025-04-30 11:50:28 +02:00
Simplify and improve scm_i_big2dbl, and add scm_i_big2dbl_2exp
* libguile/numbers.c (scm_i_big2dbl_2exp): New static function. (scm_i_big2dbl): Reimplement in terms of 'scm_i_big2dbl_2exp', with proper rounding. * test-suite/tests/numbers.test ("exact->inexact"): Add tests.
This commit is contained in:
parent
e08a12b535
commit
1eb6a33a30
2 changed files with 80 additions and 78 deletions
|
@ -330,81 +330,52 @@ scm_i_dbl2num (double u)
|
||||||
return scm_i_dbl2big (u);
|
return scm_i_dbl2big (u);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* scm_i_big2dbl() rounds to the closest representable double, in accordance
|
static SCM round_right_shift_exact_integer (SCM n, long count);
|
||||||
with R5RS exact->inexact.
|
|
||||||
|
|
||||||
The approach is to use mpz_get_d to pick out the high DBL_MANT_DIG bits
|
/* scm_i_big2dbl_2exp() is like frexp for bignums: it converts the
|
||||||
(ie. truncate towards zero), then adjust to get the closest double by
|
bignum b into a normalized significand and exponent such that
|
||||||
examining the next lower bit and adding 1 (to the absolute value) if
|
b = significand * 2^exponent and 1/2 <= abs(significand) < 1.
|
||||||
necessary.
|
The return value is the significand rounded to the closest
|
||||||
|
representable double, and the exponent is placed into *expon_p.
|
||||||
|
If b is zero, then the returned exponent and significand are both
|
||||||
|
zero. */
|
||||||
|
|
||||||
Bignums exactly half way between representable doubles are rounded to the
|
static double
|
||||||
next higher absolute value (ie. away from zero). This seems like an
|
scm_i_big2dbl_2exp (SCM b, long *expon_p)
|
||||||
adequate interpretation of R5RS "numerically closest", and it's easier
|
{
|
||||||
and faster than a full "nearest-even" style.
|
size_t bits = mpz_sizeinbase (SCM_I_BIG_MPZ (b), 2);
|
||||||
|
size_t shift = 0;
|
||||||
|
|
||||||
The bit test must be done on the absolute value of the mpz_t, which means
|
if (bits > DBL_MANT_DIG)
|
||||||
we need to use mpz_getlimbn. mpz_tstbit is not right, it treats
|
{
|
||||||
negatives as twos complement.
|
shift = bits - DBL_MANT_DIG;
|
||||||
|
b = round_right_shift_exact_integer (b, shift);
|
||||||
|
if (SCM_I_INUMP (b))
|
||||||
|
{
|
||||||
|
int expon;
|
||||||
|
double signif = frexp (SCM_I_INUM (b), &expon);
|
||||||
|
*expon_p = expon + shift;
|
||||||
|
return signif;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
In GMP before 4.2, mpz_get_d rounding was unspecified. It ended up
|
{
|
||||||
following the hardware rounding mode, but applied to the absolute
|
long expon;
|
||||||
value of the mpz_t operand. This is not what we want so we put the
|
double signif = mpz_get_d_2exp (&expon, SCM_I_BIG_MPZ (b));
|
||||||
high DBL_MANT_DIG bits into a temporary. Starting with GMP 4.2
|
scm_remember_upto_here_1 (b);
|
||||||
(released in March 2006) mpz_get_d now always truncates towards zero.
|
*expon_p = expon + shift;
|
||||||
|
return signif;
|
||||||
ENHANCE-ME: The temporary init+clear to force the rounding in GMP
|
}
|
||||||
before 4.2 is a slowdown. It'd be faster to pick out the relevant
|
}
|
||||||
high bits with mpz_getlimbn. */
|
|
||||||
|
|
||||||
|
/* scm_i_big2dbl() rounds to the closest representable double,
|
||||||
|
in accordance with R5RS exact->inexact. */
|
||||||
double
|
double
|
||||||
scm_i_big2dbl (SCM b)
|
scm_i_big2dbl (SCM b)
|
||||||
{
|
{
|
||||||
double result;
|
long expon;
|
||||||
size_t bits;
|
double signif = scm_i_big2dbl_2exp (b, &expon);
|
||||||
|
return ldexp (signif, expon);
|
||||||
bits = mpz_sizeinbase (SCM_I_BIG_MPZ (b), 2);
|
|
||||||
|
|
||||||
#if 1
|
|
||||||
{
|
|
||||||
/* For GMP earlier than 4.2, force truncation towards zero */
|
|
||||||
|
|
||||||
/* FIXME: DBL_MANT_DIG is the number of base-`FLT_RADIX' digits,
|
|
||||||
_not_ the number of bits, so this code will break badly on a
|
|
||||||
system with non-binary doubles. */
|
|
||||||
|
|
||||||
mpz_t tmp;
|
|
||||||
if (bits > DBL_MANT_DIG)
|
|
||||||
{
|
|
||||||
size_t shift = bits - DBL_MANT_DIG;
|
|
||||||
mpz_init2 (tmp, DBL_MANT_DIG);
|
|
||||||
mpz_tdiv_q_2exp (tmp, SCM_I_BIG_MPZ (b), shift);
|
|
||||||
result = ldexp (mpz_get_d (tmp), shift);
|
|
||||||
mpz_clear (tmp);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result = mpz_get_d (SCM_I_BIG_MPZ (b));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
/* GMP 4.2 or later */
|
|
||||||
result = mpz_get_d (SCM_I_BIG_MPZ (b));
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (bits > DBL_MANT_DIG)
|
|
||||||
{
|
|
||||||
unsigned long pos = bits - DBL_MANT_DIG - 1;
|
|
||||||
/* test bit number "pos" in absolute value */
|
|
||||||
if (mpz_getlimbn (SCM_I_BIG_MPZ (b), pos / GMP_NUMB_BITS)
|
|
||||||
& ((mp_limb_t) 1 << (pos % GMP_NUMB_BITS)))
|
|
||||||
{
|
|
||||||
result += ldexp ((double) mpz_sgn (SCM_I_BIG_MPZ (b)), pos + 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scm_remember_upto_here_1 (b);
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SCM
|
SCM
|
||||||
|
|
|
@ -3859,20 +3859,16 @@
|
||||||
|
|
||||||
(with-test-prefix "exact->inexact"
|
(with-test-prefix "exact->inexact"
|
||||||
|
|
||||||
|
;; Test "(exact->inexact n)", expect "want".
|
||||||
|
(define (test name n want)
|
||||||
|
(with-test-prefix name
|
||||||
|
(pass-if-equal "pos" want (exact->inexact n))
|
||||||
|
(pass-if-equal "neg" (- want) (exact->inexact (- n)))))
|
||||||
|
|
||||||
;; Test "(exact->inexact n)", expect "want".
|
;; Test "(exact->inexact n)", expect "want".
|
||||||
;; "i" is a index, for diagnostic purposes.
|
;; "i" is a index, for diagnostic purposes.
|
||||||
(define (try-i i n want)
|
(define (try-i i n want)
|
||||||
(with-test-prefix (list i n want)
|
(test (list i n want) n want))
|
||||||
(with-test-prefix "pos"
|
|
||||||
(let ((got (exact->inexact n)))
|
|
||||||
(pass-if "inexact?" (inexact? got))
|
|
||||||
(pass-if (list "=" got) (= want got))))
|
|
||||||
(set! n (- n))
|
|
||||||
(set! want (- want))
|
|
||||||
(with-test-prefix "neg"
|
|
||||||
(let ((got (exact->inexact n)))
|
|
||||||
(pass-if "inexact?" (inexact? got))
|
|
||||||
(pass-if (list "=" got) (= want got))))))
|
|
||||||
|
|
||||||
(with-test-prefix "2^i, no round"
|
(with-test-prefix "2^i, no round"
|
||||||
(do ((i 0 (1+ i))
|
(do ((i 0 (1+ i))
|
||||||
|
@ -3945,7 +3941,42 @@
|
||||||
;; convert the num and den to doubles, resulting in infs.
|
;; convert the num and den to doubles, resulting in infs.
|
||||||
(pass-if "frac big/big, exceeding double"
|
(pass-if "frac big/big, exceeding double"
|
||||||
(let ((big (ash 1 4096)))
|
(let ((big (ash 1 4096)))
|
||||||
(= 1.0 (exact->inexact (/ (1+ big) big))))))
|
(= 1.0 (exact->inexact (/ (1+ big) big)))))
|
||||||
|
|
||||||
|
(test "round up to odd"
|
||||||
|
;; =====================================================
|
||||||
|
;; 11111111111111111111111111111111111111111111111111000101 ->
|
||||||
|
;; 11111111111111111111111111111111111111111111111111001000
|
||||||
|
(+ (expt 2 (+ dbl-mant-dig 3)) -64 #b000101)
|
||||||
|
(+ (expt 2.0 (+ dbl-mant-dig 3)) -64 #b001000))
|
||||||
|
|
||||||
|
(test "round down to odd"
|
||||||
|
;; =====================================================
|
||||||
|
;; 11111111111111111111111111111111111111111111111111001011 ->
|
||||||
|
;; 11111111111111111111111111111111111111111111111111001000
|
||||||
|
(+ (expt 2 (+ dbl-mant-dig 3)) -64 #b001011)
|
||||||
|
(+ (expt 2.0 (+ dbl-mant-dig 3)) -64 #b001000))
|
||||||
|
|
||||||
|
(test "round tie up to even"
|
||||||
|
;; =====================================================
|
||||||
|
;; 11111111111111111111111111111111111111111111111111011100 ->
|
||||||
|
;; 11111111111111111111111111111111111111111111111111100000
|
||||||
|
(+ (expt 2 (+ dbl-mant-dig 3)) -64 #b011100)
|
||||||
|
(+ (expt 2.0 (+ dbl-mant-dig 3)) -64 #b100000))
|
||||||
|
|
||||||
|
(test "round tie down to even"
|
||||||
|
;; =====================================================
|
||||||
|
;; 11111111111111111111111111111111111111111111111111000100 ->
|
||||||
|
;; 11111111111111111111111111111111111111111111111111000000
|
||||||
|
(+ (expt 2 (+ dbl-mant-dig 3)) -64 #b000100)
|
||||||
|
(+ (expt 2.0 (+ dbl-mant-dig 3)) -64 #b000000))
|
||||||
|
|
||||||
|
(test "round tie up to next power of two"
|
||||||
|
;; =====================================================
|
||||||
|
;; 11111111111111111111111111111111111111111111111111111100 ->
|
||||||
|
;; 100000000000000000000000000000000000000000000000000000000
|
||||||
|
(+ (expt 2 (+ dbl-mant-dig 3)) -64 #b111100)
|
||||||
|
(expt 2.0 (+ dbl-mant-dig 3))))
|
||||||
|
|
||||||
;;;
|
;;;
|
||||||
;;; expt
|
;;; expt
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue