A few things that come to mind:
The procedure inexact-real-from-integers can’t create negative zero. Perhaps the sign should be separate from the “int” part.
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.
I don’t see provisions to parse +/- infinity and nan.
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).
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?