[gambit-list] API to parse floats in Scheme
Lassi Kortela
lassi at lassi.io
Wed May 12 18:24:17 EDT 2021
> A few things that come to mind:
>
> 1) The procedure inexact-real-from-integers can’t create negative zero. Perhaps the sign should be separate from the “int” part.
>
> 2) In the code I have written in the past to convert strings to numbers all of the digits are accumulated in a single integer and the number of decimals is kept separately (your fac-length). The parameters “int” and “frac” could be combined into one. By doing this the frac-length and exp can also be combined.
>
> 3) I don’t see provisions to parse +/- infinity and nan.
>
> 4) Many languages now allow “_” in the digits, so it is not clear how this fits into your API. Maybe an extra parameter indicating the allowed separator (with a default of #f meaning no separator allowed).
>
> 5) The API for the procedure inexact-real-from-strings is not ideal to implement something like string->number, because there will be some calls to “substring” to extract each part. This will be slow and generate garbage. It would be better to have an API where the number of calls to “substring” can be minimized. For example the API could be a single procedure where the sign, int and exp parts can be a string, symbol or integer. Trailing parameters could be optional with a default of 0. Something like this:
>
> (make-inexact-real sign int exp precision)
>
> (make-inexact-real '- 0) ;; => -0.0
> (make-inexact-real '+ 123 -2) ;; => 1.23
> (make-inexact-real "+" "123" 10 'e) ;; => 1.23e12
>
> You can always do
>
> (make-inexact-real "-" (string-append "123" "45") (- 6 2) 'e)
>
> instead of (inexact-real-from-strings "-123" "45" 'e "6")
Excellent comments! I'm glad I asked for your advice. The interface you
suggest is indeed better than what we came up with.
Maybe the sign could be a fixnum or boolean? This would simplify the
procedure even further.
Gambit's lib/_num.scm has the string->number implementation with the
following subroutine, which takes all the arguments you suggest except
for precision:
(define (make-inexact-real sign uinteger exponent)
(let ((n
(if (and (##fixnum? uinteger)
(##fixnum->flonum-exact? uinteger)
(##fixnum? exponent)
(##fx< (##fx- exponent)
(##f64vector-length exact-10^n-table))
(##fx< exponent
(##f64vector-length exact-10^n-table)))
(if (##fx< exponent 0)
(##fl/ (##fixnum->flonum uinteger)
(##f64vector-ref exact-10^n-table
(##fx- exponent)))
(##fl* (##fixnum->flonum uinteger)
(##f64vector-ref exact-10^n-table
exponent)))
(##exact->inexact
(##* uinteger (##expt 10 exponent))))))
(if (##char=? sign #\-)
(##flcopysign n (macro-inexact--1))
n)))
Two other subroutines in the file:
(substring->uinteger string radix i j)
(float-substring->uinteger string i j)
At least substring->uinteger should probably be standardized.
number->string is too much powerful when one only wants to convert a
span of digits.
R7RS says the infinity and not-a-number syntax is:
+inf.0
-inf.0
+nan.0
-nan.0 (same as +nan.0)
Given that these are only three or four choices, do we need to support
them in a subroutine, or is it enough if the user can check whether the
text input is inf or nan and build one of these by hand? There is now
SRFI 208 (NaN procedures) which can work with NaNs on the bit level.
Supporting underscores (and space, commas, etc.) for separators is
certainly useful, and it simplifies a parser if it doesn't have remove
the separators itself. Should we add a char or string argument for which
separators to skip (#f to deny all separators), or add another procedure?
More information about the Gambit-list
mailing list