Closed jpellegrini closed 1 year ago
By the way... Guile also does have an optimized version of exact->inexact
, but they use the low-level GMP functions that work on the internal GMP representation for numbers (the functions that deal with number "limbs") -- I thought this would be a bit too complex and didn't go that way.
Another great contribution. Thanks, @jpellegrini. PR is merged.
Hi @egallesio !
One more PR... :) The second one seems interesting, because
bignum2double
is used a lot in the code...1. Use
mpz_sgn
instead ofmpz_cmp_si
when possibleAt least for
positive?
andnegative?
there seems to be some speedup - and it seems simpler.2. Speed up
bignum2double
This patch adds only 20 lines of code, and a lot more of comments... :)
Currently,
bignum2double
does not use the functionmpz_get_d
since it gives an unspecified value when converting a number which is+inf
or-inf
.I have verified that it also brings rounding problems: if we use
mpz_get_d
, the resulting double will be rounded towards zero, but R7RS requires that we pick the closest number.What this patch does
1. Treat infinities separately
If the result does not fit a double, we return $\pm$
inf.0
We don't use the result ofmpz_get_d
because it does not guarantee that an inf will be returned in this case.Checks if the number fits into a double, comparing it to $\pm$
DBL_MAX
, and directly returns $\pm$inf.0
if the number doesn't fit.2. Use
mpz_get_d
and find the closest integer representable as doubleA very large integer may not be representable as a float.
mpz_get_d
always "rounds towards zero" -- that is, it returns the closest float to n that is between 0 and n, but R7RS requires 'inexact' to return the closest number. So we need to adapt.Suppose there are two representable integers around n, but n itself is not representable. Call those integers 'below' and 'above':
As the figure shows, even if there is an integer 'above' that is closer to n, the number 'below' (closer to 0) will be returned. Note that the whole picture could be reflected around zero if n is negative, so "above" actually means "farthest from zero" but not "largest":
We of course can be sure that there is no integer between 'below' and 'above'.
So we do the following:
Get the next representable double with
nextafter
(the one starting frombelow
, but away from zero). We call this one 'above'Convert both below and above back into bignums (!), as 'zbelow' and 'zabove'
Measure (using the GMP) the distances
ABS(zabove - n)
andABS(n - zbelow)
and if n is closer tozabove
, we returnceil(above)
orfloor(above)
, depending on the sign. If it's closer tobelow
, we returnbelow
.The speedup
Average of 10 runs is shown.
1. bignum does not fit a double
In this case the time for the currnet STklos implementation is proportional to the size of the number. We take $3^{431} \cdot 2^{560} \cdot 5 \cdot 11$ as an example.
2. bignum does fit a double
Both the STklos implementation and the proposed patch do depend on the size of the number, but there is a great speedup.
Tests
Both the numbers test in STklos and the tests for SRFI 144 detect rounding problems, and indeed, previous attempts for preparing this patch were wrong, and it was only because of those tests that I realized that.