; Copyright (c) 1993-1999 by Richard Kelsey and Jonathan Rees. See file COPYING. ; Inexact rational arithmetic using hacked-in floating point numbers. (define floatnum? double?) (define-enumeration flop (+ - * / = < fixnum->float string->float float->string exp log sin cos tan asin acos atan sqrt floor integer? float->fixnum quotient remainder)) (define-syntax floperate (syntax-rules () ((floperate ?which ?x) (vm-extension (+ ?which 100) ?x)) ((floperate ?which ?x ?y) (vm-extension (+ ?which 100) (cons ?x ?y))) ((floperate ?which ?x ?y ?z) (vm-extension (+ ?which 100) (vector ?x ?y ?z))))) (define (float&float->float op) (lambda (a b) (let ((res (make-double))) (floperate op (x->float a) (x->float b) res) res))) (define (float&float->boolean op) (lambda (a b) (floperate op (x->float a) (x->float b)))) (define (float1 op) (lambda (float) (floperate op float))) (define (float->float op) (lambda (a) (let ((res (make-double))) (floperate op (x->float a) res) res))) (define (string->float string) (let ((res (make-double))) (floperate (enum flop string->float) string res) res)) ; Call the OS to get a string and then add a `.' if necessary (so that ; it will be inexact). (define (float->string float) (let* ((res (make-string 40 #\space)) (len (floperate (enum flop float->string) float res)) (str (substring res 0 len))) (let loop ((i 0)) (cond ((>= i (string-length str)) (string-append str ".")) ((char=? (string-ref str i) #\.) str) ((char=? (string-ref str i) #\e) (string-append (substring str 0 i) "." (substring str i (string-length str)))) (else (loop (+ i 1))))))) (define (x->float x) (cond ((double? x) x) ((integer? x) (exact-integer->float (if (exact? x) x (inexact->exact x)))) ((rational? x) ;; This loses when num or den overflows flonum range ;; but x doesn't. (float/ (numerator x) (denominator x))) (else (error "cannot coerce to a float" x)))) ; Conversion to/from exact integer (define (exact-integer->float k) (or (fixnum->float k) (float+ (float* (fixnum->float definitely-a-fixnum) (quotient k definitely-a-fixnum)) (fixnum->float (remainder k definitely-a-fixnum))))) (define (fixnum->float k) ;Returns #f is k is a bignum (let ((res (make-double))) (if (floperate (enum flop fixnum->float) k res) res #f))) (define (float->exact-integer x) (or (float->fixnum x) (let ((d (fixnum->float definitely-a-fixnum))) (+ (* definitely-a-fixnum (float->exact-integer (float-quotient x d))) (float->fixnum (loophole :double ; outsmarted ourselves (float-remainder x d))))))) (define definitely-a-fixnum (expt 2 23)) ;Be conservative (define integral-floatnum? (float1 (enum flop integer?))) (define float->fixnum (float1 (enum flop float->fixnum))) (define float+ (float&float->float (enum flop +))) (define float- (float&float->float (enum flop -))) (define float* (float&float->float (enum flop *))) (define float/ (float&float->float (enum flop /))) (define float-quotient (float&float->float (enum flop quotient))) (define float-remainder (float&float->float (enum flop remainder))) (define float-atan (float&float->float (enum flop atan))) (define float= (float&float->boolean (enum flop =))) (define float< (float&float->boolean (enum flop <))) (define float-exp (float->float (enum flop exp))) (define float-log (float->float (enum flop log))) (define float-sin (float->float (enum flop sin))) (define float-cos (float->float (enum flop cos))) (define float-tan (float->float (enum flop tan))) (define float-asin (float->float (enum flop asin))) (define float-acos (float->float (enum flop acos))) (define float-sqrt (float->float (enum flop sqrt))) (define float-floor (float->float (enum flop floor))) ; This lets you do ,open floatnum to get faster invocation (begin (define exp float-exp) (define log float-log) (define sin float-sin) (define cos float-cos) (define tan float-tan) (define asin float-asin) (define acos float-acos) (define atan float-atan) (define sqrt float-sqrt)) (define (float-fraction-length x) (let ((two (exact-integer->float 2))) (do ((x x (float* x two)) (i 0 (+ i 1))) ((integral-floatnum? x) i) (if (> i 1000) (error "I'm bored." x))))) (define (float-denominator x) (expt (exact-integer->float 2) (float-fraction-length x))) (define (float-numerator x) (float* x (float-denominator x))) (define (float->exact x) (if (integral-floatnum? x) (float->exact-integer x) ;+++ (let ((lose (lambda () (call-error "no exact representation" inexact->exact x))) (q (expt 2 (float-fraction-length x)))) (if (exact? q) (let ((e (/ (float->exact-integer (float* x (exact-integer->float q))) q))) (if (exact? e) e (lose))) (lose))))) ; Methods on floatnums (define-method &integer? ((x :double)) (integral-floatnum? x)) (define-method &rational? ((n :double)) #t) (define-method &exact? ((x :double)) #f) (define-method &inexact->exact ((x :double)) (float->exact x)) (define-method &exact->inexact ((x :rational)) (x->float x)) ;Should do this only if the number is within range. (define-method &floor ((x :double)) (float-floor x)) ; beware infinite regress (define-method &numerator ((x :double)) (float-numerator x)) (define-method &denominator ((x :double)) (float-denominator x)) (define (define-floatnum-method mtable proc) (define-method mtable ((m :rational) (n :rational)) (proc m n))) (define-floatnum-method &+ float+) (define-floatnum-method &- float-) (define-floatnum-method &* float*) (define-floatnum-method &/ float/) (define-floatnum-method "ient float-quotient) (define-floatnum-method &remainder float-remainder) (define-floatnum-method &= float=) (define-floatnum-method &< float<) (define-method &numerator ((x :rational)) (float-numerator x)) (define-method &denominator ((x :rational)) (float-denominator x)) (define-method &exp ((x :rational)) (float-exp x)) (define-method &log ((x :rational)) (float-log x)) (define-method &sqrt ((x :rational)) (float-sqrt x)) (define-method &sin ((x :rational)) (float-sin x)) (define-method &cos ((x :rational)) (float-cos x)) (define-method &tan ((x :rational)) (float-tan x)) (define-method &acos ((x :rational)) (float-acos x)) (define-floatnum-method &atan float-atan) (define-method &number->string ((n :double) radix) (if (= radix 10) (float->string n) (next-method))) ; Recognizing a floating point number. This doesn't know about `#'. (define (float-string? s) (let ((len (string-length s))) (define (start) (and (< 1 len) (let ((first (string-ref s 0)) (second (string-ref s 1))) (if (char-numeric? first) (digits 1 #f #f) (case first ((#\+ #\-) (and (char-numeric? second) (digits 2 #f #f))) ((#\.) (and (char-numeric? second) (digits 2 #t #f))) (else #f)))))) ; Read digits until the end or an `e' or a `.'. E-OR-DOT? is true if ; we have seen either, E? is true if we've seen an `e'. (define (digits i e-or-dot? e?) (if (= i len) e-or-dot? (let ((next (string-ref s i))) (if (char-numeric? next) (digits (+ i 1) e-or-dot? e?) (case next ((#\e #\E) (and (not e?) (exponent (+ i 1) #f))) ((#\.) (and (not e-or-dot?) (digits (+ i 1) #t #f))) (else #f)))))) ; Read in an exponent. If SIGN? is true then we have already got the sign. (define (exponent i sign?) (and (< i len) (let ((next (string-ref s i))) (if (char-numeric? next) (digits (+ i 1) #t #t) (case next ((#\+ #\-) (and (not sign?) (exponent (+ i 1) #t))) (else #f)))))) (start))) (define-simple-type :float-string (:string) float-string?) (define-method &really-string->number ((s :float-string) radix exact?) (if (and (= radix 10) (not exact?)) (string->float s) (next-method)))